package brn.sim.data.dump;

import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;

import jist.runtime.JistAPI;
import jist.swans.misc.Message;
import jist.swans.misc.Pickle;

import org.apache.log4j.Logger;

/**
 * Writes JiST packets in a wireshark-compatible format out on disk.
 *
 *
 * @author kurth
 *
 */
public class WiresharkDump implements JistAPI.DoNotRewrite {

  public static final Logger log = Logger.getLogger(WiresharkDump.class
      .getName());

  // ////////////////////////////////////////////////
  // Constants
  //

  /*
   * Canonical (pcap file) data link types (may differ from host versions)
   */

  /** Unknown */
  public static final int FAKE_DLT_NONE = (-1);

  /** Null encapsulation */
  public static final int FAKE_DLT_NULL = 0;

  /** Ethernet (10Mb) */
  public static final int FAKE_DLT_EN10MB = 1;

  /** PPP */
  public static final int FAKE_DLT_PPP = 9;

  /** FDDI */
  public static final int FAKE_DLT_FDDI = 10;

  /** PPP or Cisco HDLC */
  public static final int FAKE_DLT_PPP_HDLC = 50;

  /** RFC 1483-encapsulated ATM */
  public static final int FAKE_DLT_ATM_RFC1483 = 100;

  /** raw IP */
  public static final int FAKE_DLT_RAW = 101;

  /** Cisco HDLC */
  public static final int FAKE_DLT_C_HDLC = 104;

  /** IEEE 802.11 wireless */
  public static final int FAKE_DLT_IEEE802_11 = 105;

  /** Linux cooked socket */
  public static final int FAKE_DLT_LINUX_SLL = 113;

  /** 802.11+Prism II monitor code */
  public static final int FAKE_DLT_PRISM_HEADER = 119;

  /** Aironet wireless header */
  public static final int FAKE_DLT_AIRONET_HEADER = 120;

  /** Aironet wireless header */
  public static final int FAKE_DLT_RADIOTAP_HEADER = 127;

  /** Full Frontal ATM: ATM header + ATM_RFC1483 */
  public static final int FAKE_DLT_SUNATM = 123;

  /* Host data link types */
  /** raw IP */
  public static final int FAKE_DLT_HOST_RAW = 12;

  // ////////////////////////////////////////////////
  // Inner classes
  //

  protected static class WiresharkFileHeader implements JistAPI.DoNotRewrite {

    public static final int HEADER_SIZE = 24;

    private static final int FAKE_PCAP_MAGIC = 0xA1B2C3D4;

    private static final short FAKE_PCAP_VERSION_MAJOR = 2;

    private static final short FAKE_PCAP_VERSION_MINOR = 4;

    private static final int TIMEZONE_GMT = 0;

    private static final int SIGFIGS = 0;

    /**
     * 32 bit unsigned magic number.
     */
    protected int magic;

    /**
     * 16 bit unsigned version major.
     */
    protected short version_major;

    /**
     * 16 bit unsigned version minor.
     */
    protected short version_minor;

    /**
     * 32 bit signed gmt to local correction.
     */
    protected int thiszone;

    /**
     * 32 bit unsigned accuracy of timestamps.
     */
    protected int sigfigs;

    /**
     * 32 bit unsigned max length saved portion of each pkt.
     */
    protected int snaplen;

    /**
     * 32 bit unsigned data link type (DLT_*).
     */
    protected int linktype;

    public WiresharkFileHeader(int snaplen, int linktype) {
      this.magic = FAKE_PCAP_MAGIC;
      this.version_major = FAKE_PCAP_VERSION_MAJOR;
      this.version_minor = FAKE_PCAP_VERSION_MINOR;
      this.thiszone = TIMEZONE_GMT;
      this.sigfigs = SIGFIGS;
      this.snaplen = snaplen;
      this.linktype = linktype;
    }

    public WiresharkFileHeader(InputStream inStream) throws IOException {
      byte[] data = new byte[HEADER_SIZE];
      int offset = 0;

      inStream.read(data);

      magic = (int) Pickle.arrayToUInt(data, offset + 0);
      version_major = (short) Pickle.arrayToUShort(data, offset + 4);
      version_minor = (short) Pickle.arrayToUShort(data, offset + 6);
      thiszone = Pickle.arrayToInteger(data, offset + 8);
      sigfigs = (int) Pickle.arrayToUInt(data, offset + 12);
      snaplen = (int) Pickle.arrayToUInt(data, offset + 16);
      linktype = (int) Pickle.arrayToUInt(data, offset + 20);
    }

    /**
     * Returns a byte representation of this header.
     */
    public void getBytes(byte[] data, int offset) {
      Pickle.uintToArray(magic, data, offset + 0);
      Pickle.ushortToArray(version_major, data, offset + 4);
      Pickle.ushortToArray(version_minor, data, offset + 6);
      Pickle.integerToArray(thiszone, data, offset + 8);
      Pickle.uintToArray(sigfigs, data, offset + 12);
      Pickle.uintToArray(snaplen, data, offset + 16);
      Pickle.uintToArray(linktype, data, offset + 20);
    }

    public int getSize() {
      return HEADER_SIZE;
    }
  }

  // ////////////////////////////////////////////////
  // locals
  //

  /**
   * Writer used for output.
   *
   */
  private java.io.OutputStream outStream;

  private java.io.InputStream inStream;


  // ////////////////////////////////////////////////
  // Initialisation
  //

  public WiresharkDump(String fileName, int encapType) throws IOException {
    create(fileName, encapType);
  }

  public WiresharkDump() {
  }

  // ////////////////////////////////////////////////
  // Accessory
  //

  // ////////////////////////////////////////////////
  // overwrites
  //

  // ////////////////////////////////////////////////
  // call interface
  //

  public void open(String fileName) throws IOException {
    inStream = new BufferedInputStream(new FileInputStream(fileName));

    // skip header info
    new WiresharkFileHeader(inStream);
  }

  public WiresharkMessage read() throws IOException {
    WiresharkMessage msg = new WiresharkMessage(inStream);
    if (null == msg.getPayload())
      return null;
    return msg;
  }

  /**
   * Closes the underlaying stream and flushes all bytes to disk.
   *
   * @throws IOException
   */
  public void close() throws IOException {
    outStream.flush();
    outStream.close();
    if (log.isDebugEnabled())
      log.debug("Closed dump file.");
  }

  /**
   * Writes the given message to the output.
   *
   * @param msg
   * @param time
   * @throws IOException
   */
  public void dump(Message msg, long time) throws IOException {
    this.dump(msg, time, false);
  }

  /**
   * Writes the given message to the output.
   *
   * @param msg
   * @param time
   * @param fakeEtherHeader
   * @throws IOException
   */
  public void dump(Message msg, long time, boolean fakeEtherHeader) throws IOException {
    if (log.isDebugEnabled())
      log.debug("Writing message " + msg.toString() + " to dump.");

    WiresharkMessage wsm = new WiresharkMessage(time, msg, fakeEtherHeader);

    byte[] data = new byte[wsm.getSize()];
    try {
      wsm.getBytes(data, 0);
    }
    catch (IndexOutOfBoundsException e) {
      log.error(e.toString());
      return;
    }

    outStream.write(data);
  }

  // ////////////////////////////////////////////////
  // Implementation
  //

  private void create(String fileName, int encapType) throws IOException {

    // Open the file
    outStream = new java.io.BufferedOutputStream(new java.io.FileOutputStream(
        fileName));

    WiresharkFileHeader header = new WiresharkFileHeader(3000, encapType);

    byte[] data = new byte[header.getSize()];
    header.getBytes(data, 0);
    outStream.write(data);
    outStream.flush();

    if (log.isDebugEnabled())
    log.debug("Created dump file " + fileName + " with encap type "
            + encapType);
  }

}
