package brn.sim.data.dump;

import java.io.IOException;
import java.io.InputStream;

import sun.misc.HexDumpEncoder;

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

/**
 * Each packet in the dump file is prepended with this generic header.
 * This gets around the problem of different headers for different
 * packet interfaces.
 */
public class WiresharkMessage implements Message, JistAPI.DoNotRewrite {

  // ////////////////////////////////////////////////
  // constants
  //

  /**
   * 16 = 4 * 4 Byte header length
   */
  private static final int HEADER_LENGTH = 16;


  private static final byte[] etherHeader;

  static {
    etherHeader = new byte[14];
    for (int i = 0; i < etherHeader.length; i++)
      etherHeader[i] = (byte)0;

    Pickle.ushortToArray(Constants.MAC_ETHERTYPE_IP, etherHeader, 12);
  }


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

  /**
   * Time stamp (2 x 32 bit signed)
   */
  protected int tv_sec;
  protected int tv_usec;

  /**
   * 32 bit unsigned length of portion present
   */
  protected int caplen;

  /**
   * 32 bit unsigned length this packet (off wire)
   */
  protected int len;

  protected boolean fakeEtherHeader;

  /**
   * The actual payload.
   */
  protected Message payload;

  // ////////////////////////////////////////////////
  // initialization
  //

  public WiresharkMessage(int tv_sec, int tv_usec, Message payload) {
    super();
    this.tv_sec = tv_sec;
    this.tv_usec = tv_usec;
    this.len = payload.getSize();
    this.caplen = this.len;
    this.payload = payload;
    this.fakeEtherHeader = false;
  }

  public WiresharkMessage(long timeStamp, Message payload) {
    super();
    this.tv_sec = (int)(timeStamp / Constants.SECOND);
    this.tv_usec = (int)((timeStamp % Constants.SECOND) / Constants.MICRO_SECOND);
    this.len = payload.getSize();
    this.caplen = this.len;
    this.payload = payload;
    this.fakeEtherHeader = false;
  }

  public WiresharkMessage(long time, Message msg, boolean fakeEtherHeader) {
    this(time, msg);
    this.fakeEtherHeader = fakeEtherHeader;
  }


  public WiresharkMessage(InputStream inStream) throws IOException {
    int offset = 0;
    byte[] msg = new byte[16];
    int read = inStream.read(msg);
    if (-1 == read)
      return;

    tv_sec = (int) Pickle.arrayToInteger(msg, offset + 0);
    tv_usec = (int)Pickle.arrayToInteger(msg, offset + 4);
    caplen = (int)Pickle.arrayToInteger(msg, offset + 8);
    len = (int)Pickle.arrayToInteger(msg, offset + 12);

    msg = new byte[caplen];
    inStream.read(msg);
    payload = new MessageBytes(msg);
  }

  // ////////////////////////////////////////////////
  // accessors
  //

  public int getCaplen() {
    return caplen;
  }

  public int getLen() {
    return len;
  }

  public Message getPayload() {
    return payload;
  }

  public int getTv_sec() {
    return tv_sec;
  }

  public int getTv_usec() {
    return tv_usec;
  }

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

  public String toString() {
    String ret = getClass().getSimpleName() + "(tv=" + tv_sec + "." + tv_usec +
      ", caplen=" + caplen + ", len=" + len + ")" + "\n";

    HexDumpEncoder hde = new HexDumpEncoder();
    byte[] msg = new byte[payload.getSize()];
    payload.getBytes(msg, 0);

    ret += hde.encode(msg) + "\n";

    return ret;
  }

  public void getBytes(byte[] msg, int offset) {

    Pickle.uintToArray(tv_sec, msg, offset + 0);
    Pickle.uintToArray(tv_usec, msg, offset + 4);
    Pickle.uintToArray(caplen, msg, offset + 8);
    Pickle.uintToArray(len, msg, offset + 12);

//    intToByteArray(tv_sec, msg, offset + 0);
//    intToByteArray(tv_usec, msg, offset + 4);
//    intToByteArray(caplen, msg, offset + 8);
//    intToByteArray(len, msg, offset + 12);

    if (fakeEtherHeader) {
      System.arraycopy(etherHeader, 0, msg, offset + 16, etherHeader.length);
    }

    payload.getBytes(msg, offset + HEADER_LENGTH + (fakeEtherHeader ? 14 : 0));
  }


  public int getSize() {
    return payload.getSize() + HEADER_LENGTH + (fakeEtherHeader ? 14 : 0);
  }

  /* (non-Javadoc)
   * @see java.lang.Object#hashCode()
   */
  public int hashCode() {
    final int PRIME = 31;
    int result = 1;
    result = PRIME * result + caplen;
    result = PRIME * result + (fakeEtherHeader ? 1231 : 1237);
    result = PRIME * result + len;
    result = PRIME * result + ((payload == null) ? 0 : payload.hashCode());
    result = PRIME * result + tv_sec;
    result = PRIME * result + tv_usec;
    return result;
  }

  /* (non-Javadoc)
   * @see java.lang.Object#equals(java.lang.Object)
   */
  public boolean equals(Object obj) {
    if (this == obj)
      return true;
    if (obj == null)
      return false;
    if (getClass() != obj.getClass())
      return false;
    final WiresharkMessage other = (WiresharkMessage) obj;
    if (caplen != other.caplen)
      return false;
    if (fakeEtherHeader != other.fakeEtherHeader)
      return false;
    if (len != other.len)
      return false;
    if (payload == null) {
      if (other.payload != null)
        return false;
    } else if (!payload.equals(other.payload))
      return false;
    if (tv_sec != other.tv_sec)
      return false;
    if (tv_usec != other.tv_usec)
      return false;
    return true;
  }

  /**
   * Convert an integer into a byte array.
   *
   * @param i input integer to convert
   * @return corresponding byte array
   */
//  private static void intToByteArray(int i, byte[] b, int offset)
//  {
//    b[offset + 3] = (byte)(i & 0xff);
//    b[offset + 2] = (byte)((i>>8) & 0xff);
//    b[offset + 1] = (byte)((i>>16) & 0xff);
//    b[offset + 0] = (byte)((i>>24) & 0xff);
//  }

}
