package nxm.net.lib;

import nxm.sys.inc.DataTypes;
import nxm.sys.inc.InternalUseOnly;
import nxm.sys.lib.Convert;

/** Utility class for parsing an RTP Header
 * <pre>
 *  The RTP header has the following format:
 * 0                   1                   2                   3
 * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
 *+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 *|V=2|P|X|  CC   |M|     PT      |       sequence number         |
 *+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 *|                           timestamp                           |
 *+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 *|           synchronization source (SSRC) identifier            |
 *+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
 *|            contributing source (CSRC) identifiers             |
 *|                             ....                              |
 *+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 *The CC in RTPVPXCC tells how many CSRC's (0-15) follow the fixed header
 *If the X bit in the RTP header is one, a variable-length header
 *extension is appended to the RTP header, following the CSRC list if
 *present. The header extension contains a 16-bit length field that
 *counts the number of 32-bit words in the extension, excluding the
 *four-octet extension header (therefore zero is a valid length).
 * 0                   1                   2                   3
 * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
 *+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 *|      defined by profile       |           length              |
 *+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 *|                        header extension                       |
 *|                             ....                              |
 * </pre>
 *
 * @author Jay Hallam, Jeanette McCall
 * @since NeXtMidas 3.8.1
 *
 */
public class RTPHeader {

  /** Byte containing RTP Version, P (Padding), X (Extension), CC (CSRC count -
   * i.e. number of identifiers that follow the fixed header) */
  private static final byte VPXCC_BYTE=0;
  /** Byte containing RTP M (Marker), PT (Payload Type) */
  private static final byte MPT_BYTE=1;
  /** Byte containing RTP Sequence Number (high octet) */
  private static final byte SQH_BYTE=2;
  /** Byte containing RTP Sequence Number (high octet) */
  private static final byte SQL_BYTE=3;
  /** First byte containing RTP Timestamp */
  private static final byte TS_BYTE=4;
  /** First byte containing RTP SSRC (synchronization source identifier) */
  private static final byte SSRC_BYTE=8;
  /** Starting Byte for RTP Data */
  private static final byte DATA_BYTE=12;
  private static final int NUM_BYTES_EXT_HEADER_ID = 2;
  private static final int BYTES_PER_CC = 4;
  /** bytes per word */
  @InternalUseOnly
  public static final int BYTES_PER_WORD = 4;

  private boolean validHeader = false;
  private String invalidMessage = null;

  /** RTP Payload type: ITU-T G.711 PCM Mu-Law audio */
  public static final int PAYLOADTYPE_AUDIO_MULAW = 0;
  /** RTP Payload type: Linear audio */
  public static final int PAYLOADTYPE_AUDIO_LINEAR = 96;
  /** RTP Payload type: Speex audio */
  public static final int PAYLOADTYPE_AUDIO_SPEEX = 101;
  /** RTP Payload type: Raw */
  public static final int PAYLOADTYPE_RAW = 97;
  /** RTP Payload type: Spectral ? */
  public static final int PAYLOADTYPE_SPECTRA = 98;

  /** Mask for CSRC count */
  private static final int CC_MASK      = 0xF;
  /** Mask for Extension */
  private static final int X_MASK       = 0x10;
  /** Mask for Padding */
  private static final int P_MASK       = 0x20;
  /** Mask for Version */
  private static final int VERSION_MASK = 0xC0;
  /** Mask for Payload Type */
  private static final int PT_MASK      = 0x7F;
  /** Mask for Marker */
  private static final int M_MASK       = 0x80;

  /** Mask for lower byte worth of integer */
  private static final int LOWER_BYTE_MASK = 0x000000FF;

  /** Maximum sequence value */
  public static final int MAX_SEQUENCE_VAL = 0xFFFF;

  /** Maximum gap in sequence number before sequence number is assumed to be behind rather than ahead */
  static int MAX_AHEAD_SEQUENCE_NUM = (int) (MAX_SEQUENCE_VAL * 0.7);

  /**
   * The following is from the AppGEO 2.1 developer reference guide.
   * The profile is followed by a 16-bit length field that counts the number of 32-bit words in the extension, excluding
   * the 4 byte extension header. The extension should be padded if necessary to end on a 32-bit word boundary.
   */
  static final int EXTENDED_HEADER_ADJUST_DATA_START = 4;

  /** Indicates that this value is not available */
  private static final int NOT_AVAILABLE = -1;

  /** CC (CSRC count) */
  private int csrcCount            = 0;
  /** X (Extension Flag) */
  private int extFlag              = 0;
  /** P (Padding) */
  private int padding              = 0;
  /** Version */
  private int version              = 0;
  /** PT (Payload Type) */
  private int payloadType          = 0;
  /** M (Marker) */
  private int marker               = 0;
  /** Sequence Number */
  private int sequenceNumber       = -1;
  /** Extension Type */
  private int extensionType        = 0;
  /** Extension Length (Bytes)*/
  private int extensionLengthBytes = 0;
  /** Header Length */
  private int headerLength         = 0;
  /** Timestamp */
  private int timeStamp            = 0;
  /** SSRC (Synchronization source identifier */
  private int ssrc                 = 0;
  /** */
  private int extensionLengthWords = 0;
  /** Array to hold all the contributing sources */
  private String[] csrcs;

  /**
   * Default constructor
   */
  public RTPHeader () {
  }

  /**
   * parses the header from the given buffer.
   * @param rtpBuf the byte buffer
   */
  public void parseHeader (byte[] rtpBuf) {
    // this will never return anything valid, why is this method available?
    // It does that because NOT_AVALIABLE = -1, When that is used for the length, it returns invalid. So, there is only one return here and that is invalid.
    parseHeader(rtpBuf, 0, NOT_AVAILABLE);
  }

  /**
   * Utility method to return the sequence number of the RTPHeader
   * @param     rtpBuf The buffer containing the header
   * @param     offset Offset to where the start of the header is located in the rtpBuf
   * @return    The sequence number
   */
  public static int parseSequenceNumber(byte[] rtpBuf, int offset) {
    int seqNum = Convert.unpackI(rtpBuf, offset+SQH_BYTE, DataTypes.IEEE);
    if (seqNum < 0) seqNum = 0x10000 + seqNum; // create unsigned sequence number
    return seqNum;
  }

  /**
   * Utility method to return the payload type from the RTPHeader
   * @param rtpBuf The buffer containing the header
   * @param offset Offset to where the start of the header is located in the rtpBuf
   * @return The payload Type
   */
  public static int parsePayloadType(byte[] rtpBuf, int offset) {
    return rtpBuf[offset+MPT_BYTE]   & PT_MASK;
  }

  /**
   * Parses the RTP Header from the given buffer
   * @param rtpBuf The buffer containing the header
   * @param offset Offset to where the start of the header is located in the rtpBuf
   * @param len    The length of the buffer
   */
  public void parseHeader (byte[] rtpBuf, int offset, int len) {
    validHeader = true;
    invalidMessage = null;
    if (len == NOT_AVAILABLE) len = rtpBuf.length;
    if (len < DATA_BYTE) {
      // Error - handle this
      validHeader = false;
      invalidMessage = "Input buffer < minimum RTP buffer size = "+DATA_BYTE+" bytes";
      return;
    }

    csrcCount      = rtpBuf[offset+VPXCC_BYTE] & CC_MASK;
    csrcs = new String[csrcCount];
    int boff = DATA_BYTE;
    for (int ci=0;ci<csrcCount;ci++) {
      csrcs[ci] = rtpBuf[boff]+"."+rtpBuf[boff+1]+"."+rtpBuf[boff+2]+"."+rtpBuf[boff+3];
      boff+=4;
    }
    extFlag = rtpBuf[offset+VPXCC_BYTE] & X_MASK;
    padding = rtpBuf[offset+VPXCC_BYTE] & P_MASK;
    version = rtpBuf[offset+VPXCC_BYTE] & VERSION_MASK;
    payloadType = parsePayloadType(rtpBuf, offset);
    marker  = rtpBuf[offset+MPT_BYTE]   & M_MASK;

    // Compute sequence number
    sequenceNumber = parseSequenceNumber(rtpBuf, offset);

    // Compute time stamp
    timeStamp = Convert.unpackL(rtpBuf, offset+TS_BYTE, DataTypes.IEEE);

    ssrc = Convert.unpackL(rtpBuf, offset+SSRC_BYTE, DataTypes.IEEE);

    // Now lets compute the data start the right way
    int computedHeaderLen = DATA_BYTE + (csrcCount * BYTES_PER_CC); // location right after the CRSC identifiers
    extensionLengthBytes = 0;
    extensionType = 0; // rtp extension type

    if (extFlag > 0) {
      extensionType = Convert.unpackI(rtpBuf, computedHeaderLen, DataTypes.IEEE);
      extensionLengthWords = Convert.unpackI(rtpBuf,  computedHeaderLen + NUM_BYTES_EXT_HEADER_ID, DataTypes.IEEE);
      extensionLengthBytes = extensionLengthWords * BYTES_PER_WORD;
    }
    if (computedHeaderLen <=0) {
      invalidMessage = "ComputedHeaderLength="+computedHeaderLen+" <=0 ???. Using "+headerLength;
      validHeader=false;
    }
    else {
      headerLength = computedHeaderLen;
    }
  }

  /** Get the Array holding all the contributing sources
   *  @return the csrcs */
  public String[] getCsrcs () {
    return csrcs;
  }

  /** Get the header length
   *  @return the header length */
  public int getRtpHeaderLen () {
    return headerLength;
  }

  /** Get the invalid message
   *  @return the invalid message */
  public String getInvalidMessage() {
    return invalidMessage;
  }

  /** Utility method to print out the header */
  @InternalUseOnly
  public void dumpHeader() {
    System.out.println(toString());
  }

  @Override
  public String toString() {
    String strHeader =
        "RTP -> Version="+version+"\n"+
        "       pad="+padding+", ext flag ="+extFlag+", xtype="+extensionType+" xlenbytes="+extensionLengthBytes+"\n"+
        "       marker="+marker+", payload="+payloadType+"\n"+
        "       seq="+sequenceNumber+"\n"+
        "       timeStamp="+timeStamp+"\n"+
        "       SSRC="+ssrc+" ("+Integer.toHexString(ssrc)+")\n"+
        "       CC="+csrcCount+"\n"+
        crscsToString();
    return strHeader;
  }

  /** Utility method to return a String representation of the crscs
   *  @return String representation of the crscs
   */
  public String crscsToString() {
    StringBuffer sb = new StringBuffer();
    for (int ci=0;ci<csrcCount;ci++) {
      sb.append("DatagramData: CCRC Num="+(ci+1)+"\n");
      sb.append("DatagramData: CSRC = "+csrcs[ci]+"\n");
    }
    return sb.toString();
  }

  /** Compute Number of Packets Between the Current Sequence Number and an
   *  older sequence number
   *  This
   *  @param curSeq current sequence number
   *  @param oldSeq older sequence number (-1 if current is first))
   *  @return number of packets that should be in-between these two packets
   */
  static int computeNumPktsBetween (int curSeq, int oldSeq) {
    int numPktsBetween = Math.abs(computeNumPktsAhead(curSeq, oldSeq)) - 1;
    return numPktsBetween;
  }

  /** Computes how many packets ahead (or behind the current packet is with
   *  respect to the old sequence number
   *  This assumes that a packet number that is almost the max sequence number
   *  ahead is actually an older packet
   *  @param curSeq current sequence number
   *  @param oldSeq older sequence number (-1 if current is first))
   *  @return number of packets ahead
   *          (can be negative if current sequence number is older than
   *          older sequence number)
   */
  static int computeNumPktsAhead (int curSeq, int oldSeq) {
    if (oldSeq < 0) return 1; // Current is first packet received

    int pktsAhead;
    if (Math.abs(curSeq - oldSeq) < MAX_AHEAD_SEQUENCE_NUM) {
      // Assume has NOT wrapped
      pktsAhead = curSeq - oldSeq;
    }
    else
      if (curSeq < oldSeq) { // assume has wrapped and current is actually larger
        int adjustedCurrent = curSeq + MAX_SEQUENCE_VAL + 1; // get effective value
        pktsAhead = adjustedCurrent - oldSeq;
      }
      else {
        int adjustedOld = oldSeq + MAX_SEQUENCE_VAL + 1; // get effective value
        pktsAhead = curSeq - adjustedOld;
      }

    return pktsAhead;
  }

  /**
   * This will increment the value and wrap it.
   * (Note: does not increment the object itself as it is a primitive.
   * @param seq
   * @return The incremented value.
   */
  static int incrementAndWrapSeqNum (int seq) {
    if (seq < 0)  return seq; // -1 means no packets received yet, so do not know what first packet number will be
    if (seq == MAX_SEQUENCE_VAL) return 0; // wrap around to 0
    return seq + 1; // no wrapping needed
  }

  /**
   * This will increment the value and wrap it.
   * (Note: does not increment the object itself as it is a primitive.
   * @param seq
   * @return The incremented value.
   */
  static int addToSeqNumberWithWrap (int seq, int increment) {
    if (seq < 0)  return seq; // -1 means no packets received yet
    int newSeqNum = seq + increment;
    if (newSeqNum > MAX_SEQUENCE_VAL) {
      newSeqNum = newSeqNum % (MAX_SEQUENCE_VAL + 1);
    }
    else if (newSeqNum < 0) {
      newSeqNum = newSeqNum + (MAX_SEQUENCE_VAL + 1);
    }
    return newSeqNum;
  }

  /**
   * This will increment the value and wrap it.
   * (Note: does not increment the object itself as it is a primitive.
   * @param seq
   * @return The incremented value.
   */
  static int decrementAndWrapSeqNum (int seq) {
    if (seq < 0)  return seq; // -1 means no packets received yet
    if (seq == 0) return MAX_SEQUENCE_VAL; // wrap back around to largest sequence number
    return seq - 1; // no wrapping needed
  }

  /** get CC (CSRC count)
   *  @return CC (CSRC count)
   */
  int getCSRCCount() {
    return csrcCount;
  }

  /** get X (Extension Flag)
   *  @return  X (Extension Flag)
   */
  int getExtensionFlag() {
    return extFlag;
  }

  /** get P (Padding)
   *  @return P (Padding)
   */
  int getPadding() {
    return padding;
  }

  /** get Version
   *  @return Version
   */
  int getVersion() {
    return version;
  }

  /** get PT (Payload Type)
   *  @return PT (Payload Type)
   */
  int getPayloadType() {
    return payloadType;
  }

  /** get M (Marker)
   *  @return M (Marker)
   */
  int getMarker() {
    return marker;
  }

  /** Sequence Number         */
  /** get Sequence Number
   *  @return Sequence Number
   */
  int getSequenceNumber() {
    return sequenceNumber;
  }

  /** get Extension Type
   *  @return Extension Type
   */
  int getExtensionType() {
    return extensionType;
  }

  /** get Extension Length (Bytes)
   *  @return Extension Length (Bytes)
   */
  int getExtensionLengthBytes() {
    return extensionLengthBytes;
  }

  /** get Header Length
   *  @return Header Length
   */
  int getHeaderLength() {
    return headerLength;
  }

  /** get Timestamp
   *  @return Timestamp
   */
  int getTimestamp() {
    return timeStamp;
  }

  /** get SSRC (Synchronization source identifier
   *  @return SSRC
   */
  int getSSRC() {
    return ssrc;
  }

  /** Get the Extension Length
   *  @return the Extension Length in Words */
  public int getExtensionLengthWords () {
    return extensionLengthWords;
  }

}
