package brn.swans.app.rtp.msg;

import jist.swans.misc.Message;
import jist.swans.misc.MessageBytes;
import jist.swans.misc.Pickle;

/**
 * This class represents an rtp data packet. Real data can either be
 * encapsulated or simulated by adding length. Following rtp header fields exist
 * but aren't used: padding, extension, csrcCount. The csrcList can't be used
 * because csrcCount is always 0.
 */
public class RtpMessage implements Message, Comparable {

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

  private static final int HEADER_LENGTH_FIXED = 12;

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

  /**
   * The rtp version of this RTPPacket.
   */
  private byte version;

  /**
   * Set to 1 if padding is used in this RTPPacket, 0 otherwise. This
   * implementation doesn't use padding bytes, so it is always 0.
   */
  private boolean padding;

  /**
   * Set to 1, if this RTPPacket contains an rtp header extension, 0 otherwise.
   * This implementation doesn't support rtp header extensions, so it is always
   * 0.
   */
  private boolean extension;

  /**
   * Stores the number (0..31) of contributing sources for this RTPPacket. It is
   * always 0 because contributing sources are added by rtp mixers which aren't
   * implemented.
   */
  private byte csrcCount;

  /**
   * The marker of this RTPPacket.
   */
  private boolean marker;

  /**
   * The type of payload carried in this RTPPacket.
   */
  private byte payloadType;

  /**
   * The sequence number of this RTPPacket (unsigned).
   */
  private short sequenceNumber;

  /**
   * The rtp time stamp of this RTPPacket (unsigned).
   */
  private int timeStamp;

  /**
   * The ssrc identifier of the creator of this RTPPacket (unsigned).
   */
  private int ssrc;

  // no mixers, no contributing sources
  // int _csrc[];

  /**
   * The payload.
   */
  private Message payload;

  /**
   * Time when the packet arrived.
   */
  private long arrivalTime;

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

  public RtpMessage() {
    this(null);
  }

  public RtpMessage(Message payload) {
    this.version = 2;
    this.padding = false;
    this.extension = false;
    this.csrcCount = 0;
    this.marker = false;
    this.payloadType = 0;
    this.sequenceNumber = 0;
    this.timeStamp = 0;
    this.ssrc = 0;
    this.payload = payload;
  }

  public RtpMessage(byte[] msg, int offset) {

    // Byte 0, bits 0,1: version
    version = (byte)((msg[offset] >> 6) & 0x03);
    // Byte 0, bit 2: padding
    padding = (msg[offset] & 0x20) == 0x20;
    // Byte 0, bit 3: extension
    extension = (msg[offset] & 0x10) == 0x10;
    // Byte 0, bit 4-7: crsc count
    csrcCount = (byte)(msg[offset] & 0x0F);

    // Byte 1, bit 0: marker
    marker = (msg[offset+1] & 0x80) == 0x80;
    // Byte 1, bit 1-7: payload type
    payloadType = (byte)(msg[offset+1] & 0x7F);

    // Byte 2-3: sequence number
    sequenceNumber = (short)Pickle.arrayToUShort(msg, offset + 2);
    // Byte 4-7: timestamp
    timeStamp = (int) Pickle.arrayToUInt(msg, offset + 4);
    // Byte 8-11: ssrc
    ssrc = (int)Pickle.arrayToUInt(msg, offset + 8);
    // optional: csrc list 0-15 entries a 32 bit
    for (int i = 0; i < csrcCount; i++) {
      // TODO not implemented
      //Pickle.uintToArray(ssrc, msg, offset + 8);
      throw new RuntimeException("CSRC list not implemented");
    }
    // optional: header extensions
    if (extension) {
      throw new RuntimeException("Header extension not implemented");
    }
    // Data
    int length = msg.length - HEADER_LENGTH_FIXED - offset;
    byte[] data = new byte[length];
    System.arraycopy(msg, offset + 12, data, 0, length);
    payload = new MessageBytes(data);
  }

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

  public byte getCsrcCount() {
    return csrcCount;
  }

  public void setCsrcCount(byte csrcCount) {
    this.csrcCount = csrcCount;
  }

  public boolean getExtension() {
    return extension;
  }

  public void setExtension(boolean extension) {
    this.extension = extension;
  }

  public boolean getMarker() {
    return marker;
  }

  public void setMarker(boolean marker) {
    this.marker = marker;
  }

  public Message getPayload() {
    return payload;
  }

  public void setPayload(Message payload) {
    this.payload = payload;
  }

  public byte getPayloadType() {
    return payloadType;
  }

  public void setPayloadType(byte payloadType) {
    this.payloadType = payloadType;
  }

  public short getSequenceNumber() {
    return sequenceNumber;
  }

  public void setSequenceNumber(short sequenceNumber) {
    this.sequenceNumber = sequenceNumber;
  }

  public int getSsrc() {
    return ssrc;
  }

  public void setSsrc(int ssrc) {
    this.ssrc = ssrc;
  }

  public int getTimeStamp() {
    return timeStamp;
  }

  public void setTimeStamp(int timeStamp) {
    this.timeStamp = timeStamp;
  }

  public byte getVersion() {
    return version;
  }

  public long getArrivalTime() {
    return arrivalTime;
  }

  public void setArrivalTime(long arrivalTime) {
    this.arrivalTime = arrivalTime;
  }

  public boolean getPadding() {
    return padding;
  }


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

  public String toString() {
    StringBuffer sa = new StringBuffer();
    sa.append("RTPPacket [payloadType=");
    sa.append(payloadType);
    sa.append(", sequenceNumber=");
    sa.append((int)(sequenceNumber & 0xFFFF));
    sa.append(", timeStamp=");
    sa.append(timeStamp);
    sa.append(", payloadLength=");
    sa.append(payload.getSize());
    sa.append(", marker=");
    sa.append(marker);
    sa.append("]");
    return sa.toString();
  }

  /**
   * Compares two RTPPacket objects by comparing their sequence numbers.
   */
  public int compareTo(Object arg0) {
    if (arg0.getClass() != this.getClass())
      return -1;
    return (getSequenceNumber() - ((RtpMessage) arg0).getSequenceNumber());
  }

  // ////////////////////////////////////////////////
  // Message interface impl
  //

  /**
   * Returns the length of the fixed header of an RTPPacket.
   */
  public int getFixedHeaderLength() {
    return HEADER_LENGTH_FIXED;
  }

  /**
   * Returns the length of the header (fixed plus variable part) of this
   * RTPPacket.
   */
  public int getHeaderLength() {
    // fixed header is 12 bytes long,
    // add 4 bytes for every csrc identifier
    return (getFixedHeaderLength() + 4 * csrcCount);
  }

  /*
   * (non-Javadoc)
   *
   * @see jist.swans.misc.Message#getSize()
   */
  public int getSize() {
    // TODO Auto-generated method stub
    return getHeaderLength() + (payload != null ? payload.getSize() : 0);
  }

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

    byte b = 0;
    // Byte 0, bits 0,1: version
    b |= (version & 0x03) << 6;
    // Byte 0, bit 2: padding
    b |= (padding ? 0x20 : 0x00);
    // Byte 0, bit 3: extension
    b |= (extension ? 0x10 : 0x00);
    // Byte 0, bit 4-7: crsc count
    b |= (csrcCount & 0x0F);
    Pickle.ubyteToArray(b, msg, offset);

    b = 0;
    // Byte 1, bit 0: marker
    b |= (marker ? 0x80 : 0x00);
    // Byte 1, bit 1-7: payload type
    b |= payloadType & 0x7F;
    Pickle.ubyteToArray(b, msg, offset + 1);

    // Byte 2-3: sequence number
    Pickle.ushortToArray(sequenceNumber, msg, offset + 2);
    // Byte 4-7: timestamp
    Pickle.uintToArray(timeStamp, msg, offset + 4);
    // Byte 8-11: ssrc
    Pickle.uintToArray(ssrc, msg, offset + 8);
    // optional: csrc list 0-15 entries a 32 bit
    for (int i = 0; i < csrcCount; i++) {
      // TODO not implemented
      //Pickle.uintToArray(ssrc, msg, offset + 8);
      throw new RuntimeException("CSRC list not implemented");
    }
    // optional: header extensions
    if (extension) {
      throw new RuntimeException("Header extension not implemented");
    }
    // Data
    payload.getBytes(msg, offset + 12);
  }

  public static RtpMessage fromBytes(Message msg) {
    if (msg instanceof RtpMessage)
      return (RtpMessage)msg;

    return new RtpMessage(((MessageBytes)msg).getBytes(), 0);
  }

  /* (non-Javadoc)
   * @see java.lang.Object#hashCode()
   */
  public int hashCode() {
    final int PRIME = 31;
    int result = 1;
    result = PRIME * result + (int) (arrivalTime ^ (arrivalTime >>> 32));
    result = PRIME * result + (extension ? 1231 : 1237);
    result = PRIME * result + (marker ? 1231 : 1237);
    result = PRIME * result + (padding ? 1231 : 1237);
    result = PRIME * result + ((payload == null) ? 0 : payload.hashCode());
    result = PRIME * result + sequenceNumber;
    result = PRIME * result + ssrc;
    result = PRIME * result + timeStamp;
    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 RtpMessage other = (RtpMessage) obj;
    if (arrivalTime != other.arrivalTime)
      return false;
    if (csrcCount != other.csrcCount)
      return false;
    if (extension != other.extension)
      return false;
    if (marker != other.marker)
      return false;
    if (padding != other.padding)
      return false;
    if (payload == null) {
      if (other.payload != null)
        return false;
    } else if (!payload.equals(other.payload))
      return false;
    if (payloadType != other.payloadType)
      return false;
    if (sequenceNumber != other.sequenceNumber)
      return false;
    if (ssrc != other.ssrc)
      return false;
    if (timeStamp != other.timeStamp)
      return false;
    if (version != other.version)
      return false;
    return true;
  }

}
