package org.kne.cloud.network.congestion;

import java.util.function.Consumer;

public class BBRCongestionAlgorithm implements CongestionAlgorithm ,DetnetCongestionAlgorithm{

	private static final long BURST_TIME = 10000000L;
	private static final long BIAS = 500000L;
	private static long MIN_SPEED = 512 * 1024L;
	private static long MIN_WINDOW = 16384;

	private Consumer<Long> windowControlConsumer;

	private long MIN_RTTVAR = 5000000L;
	private volatile long RTTMin = 1000000000L;
	private volatile long RTTVar = 1000000000L;
	private volatile long RTTAvg = 1000000000L;
	private volatile long RTTAvg2 = 1000000000L;
	private volatile long RTTTotal = 0;
	private volatile long RTTCount = 0;
	private volatile long RTO = 1000000000L;


	private volatile long window=MIN_WINDOW;
	private volatile long maxwindow=99999999999L;
	private long congressSpeed=MIN_SPEED;
	private double MIN_GAIN=1.05;
	private double MAX_GAIN=1.20;//2.8853900817779
	private double windowGain=MIN_GAIN;
    private double burstLimit=1.20;
	@Override
	public void reset() {
		window = MIN_WINDOW;
		if (windowControlConsumer != null) {
			windowControlConsumer.accept(window);
		}
	
		windowGain=MAX_GAIN;
	}

	@Override
	public void setWindowControlConsumer(Consumer<Long> windowControlConsumer) {
		this.windowControlConsumer = windowControlConsumer;
	}

	

	private long RTTstartTime = System.nanoTime();
	private long bandwidth;
	@Override
	public synchronized void putAck(long packetSize, long latencyns, boolean ecn) {
		if (latencyns <= RTTMin) {
			RTTMin = latencyns;
		} else {
			RTTMin = (RTTMin * 99999 + latencyns) / 100000;
		}
		RTTVar = (RTTVar * 3 + Math.abs(RTTAvg - latencyns)) / 4;
		RTTAvg = (RTTAvg * 7 + latencyns) / 8;
		
		RTO = RTTAvg + Math.max(MIN_RTTVAR, RTTVar * 4);// RTTVar*4

		RTTTotal += latencyns;
		RTTCount++;
		/*double rate=latencyns/(double)(RTTMin+BIAS);
		if(rate>2) {
			windowGain=MIN_GAIN;
		}else if(rate<1.2){
			windowGain=MAX_GAIN;
			
		}*/
		long curr=System.nanoTime();
		if(curr-RTTstartTime>RTTAvg2) {
			if(RTTCount>0) {
				RTTAvg2=(RTTAvg2+RTTTotal/RTTCount)/2;
				RTTTotal=0;
				RTTCount=0;
			}
			
			RTTstartTime=curr;
			
			if (bandwidth >= congressSpeed) {
				congressSpeed =   bandwidth;
				//congressSpeed = (congressSpeed  + bandwidth) / 2;
			} else {
				congressSpeed = (congressSpeed * 49 + bandwidth) / 50;
			}
			window= ((long) (congressSpeed*windowGain )*(RTTMin+BIAS)/1000000000L)+MIN_WINDOW;
			if (windowControlConsumer != null) {
				windowControlConsumer.accept(window);
			}
		}
		
	}

	@Override
	public void putLoss(long packetSize, long lossns, int losscounter) {
		
	}

	@Override
	public long getRTO() {
		return RTO;
	}

	@Override
	public void setCurrentWindowUsed(long used) {
        this.maxwindow = (long) (used*burstLimit)  + MIN_WINDOW;

	}

	@Override
	public void setCurrentBandwidth(long bandwidth) {
		this.bandwidth=bandwidth;
		
		//System.out.println(gain);
		//System.out.println(bandwidth+" "+congressSpeed+" "+window);
	}

	@Override
	public void setUpperDelayBound(double upper) {
		MAX_GAIN=upper;
	}

	@Override
	public void setLowerDelayBound(double lower) {
		MIN_GAIN=lower;
		windowGain=MIN_GAIN;
	}
	@Override
	public void setBurstLimit(double limit) {
		burstLimit=limit;
	}

	@Override
	public String getName() {
		return "BBR";
	}

}
