/**
 * Java RTP Library (jlibrtp)
 * Copyright (C) 2006 Arne Kepp
 * 
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 */
package jlibrtp;

import java.net.DatagramPacket;

/**
 * RtpPkt is the basic class for creating and parsing RTP packets.
 * 
 * There are two ways of instantiating an RtpPkt. One is for packets that you
 * wish to send, which requires that you provide basic information about the
 * packet and a payload. Upon calling encode() the fields of the structure are
 * written into a bytebuffer, in the form that it would sent across the network,
 * excluding the UDP headers.
 * 
 * The other way is by passing a bytebuffer. The assumption is that this is a
 * packet that has been received from the network, excluding UDP headers, and
 * the bytebuffer will be parsed into the correct fields.
 * 
 * The class keeps track of changes. Therefore, modifications are possible after
 * calling encode(), if necessary, the raw version of the packet will be
 * regenerated on subsequent requests.
 * 
 * @author Arne Kepp
 */
public class RtpPkt {
	/** Whether the packet has been changed since encode() */
	private boolean rawPktCurrent = false;
	/** The version, always 2, 2 bits */
	private int version = 2; // 2 bits
	/** Whether the packet is padded, 1 bit */
	private int padding; // 1 bit
	/** Whether and extension is used, 1 bit */
	private int extension = 0; // 1 bit
	/** Whether the packet is marked, 1 bit */
	private int marker = 0; // 1 bit
	/** What payload type is used, 7 bits */
	private int payloadType; //
	/** The sequence number, taken from RTP Session, 16 bits */
	private int seqNumber; // 16 bits
	/** The RTP timestamp, 32bits */
	private long timeStamp; // 32 bits
	/** The SSRC of the packet sender, 32 bits */
	private long ssrc; // 32 bits
	/** SSRCs of contributing sources, 32xn bits, n<16 */
	private long[] csrcArray = new long[0];//

	/** Contains the actual data (eventually) */
	private byte[] rawPkt = null;
	


	/** The actual data, without any RTP stuff */
	//private byte[] payload = null;
	private DatagramPacket datagramPacket;

	/**
	 * Construct a packet-instance. The ByteBuffer required for UDP transmission
	 * can afterwards be obtained from getRawPkt(). If you need to set
	 * additional parameters, such as the marker bit or contributing sources,
	 * you should do so before calling getRawPkt;
	 * 
	 * @param aTimeStamp
	 *            RTP timestamp for data
	 * @param syncSource
	 *            the SSRC, usually taken from RTPSession
	 * @param seqNum
	 *            Sequency number
	 * @param plt
	 *            Type of payload
	 * @param pl
	 *            Payload, the actual data
	 */
	protected void initPacket(long aTimeStamp, long syncSource, int seqNum, int plt,
			byte[] pl) {
		int test = 0;
		test += setTimeStamp(aTimeStamp);
		test += setSsrc(syncSource);
		test += setSeqNumber(seqNum);
		test += setPayloadType(plt);
		//test += setPayload(pl); //TODO: faire d'une manière propre
		datagramPacket = null;
		if (test != 0) {
			System.out.println("RtpPkt() failed, check with checkPkt()");
		}
		rawPktCurrent = true;
		if (RTPSession.rtpDebugLevel > 5) {
			System.out
					.println("<--> RtpPkt(aTimeStamp, syncSource, seqNum, plt, pl)");
		}
	}

	/**
	 * Construct a packet-instance from an raw packet (believed to be RTP). The
	 * UDP-headers must be removed before invoking this method. Call checkPkt on
	 * the instance to verify that it was successfully parsed.
	 * 
	 * @param aRawPkt
	 *            The data-part of a UDP-packet believed to be RTP
	 * @param packetSize
	 *            the number of valid octets in the packet, should be
	 *            aRawPkt.length
	 */
	public RtpPkt(byte[] aRawPkt, int packetSize, DatagramPacket packet) {
		initPacket(aRawPkt, packetSize, packet);
	}
	
	public RtpPkt() {
	}
	
	public RtpPkt(long aTimeStamp, long syncSource, int seqNum, int plt,
			byte[] pl) {
		initPacket(aTimeStamp, syncSource, seqNum, plt, pl);
	}
	
	protected void initPacket(byte[] aRawPkt, int packetSize, DatagramPacket packet) {
		if (RTPSession.rtpDebugLevel > 5) {
			System.out.println("-> RtpPkt(aRawPkt)");
		}
		// Check size, need to have at least a complete header
		if (aRawPkt == null) {
			System.out.println("RtpPkt(byte[]) Packet null");
		}

		int remOct = packetSize - 12;
		if (remOct >= 0) {
			rawPkt = aRawPkt; // Store it
			// Interrogate the packet
			datagramPacket = packet;
			sliceFirstLine();
			if (version == 2) {
				sliceTimeStamp();
				sliceSSRC();
				if (remOct > 4 && getCsrcCount() > 0) {
					sliceCSRCs();
					remOct -= csrcArray.length * 4; // 4 octets per CSRC
				}
				// TODO Extension
				/*if (remOct > 0) {
					slicePayload(remOct);
				}*/

				// Sanity checks
				checkPkt();

				// Mark the buffer as current
				rawPktCurrent = true;
			} else {
				System.out
						.println("RtpPkt(byte[]) Packet is not version 2, giving up.");
			}
		} else {
			System.out.println("RtpPkt(byte[]) Packet too small to be sliced");
		}
		rawPktCurrent = true;
		if (RTPSession.rtpDebugLevel > 5) {
			System.out.println("<- RtpPkt(aRawPkt)");
		}
	}

	/*********************************************************************************************************
	 * Reading stuff
	 *********************************************************************************************************/
	protected int checkPkt() {
		// TODO, check for version 2 etc
		return 0;
	}

	protected int getHeaderLength() {
		// TODO include extension
		return 12 + 4 * getCsrcCount();
	}

	protected int getPayloadLength() {
		return rawPkt.length - getHeaderLength();
	}

	// public int getPaddingLength() {
	// return lenPadding;
	// }
	protected int getVersion() {
		return version;
	}

	// public boolean isPadded() {
	// if(lenPadding > 0) {
	// return true;
	// }else {
	// return false;
	// }
	// }
	// public int getHeaderExtension() {
	// TODO
	// }
	protected boolean isMarked() {
		return (marker != 0);
	}

	protected int getPayloadType() {
		return payloadType;
	}

	public int getSeqNumber() {
		return seqNumber;
	}

	protected long getTimeStamp() {
		return timeStamp;
	}

	protected long getSsrc() {
		return ssrc;
	}

	protected int getCsrcCount() {
		if (csrcArray != null) {
			return csrcArray.length;
		} else {
			return 0;
		}
	}

	protected long[] getCsrcArray() {
		return csrcArray;
	}

	/**
	 * Encodes the a
	 */
	protected byte[] encode() {
		if (!rawPktCurrent || rawPkt == null) {
			writePkt();
			android.util.Log.d("RtpPkt", "writePkt");
		}
		return rawPkt;
	}

	/* For debugging purposes */
	protected void printPkt() {
		System.out
				.print("V:" + version + " P:" + padding + " EXT:" + extension);
		System.out.println(" CC:" + getCsrcCount() + " M:" + marker + " PT:"
				+ payloadType + " SN: " + seqNumber);
		System.out.println("Timestamp:" + timeStamp
				+ "(long output as int, may be 2s complement)");
		System.out.println("SSRC:" + ssrc
				+ "(long output as int, may be 2s complement)");
		for (int i = 0; i < getCsrcCount(); i++) {
			System.out.println("CSRC:" + csrcArray[i]
					+ "(long output as int, may be 2s complement)");
		}

	}

	/*********************************************************************************************************
	 * Setting stuff
	 *********************************************************************************************************/
	protected void setMarked(boolean mark) {
		rawPktCurrent = false;
		if (mark) {
			marker = 1;
		} else {
			marker = 0;
		}
	}

	// public int setHeaderExtension() {
	// TODO
	// }
	public int setPayloadType(int plType) {
		int temp = (plType & 0x0000007F); // 7 bits, checks in RTPSession as
											// well.
		if (temp == plType) {
			rawPktCurrent = false;
			payloadType = temp;
			return 0;
		} else {
			return -1;
		}
	}

	protected int setSeqNumber(int number) {
		if (number <= 65536 && number >= 0) {
			rawPktCurrent = false;
			seqNumber = number;
			return 0;
		} else {
			System.out.println("RtpPkt.setSeqNumber: invalid number");
			return -1;
		}
	}

	protected int setTimeStamp(long time) {
		rawPktCurrent = false;
		timeStamp = time;
		return 0; // Naive for now
	}

	protected int setSsrc(long source) {
		rawPktCurrent = false;
		ssrc = source;
		return 0; // Naive for now
	}

	protected int setCsrcs(long[] contributors) {
		if (contributors.length <= 16) {
			csrcArray = contributors;
			return 0;
		} else {
			System.out
					.println("RtpPkt.setCsrcs: Cannot have more than 16 CSRCs");
			return -1;
		}
	}

	/*protected int setPayload(byte[] data) {
		// TODO Padding
		if (data.length < (1500 - 12)) {
			rawPktCurrent = false;
			payload = data;
			return 0;
		} else {
			System.out
					.println("RtpPkt.setPayload: Cannot carry more than 1480 bytes for now.");
			return -1;
		}
	}*/

	public byte[] getPayload() {
		return rawPkt;
	}

	/*********************************************************************************************************
	 * Private functions
	 *********************************************************************************************************/
	// Generate a bytebyffer representing the packet, store it.
	private void writePkt() {
		int bytes = getPayloadLength();
		int headerLen = getHeaderLength();
		int csrcLen = getCsrcCount();
		rawPkt = new byte[headerLen + bytes];

		// The first line contains, version and various bits
		writeFirstLine();
		byte[] someBytes = StaticProcs.uIntLongToByteWord(timeStamp);
		for (int i = 0; i < 4; i++) {
			rawPkt[i + 4] = someBytes[i];
		}
		// System.out.println("writePkt timeStamp:" + rawPkt[7]);

		someBytes = StaticProcs.uIntLongToByteWord(ssrc);
		System.arraycopy(someBytes, 0, rawPkt, 8, 4);
		// System.out.println("writePkt ssrc:" + rawPkt[11]);

		for (int i = 0; i < csrcLen; i++) {
			someBytes = StaticProcs.uIntLongToByteWord(csrcArray[i]);
			System.arraycopy(someBytes, 0, rawPkt, 12 + 4 * i, 4);
		}
		// TODO Extension

		// Payload
		//System.arraycopy(payload, 0, rawPkt, headerLen, bytes);
		rawPktCurrent = true;
	}
	
	void writeHeader() {
		//int bytes = rawPkt.length - 12;
		//int headerLen = getHeaderLength();
		int csrcLen = getCsrcCount();

		// The first line contains, version and various bits
		writeFirstLine();
		StaticProcs.uIntLongToByteWord(timeStamp, rawPkt, 4);
		// System.out.println("writePkt timeStamp:" + rawPkt[7]);

		StaticProcs.uIntLongToByteWord(ssrc, rawPkt, 8);
		//System.arraycopy(someBytes, 0, rawPkt, 8, 4);
		// System.out.println("writePkt ssrc:" + rawPkt[11]);

		for (int i = 0; i < csrcLen; i++) {
			StaticProcs.uIntLongToByteWord(csrcArray[i], rawPkt, 12 + 4 * i);
			//System.arraycopy(someBytes, 0, rawPkt, 12 + 4 * i, 4);
		}
		// TODO Extension

		// Payload
		//System.arraycopy(payload, 0, rawPkt, headerLen, bytes);
		rawPktCurrent = true;
	}

	// Writes the first 4 octets of the RTP packet
	protected void writeFirstLine() {
		byte aByte = 0;
		aByte |= (version << 6);
		aByte |= (padding << 5);
		aByte |= (extension << 4);
		aByte |= (getCsrcCount());
		rawPkt[0] = aByte;
		aByte = 0;
		aByte |= (marker << 7);
		aByte |= payloadType;
		rawPkt[1] = aByte;
		StaticProcs.uIntIntToByteWord(seqNumber, rawPkt, 2);
	}

	// Picks apart the first 4 octets of an RTP packet
	private void sliceFirstLine() {
		version = ((rawPkt[0] & 0xC0) >>> 6);
		padding = ((rawPkt[0] & 0x20) >>> 5);
		extension = ((rawPkt[0] & 0x10) >>> 4);
		//csrcArray = new long[(rawPkt[0] & 0x0F)];
		marker = ((rawPkt[1] & 0x80) >> 7);
		payloadType = (rawPkt[1] & 0x7F);
		seqNumber = StaticProcs.bytesToUIntInt(rawPkt, 2);
	}

	// Takes the 4 octets representing the timestamp
	private void sliceTimeStamp() {
		timeStamp = StaticProcs.bytesToUIntLong(rawPkt, 4);
	}

	// Takes the 4 octets representing the SSRC
	private void sliceSSRC() {
		ssrc = StaticProcs.bytesToUIntLong(rawPkt, 8);
	}

	// Check the length of the csrcArray (set during sliceFirstLine)
	private void sliceCSRCs() {
		for (int i = 0; i < csrcArray.length; i++) {
			ssrc = StaticProcs.bytesToUIntLong(rawPkt, i * 4 + 12);
		}
	}

	// Extensions //TODO
	/*private void slicePayload(int bytes) {
		payload = new byte[bytes];
		int headerLen = getHeaderLength();

		System.arraycopy(rawPkt, headerLen, payload, 0, bytes);
	}*/
	
	public void setRawPkt(byte[] rawPkt) {
		this.rawPkt = rawPkt;
	}

	public DatagramPacket getDatagramPacket() {
		return datagramPacket;
	}
}