/*************************************************
 * Author: 	Evelyn Lai-Tee Cheok
 *			Department of Electrical Engineering
 *			Center for Telecommunications Research
 *			Schapiro Research Building
 *			Columbia University
 *
 * Email: 	laitee@ctr.columbia.edu
 *
 **************************************************/
import java.lang.*;
import java.util.*;
import java.io.*;
import java.net.*;
import StateInfo;
import UserInfo;
import rtcp_sdes_type;


/** RTCP Packet Handler contains assembler to assemble the receiver 
	report with either SDES items or BYE packet and disassembler to do
	the reverse job. It also provides function for computing RTCP 
	transmission interval. **/

public class RTCP_PacketHandler {
	final static double RTCP_MIN_TIME = 5.0;
	final static double RTCP_SENDER_BW_FRACTION = 0.25;
	final static double RTCP_RCVR_BW_FRACTION = (1 - RTCP_SENDER_BW_FRACTION);
	final static double RTCP_SIZE_GAIN = (1.0 / 16.0);
	double interval = (double) 0.0;
	StateInfo state_info;
	int SSRCRead = 0;

	/* length of SDES items */
	int cname_len, email_len, name_len;

	/* for calculating RTCP_packet_size in bytes(octets) */
	int UDP_header_size = 8;
	int IP_header_size = 20;
	int empty_RR_header_size = 8;
	int SDES_header_size = 8;

	/* fixed definition for the RR RTCP and SDES RTCP header */
	byte ver = 2;

	/** Constructor function **/
	public RTCP_PacketHandler(StateInfo state_info) {
		this.state_info = state_info;
	}

	/** Compute RTCP transmission interval **/
	/** @param initial flag to determine whether function is first called.
	  * @param members the number of participating users.
	  * @param rtcp_size average RTCP packet size.
	  * @param activeSenders the number of active senders during the 
	  *						last interval.
	  * @param we_sent flag that's set to true if we have sent data during
	  *						the last interval.
	  * @param rtcp_bw rtcp bandwidth which is 5% of session bandwidth.
	  * @param RTCP_packet_size size of RTCP packet last sent.
	  *
	  * @return transmission interval computed. **/

	public double transm_interval(boolean initial, int members,
				       int rtcp_size, int activeSenders,
				       boolean we_sent, double rtcp_bw,
				       int RTCP_packet_size) {
		Random rand = new Random(System.currentTimeMillis());
		double t;
		double rtcp_min_time = RTCP_MIN_TIME;
		int avg_rtcp_size = 0;
		int n;

		n = members;
		if (state_info.initial == true) {
			rtcp_min_time /= 2;
			avg_rtcp_size = rtcp_size;
			state_info.initial = false;
		}
		if (activeSenders > 0 && activeSenders < 
					(members * RTCP_SENDER_BW_FRACTION)) {
			if (we_sent == true) {
				rtcp_bw *= RTCP_SENDER_BW_FRACTION;
				n = activeSenders;
			} else {
				rtcp_bw *= RTCP_RCVR_BW_FRACTION;
				n -= activeSenders;
			}
		}
		avg_rtcp_size += (RTCP_packet_size - avg_rtcp_size) * RTCP_SIZE_GAIN;
		t = (avg_rtcp_size * n) / rtcp_bw;
		state_info.actual_interval = t;
		if (t < rtcp_min_time)
			t = rtcp_min_time;
		interval = (double) ((rand.nextFloat() + 0.5) * t);
		storeStates();
		return interval;
	}

	/** stores states into StateInfo structure(class). Called by 
	  * transm_interval after computing interval **/
	public void storeStates() {
		state_info.tran_interval = interval;
		state_info.we_sent = false;
		state_info.activeSenders = 0;
		state_info.activeSenderVec.removeAllElements();
	}

	/** gets transmissional interval computed **/
	double get_interval() {
		return interval;
	}

	/** Function for assembling receiver report with either 
	  * BYE or SDES packet **/
	public byte[] assemble(int SSRC, UserInfo user_info,
			        boolean exitFlag) {
		byte first_byte;
		byte RC = 0;
		byte PT = (byte) 201;
		int length = 2 - 1;;
		byte p = 0;

		ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
		DataOutputStream dataStream = new DataOutputStream(byteStream);
		 first_byte = (byte) (((ver << 6) & 192) | ((p << 5) & 32) | (RC & 31));

		 try {
			dataStream.writeByte(first_byte);
			dataStream.writeByte(PT);
			dataStream.writeShort(length);
			dataStream.writeInt(SSRC);
			if (exitFlag == true)
				sendByePacket(dataStream, SSRC);
			else
				sendSDESItems(dataStream, SSRC, user_info);
		} catch(java.io.IOException e) {
			System.out.println("IOException in sendSR: " + e);
		}
		return byteStream.toByteArray();
	}

	/** Assembles BYE packet. Called by assemble function **/
	public void sendByePacket(DataOutputStream dataStream,
				   int SSRC) {
		byte first_byte;
		byte SC = 1;
		byte PT = (byte) 203;
		int length = 1;
		byte p = 0;

		 first_byte = (byte) (((ver << 6) & 192) | ((p << 5) & 32) | (SC & 31));
		 try {
			dataStream.writeByte(first_byte);
			dataStream.writeByte(PT);
			dataStream.writeShort(length);
			dataStream.writeInt(SSRC);
		} catch(java.io.IOException e) {
			System.out.println("IOException in sendSR: " + e);
		}
	}

	/** Assembles SDES items. Aligns packet to 32-bit boundary when
	  * necessary before setting padding bit to 1. Called by assemble 
	  * function. **/
	public void sendSDESItems(DataOutputStream dataStream,
				   int SSRC, UserInfo user_info) {
		byte first_byte;
		byte PT = (byte) 202;
		byte SC = 1;
		int len;
		int length;
		int bytesToPad = 1;
		byte p = 0;
		 cname_len = (user_info.cname).length();
		 name_len = (user_info.name).length();
		 email_len = (user_info.email).length();

		 len = (cname_len + name_len + email_len + 3 * 2 + 8) + 1;

		 /* Does padding to 32-bit boundary */
		 length = len / 4;
		if ((len % 4) > 0) {
			length++;
			bytesToPad += (4 - (len % 4));
			p = 1;
		} else {
			/*length++;
			bytesToPad = 4;
			*/
			p = 1;
		}

		first_byte = (byte) (((ver << 6) & 192) | ((p << 5) & 32) | (SC & 31));
		try {
			dataStream.writeByte(first_byte);
			dataStream.writeByte(PT);
			dataStream.writeShort(length - 1);
			dataStream.writeInt(SSRC);

			/* sends user CNAME */
			dataStream.writeByte(rtcp_sdes_type.CNAME);
			dataStream.writeByte(cname_len);
			dataStream.writeBytes(user_info.cname);

			/* sends user NAME */
			dataStream.writeByte(rtcp_sdes_type.NAME);
			dataStream.writeByte(name_len);
			dataStream.writeBytes(user_info.name);

			/* sends uer EMAIL address */
			dataStream.writeByte(rtcp_sdes_type.EMAIL);
			dataStream.writeByte(email_len);
			dataStream.writeBytes(user_info.email);

			/* padding 0 bytes to maintain 32-bit boundary */
			if (p > 0) {
				for (int i = 0; i < bytesToPad; i++)
					dataStream.writeByte((byte) 0);
			}

		} catch(java.io.IOException e) {
			System.out.println("IOException in adding SDES Items: " + e);
		}
	}

	/** Gets the size of RTCP packet assembled **/
	public int getRTCPSize() {
		int SDES_Item_size = cname_len + name_len + email_len + 3 * 2;
		 return (SDES_Item_size + SDES_header_size +
		   empty_RR_header_size + UDP_header_size + IP_header_size);
	}

	/** Segmentation function. **/
	/** @param pack datagram packet that has been received **/
	/** @return SDES information retrieved from the packet **/
	public UserInfo disassemble(DatagramPacket pack) {
		int RTCP_pkt_type;
		Random rand = new Random(System.currentTimeMillis());
		int sdes_type;
		int i = 0;
		byte cname_byte[] = new byte[1024];
		byte name_byte[] = new byte[1024];
		byte email_byte[] = new byte[1024];
		ByteArrayInputStream byteStream =
		new ByteArrayInputStream(pack.getData());
		DataInputStream dataStream = new DataInputStream(byteStream);

		 UserInfo other_users_info = new UserInfo();
		 try {
			/* reads RR header */
			dataStream.readByte();
			dataStream.readByte();
			dataStream.readUnsignedShort();
			SSRCRead = dataStream.readInt();
			other_users_info.SSRC = SSRCRead;

			/* reads SDES header */
			dataStream.readByte();
			RTCP_pkt_type = dataStream.readByte() & 255;
			dataStream.readUnsignedShort();
			dataStream.readInt();
			if (RTCP_pkt_type == 203) {
				state_info.members_leaving = true;
				state_info.members--;
			} else if (RTCP_pkt_type == 202) {
				/* reads individual SDES items */
				for (i = 0; i < 3; i++) {
					sdes_type = dataStream.readByte();
					switch (sdes_type) {
					case rtcp_sdes_type.CNAME:
						int cname_len_recvd = (int) dataStream.readByte();
						dataStream.read(cname_byte, 0, cname_len_recvd);
						other_users_info.cname = new String(cname_byte, 0);
						/* Use below if using JDK 1.1b2 to compile */
						//other_users_info.cname = new String(cname_byte);
						break;
					case rtcp_sdes_type.NAME:
						int name_len_recvd = (int) dataStream.readByte();
						dataStream.read(name_byte, 0, name_len_recvd);
						other_users_info.name = new String(name_byte, 0);
						/* Use below if using JDK 1.1b2 to compile */
						//other_users_info.name = new String(name_byte);
						break;
					case rtcp_sdes_type.EMAIL:
						int email_len_recvd = (int) dataStream.readByte();
						dataStream.read(email_byte, 0, email_len_recvd);
						other_users_info.email = new String(email_byte, 0);
						/* Use below if using JDK 1.1b2 to compile */
						//other_users_info.email = new String(email_byte);
						break;
					default:
						System.out.println("No matching rtcp_sdes_type !!");
						break;
					}
				}
			}
		} catch(java.io.IOException e) {
			System.out.println("IOException in disassembling RTCP pkts: " + e);
		}

		return other_users_info;
	}

	/** Gets SSRC number received from other participating users */
	public int get_SSRCRead() {
		return SSRCRead;
	}

}
