package brn.sim.data.dump;

import java.io.IOException;

import jist.runtime.JistAPI;
import jist.runtime.Main;
import jist.runtime.Util;
import jist.swans.mac.AbstractMac;
import jist.swans.mac.Mac802_11;
import jist.swans.mac.MacDumb;
import jist.swans.mac.MacLoop;
import jist.swans.mac.AbstractMac.DiscardEvent;
import jist.swans.mac.AbstractMac.ReceiveEvent;
import jist.swans.mac.AbstractMac.SendEvent;
import jist.swans.misc.Event;
import jist.swans.misc.Message;
import jist.swans.misc.MessageEvent;
import jist.swans.net.AbstractNet;
import jist.swans.net.AbstractNet.RecvFromTransEvent;
import jist.swans.net.AbstractNet.SendToTransEvent;

/**
 * Event handler which receives message events and dumps them to the file system.
 * If you want to use this class, first create an instance and add the event
 * types you are interessed in via subscribeEvent(). All events must be
 * derived from {@link MessageEvent}.
 *
 * @author kurth
 */
public class WiresharkEventHandler implements Event.Handler {

  protected static final String LAYER_NET = "net";

  protected static final String LAYER_MAC = "mac";

  /** Introduce a dumper instance in each mac. */
  protected WiresharkDump[] dumpers;

  private String layer;
  private int encapType;

  public WiresharkEventHandler(String layer, int encapType) {
    this.layer = layer;
    this.encapType = encapType;

    dumpers = new WiresharkDump[20];

    // Install the shutdown hook
    JistAPI.runAt(new ShutdownHook(this), JistAPI.END);
  }


  public void registerHandlers() {
    if (layer.equals(LAYER_NET)) {
      subscribeEvent(SendToTransEvent.class);
      subscribeEvent(RecvFromTransEvent.class);
    }
    else if (layer.equals(LAYER_MAC)) {
      subscribeEvent(ReceiveEvent.class);
      subscribeEvent(SendEvent.class);
      subscribeEvent(DiscardEvent.class);
    }
    else
      throw new RuntimeException("Events for layer " + layer + " not declared");
  }


  /*
   * (non-Javadoc)
   * @see jist.swans.misc.Event.Handler#handle(jist.swans.misc.Event)
   */
  public void handle(Event event) {
    Message msg = ((MessageEvent) event).getData();

    try {
      dumpers[event.nodeId].dump(msg, JistAPI.getTime());
    }
    catch (IndexOutOfBoundsException e) {
      if (Main.ASSERT) Util.assertion(dumpers.length < event.nodeId);
      WiresharkDump[] newDumpers = new WiresharkDump[2*event.nodeId];
      System.arraycopy(dumpers, 0, newDumpers, 0, dumpers.length);
      dumpers = newDumpers;
      this.handle(event);
    }
    catch (NullPointerException e) {
      String fileName = "dump-" + layer + "-" + event.nodeId + ".pcap";
      try {
        dumpers[event.nodeId] = new WiresharkDump(fileName, encapType);
      } catch (IOException e1) {
        throw new RuntimeException("Error during wireshark dump write.", e);
      }
      this.handle(event);
    }
    catch (IOException e) {
      throw new RuntimeException("Error during wireshark dump write.", e);
    }
  }

  /**
   * Subscribe to the given event class.
   *
   * @param msgEventClass
   */
  protected void subscribeEvent(Class msgEventClass) {
    if (msgEventClass.isAssignableFrom(MessageEvent.class))
      throw new RuntimeException("Given event class must be derived from MessageEvent");

    Event.addHandler(msgEventClass, this);
  }

  public static int getEncapType(Object component) {
    if (component.getClass().getName().equals("brn.swans.mac.MacMcExOR"))
      return WiresharkDump.FAKE_DLT_IEEE802_11;
    else if (component instanceof Mac802_11)
      return WiresharkDump.FAKE_DLT_IEEE802_11;
    else if (component instanceof MacDumb)
      return WiresharkDump.FAKE_DLT_IEEE802_11;
    else if (component instanceof MacLoop)
      return WiresharkDump.FAKE_DLT_RAW;
    else if (component instanceof AbstractNet)
      return WiresharkDump.FAKE_DLT_RAW;

    throw new RuntimeException("Encap type of component " + component +
        " not known.");
  }

  private static String getLayer(Object component) {
    if (component instanceof AbstractMac)
      return LAYER_MAC;
    else if (component instanceof AbstractNet)
      return LAYER_NET;

    throw new RuntimeException("Layer string of component " + component +
        " not known.");
  }

  /**
   * Instanciate the wireshark dumper. Name pattern is Dump-NodeId.pcap
   */
  public static WiresharkEventHandler enableDumping(WiresharkEventHandler weh,
      Object component) {
    if (null != weh)
      return weh;

    weh = new WiresharkEventHandler(getLayer(component), getEncapType(component));
    weh.registerHandlers();

    return weh;
  }

  /**
   * Shutdown hook, closes the output file.
   *
   */
  private static class ShutdownHook implements Runnable {
    private WiresharkEventHandler handler;

    public ShutdownHook(WiresharkEventHandler handler) {
      this.handler = handler;
    }

    public void run() {
      WiresharkDump[] dump = handler.dumpers;
      for (int i=0; i < dump.length; i++) {
        try {
          if (null != dump[i])
            dump[i].close();
        } catch (IOException e) {
          throw new RuntimeException("Unable to close wireshark dump file.", e);
        }
      }
    }
  }

}
