package org.kne.cloud.network.congestion;

import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;
import java.util.function.Consumer;

public class DCTCPCongestionAlgorithm implements CongestionAlgorithm {

	private static final long BURST_TIME = 1000000L;
	private static final long BIAS = 2000000L;
	private static long MIN_SPEED = 64 * 1024L;
	private static long MIN_WINDOW = 8192;

	private Consumer<Long> windowControlConsumer;

	private AtomicBoolean firstUpdate = new AtomicBoolean(true);
	private long MIN_RTTVAR = 100000000L;
	private volatile long RTTMin = 1000000000L;
	private volatile long RTTVar = 1000000000L;
	private volatile long RTTAvg = 1000000000L;
	private double ECNAvg2 = 0L;
	private double alpha=0;
	private volatile AtomicLong ECNSize = new AtomicLong(0);
	private volatile AtomicLong TotalSize = new AtomicLong(0);
	private volatile long RTO = 1000000000L;


	private volatile long window=MIN_WINDOW;
	private volatile long windowUsed=MIN_WINDOW;
	private volatile long congressWindowSize=MIN_WINDOW;

	private long RTTstartTime = System.nanoTime();
	private double MAX_GAIN=2.8853900817779;//2.8853900817779
	private double MIN_GAIN=1.2;
	
	@Override
	public void reset() {
		window = MIN_WINDOW;
		if (windowControlConsumer != null) {
			windowControlConsumer.accept(window);
		}
	}

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


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

		if(ecn) {
		ECNSize.addAndGet(packetSize);
		}
		TotalSize.addAndGet(packetSize);
		
		
		long curr=System.nanoTime();
		if(curr-RTTstartTime>Math.max(BIAS, 2*RTTMin)) {
			RTTstartTime=curr;
			
			long totalSize=TotalSize.getAndSet(0);
			long ecnSize=ECNSize.getAndSet(0);
			if(totalSize!=0) {
			ECNAvg2=ecnSize/(double)totalSize;
			alpha=(alpha*15+ECNAvg2)/16;
			//System.out.println(ECNAvg2);
			}
			
			if(ECNAvg2>0.5) {
				if(congressWindowSize>MIN_WINDOW) {
					congressWindowSize=Math.max(MIN_WINDOW,(long) (congressWindowSize*(1-alpha/10)));
					updateWindowSize();
				}
			}else {
				if(windowUsed*3L>=congressWindowSize) {
					congressWindowSize+=4000;
					updateWindowSize();
				}
			}
			//System.out.println(ECNAvg2);
			//System.out.println(speed+" "+window);
			
			
		}
		
		
	}

	private void updateWindowSize() {
		window=Math.max( congressWindowSize,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.windowUsed=used;
	}

	@Override
	public void setCurrentBandwidth(long bandwidth) {

	}

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

	

}
