package auto;

import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;

public class Receiver {
	//Singleton
	private static Receiver instance = null;

	//Monitoring classes
	private ReceiverSNRMonitor receiveMonitor = null;
	private ReceiverStreamingMonitor streamingMonitor = null; 
	
	
	//Communication variables
	DatagramSocket socket = null;
	final private int receivePort = 6787;
	final private int reportPort = 6788;

	//External parameters 
	//String procFile = null;
	private String senderHost = null;
	private float alphaRateOfErrorPercentage = 0; //e.g. 5%
	private float betaRateOfErrorPercentage = 0; //e.g. 30%

	//Internal adaptive thresholds
	private float alphaThreshold = 0;
	private float betaThreshold = 0;
	private float lossRate = 0; //Rate of packet lost
	private float SNR = 0;
	private int status = 0;

	//Variables for error and transmission control (packet loss) 
	private int totalBytesReceived = 0;
	private int lastPacketLabel = 0;
	private int currentPacketLabel = 0;
	private int numberOfPacketsLost = 0;
//	private long mapOfLosses = 0xFFFFFFFF;

	//Singleton
	public static Receiver getInstance() {
		if (instance == null)
			instance = new Receiver();
		return instance;
	}

	//Count the number of lost packets and
	//calculate the loss rate
	private float getLossRatePercentage() {
		if (currentPacketLabel != lastPacketLabel)
			return (numberOfPacketsLost/(currentPacketLabel - lastPacketLabel)) * 100;
		else return 0;
	}

	public void run() {
		//Send Status feedback to Sender
		//To wake up the server... 
		sendAdaptiveRequest(0);

		//Initialize SNR value and trhesholds
		SNR = receiveMonitor.getSNRValue();
		alphaThreshold = (float) (((SNR-1)*0.9)+1);
		betaThreshold = (float) (((SNR-1)*0.5)+1);
		
		long currentCheck, lastCheck = System.currentTimeMillis();

		try {
			//Main loop
			while (true) {
				currentCheck = System.currentTimeMillis();
				if (currentCheck > (lastCheck+1000) ){
					lossRate = getLossRatePercentage();
					SNR = receiveMonitor.getSNRValue();
					
					//Updates trheshoulds Alpha and Beta
					updateAlphaBeta();
					
					if (SNR <= 1)
						status = 3;
					else if (SNR <= betaThreshold)
						status = 2;
					else if (SNR <= alphaThreshold)
						status = 1;
					else status = 0;

					sendAdaptiveRequest(status);
									
					lastCheck = currentCheck;

					float mbps = refresh();
					
					print(currentCheck, mbps, lossRate, SNR, alphaThreshold, betaThreshold, status);
					//Mensuring delay
				}
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	
	synchronized private float refresh() {
		float mbps = (float) (8 * totalBytesReceived)/(1000000);
	
		//Clean up variables
		totalBytesReceived = 0;
		numberOfPacketsLost = 0;
		lastPacketLabel = currentPacketLabel;

		return mbps;
	}
	
	private void print(long timeStamp, float mbps, float lossRate, float signalNoiseRatio, float alpha, float beta, int status) {
		System.out.println(timeStamp
				+ " "
				+ mbps
				+ " "
				+ lossRate
				+ " "
				+ signalNoiseRatio
				+ " "
				+ alpha
				+ " "
				+ beta				
				+ " "
				+ status);
	}

	private void updateAlphaBeta() {
		//System.out.println("ANTES: " + SNR + " " + alphaThreshold + " " + betaThreshold);
		//Abaixa os coeficientes
		if ((lossRate <= alphaRateOfErrorPercentage) && (alphaThreshold > SNR)) {
			alphaThreshold = (alphaThreshold + SNR) / 2;
		}
		else if ((lossRate <= betaRateOfErrorPercentage) && (betaThreshold > SNR)) { 
			betaThreshold = (betaThreshold + SNR) / 2;
		}
		//Sobe os coeficientes
		else if ((lossRate > alphaRateOfErrorPercentage) && (alphaThreshold < SNR)) {
			alphaThreshold = (alphaThreshold + SNR) / 2;
		}
		else if ((lossRate > betaRateOfErrorPercentage) && (betaThreshold < SNR)) {
			alphaThreshold = (alphaThreshold + SNR) / 2;
		}
		//System.out.println("DEPOIS: " + lossRate + " " + SNR + " " + alphaThreshold + " " + betaThreshold);
	}

	private void updateAlphaBeta2() {
		System.out.println("ANTES: " + SNR + " " + alphaThreshold + " " + betaThreshold);
		//Upper region - good transmission conditions - increase alpha
		if ((lossRate <= alphaRateOfErrorPercentage) && (alphaThreshold > SNR)) {
			alphaThreshold = (alphaThreshold + SNR) / 2;
		}
		//Midle region - acceptable conditions - decrease alpha or beta
		else if (
			(lossRate > alphaRateOfErrorPercentage) && (lossRate <= betaRateOfErrorPercentage)) {
			if (alphaThreshold < SNR)
				alphaThreshold = (alphaThreshold + SNR) / 2;
			//"else" should be applicable here if always (alpha > beta) 
			//but in initialization this is not garanteed 
			if (betaThreshold > SNR) 
				betaThreshold = (betaThreshold + SNR) / 2;
		}
		//Lower region - problematic conditions 
		else if ((lossRate > betaRateOfErrorPercentage) && (SNR >= betaThreshold)){ 
			betaThreshold = (betaThreshold + SNR) / 2;
		}
		System.out.println("DEPOIS: " + SNR + " " + alphaThreshold + " " + betaThreshold);
	}

	void sendAdaptiveRequest(int status) {
		String tmp = new String();
		tmp += status;
		byte[] buf = tmp.getBytes();

		DatagramPacket packet;
		try {
			packet =
				new DatagramPacket(
					buf,
					buf.length,
					InetAddress.getByName(senderHost),
					reportPort);
			socket.send(packet);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	
	synchronized public void verifyPacket(DatagramPacket packet) {
		//Parse package label
		java.util.StringTokenizer st =
			new java.util.StringTokenizer(new String(packet.getData()), ";");
		int pLabel = (new Integer(st.nextToken())).intValue();
		long pTimestamp = (new Long(st.nextToken())).longValue();
		int pSize = (new Integer(st.nextToken())).intValue();
		//int pDelay = (new Integer(st.nextToken())).intValue();

		//Verify packet loss comparing last and current label
		//Update Label
		//System.out.println("currentPacketLabel: " + currentPacketLabel);
		if (pLabel == (currentPacketLabel + 1)) {
			currentPacketLabel = pLabel;
			//Update bytes received
			totalBytesReceived += pSize;
		} else if (pLabel > (currentPacketLabel + 1)) {
			//Update the amount of lost packets
			numberOfPacketsLost += pLabel - currentPacketLabel -1;
			currentPacketLabel = pLabel;
			totalBytesReceived += pSize;
		}
		//System.out.println (numberOfPacketsLost);
	}

	public static void main(String argv[]) throws Exception {
		if (argv.length == 0) {
			System.out.println(
				"Usage: java auto.Receiver procfile senderIP alpha% beta%");
			System.exit(0);
		}

		//Process Parameters
		String procFile = argv[0];
		String senderHost = argv[1];
		float alphaPercent = (new Float(argv[2])).floatValue();
		float betaPercent = (new Float(argv[3])).floatValue();

		Receiver receiver = Receiver.getInstance();
		receiver.init(procFile, senderHost, alphaPercent, betaPercent);

		receiver.run();
	}

	private void init(
		String procFile,
		String senderHost,
		float alphaPercent,
		float betaPercent) {
		//this.procFile = procFile;
		this.senderHost = senderHost;

		if (alphaPercent >= betaPercent){
			System.err.println ("Error: alpha must be small than beta.");
			System.exit(1);
		}
		
		this.alphaRateOfErrorPercentage = alphaPercent;
		this.betaRateOfErrorPercentage = betaPercent;
		
		try {
			socket = new DatagramSocket(receivePort);
		} catch (SocketException e) {
			e.printStackTrace();
		}
		
		receiveMonitor = new ReceiverSNRMonitor(procFile);
		receiveMonitor.start();

		streamingMonitor = new ReceiverStreamingMonitor(socket);
		streamingMonitor.start();
	}

	public int getStatus() {
		return status;
	}
}

//System.out.println("TESTE Tamanho: " + packet.getLength());
//linkUDP.total += packet.getLength();

////Ajuste do Alpha para cima
//if ((lossRate < alphaPercentage) && (snr > alphaThreshold)) {
//	alphaThreshold = (alphaThreshold + snr) / 2;
//} else
//	//Ajuste do Alpha para baixo
//	if ((lossRate > betaPercentage) && (snr > alphaThreshold)) {
//		alphaThreshold = (alphaThreshold + snr) / 2;
//	} else if (
//		(lossRate < alphaPercentage) && (snr < alphaThreshold)) {
//		alphaThreshold = (alphaThreshold + snr) / 2;
//	} else if ((lossRate > betaPercentage) && (snr > betaThreshold)) {
//		betaThreshold = (betaThreshold + snr) / 2;
//	}


//private void updateAlphaBetaOld() {
//	//Upper region - good transmission conditions
//	if (lossRate <= alphaRateOfErrorPercentage) {
//		//Reduce alpha
//		if (SNR <= alphaThreshold)
//			alphaThreshold = (alphaThreshold + SNR) / 2;
//		//Increase alpha slowly because snr >> alpha 
//		else
//			alphaThreshold = (4 * alphaThreshold + SNR) / 5;
//	}
//	//Midle region - acceptable conditions 
//	else if (
//		(lossRate > alphaRateOfErrorPercentage) && (lossRate <= betaRateOfErrorPercentage)) {
//		if (SNR > alphaThreshold)
//			alphaThreshold = (alphaThreshold + SNR) / 2;
//		else if (SNR < betaThreshold)
//			betaThreshold = (betaThreshold + SNR) / 2;
//	}
//	//Lower region - problematic conditions 
//	else if (lossRate > betaRateOfErrorPercentage) { //if lossRate < betaPercentage
//		if (SNR >= betaThreshold)
//			betaThreshold = (betaThreshold + SNR) / 2;
//		//Increase beta slowly because snr >> beta 
//		else
//			betaThreshold = (4 * betaThreshold + SNR) / 5;
//	}
//}
//
