package org.kne.cloud.network.klalb;

import java.io.IOException;
import java.io.StreamCorruptedException;
import java.net.BindException;
import java.net.NoRouteToHostException;
import java.net.SocketException;
import java.net.SocketTimeoutException;
import java.nio.channels.UnresolvedAddressException;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Queue;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.LockSupport;
import java.util.function.Consumer;

import org.jctools.queues.MpscArrayQueue;
import org.kne.cloud.clock.HighAccuracyClock;
import org.kne.cloud.clock.NTPTimestamps;
import org.kne.cloud.clock.ReliabilityBackoffTimeClock;
import org.kne.cloud.clock.WatchDogTimer;
import org.kne.cloud.network.MultiProtocolSocketAddress;
import org.kne.cloud.network.ThreadTool;
import org.kne.cloud.network.congestion.BBRCongestionAlgorithm;
import org.kne.cloud.network.congestion.CongestionAlgorithm;
import org.kne.cloud.network.congestion.CongestionAlgorithms;
import org.kne.cloud.network.congestion.DetnetCongestionAlgorithm;
import org.kne.cloud.network.congestion.SendPacketSlidingWindow;
import org.kne.cloud.network.ipv6.ControlledIPv6NetworkLink;
import org.kne.cloud.network.ipv6.IPv6Address;
import org.kne.cloud.network.ipv6.IPv6HopByHopTLV;
import org.kne.cloud.network.ipv6.IPv6Packet;
import org.kne.cloud.network.ipv6.IPv6Packet.IPv6HopByHopHeader;
import org.kne.cloud.network.ipv6.IPv6AddressGroup;
import org.kne.cloud.network.ipv6.KLALBOAMHopByHopTLV;
import org.kne.cloud.network.ipv6.KLALBPassportHopByHopTLV;
import org.kne.cloud.network.ipv6.Neighbor;
import org.kne.cloud.network.ipv6.PostcardEntry;
import org.kne.cloud.network.ipv6.RouteItem;
import org.kne.cloud.network.monitor.CostSupplierFactory;
import org.kne.cloud.network.monitor.DelayMonitorData;
import org.kne.cloud.network.monitor.LinkStatus;
import org.kne.cloud.network.monitor.SpeedAndTrafficAndDelayMonitorDataImpl;
import org.kne.cloud.network.perf.TESTNPacket;
import org.kne.cloud.network.te.BandwidthDistributer;
import org.kne.concurrent.SpinLock;
import org.kne.concurrent.ThreadParker;
import org.kne.math.Long128;

public class KLALBRemoteLink extends AbstractControlledIPv6NetworkLink implements ControlledIPv6NetworkLink {

	private static final int HEADER_CALIBRATE = 40;

	private static final boolean debug = false;

	private static final boolean showpacket = false;

	private volatile boolean closed = false;

	private volatile KLALBController klalbController;

	public KLALBController getKlalbController() {
		return klalbController;
	}


	private volatile IPv6AddressGroup remoteVaddr;

	public IPv6AddressGroup getRemoteVaddr() {
		return remoteVaddr;
	}

	private HighAccuracyClock sysclk ;

	public void waitForRemoteVaddrAvaliable(long timeout) throws SocketTimeoutException {
		long s = System.nanoTime();
		while (remoteVaddr == null) {

			try {
				Thread.sleep(1);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			if (System.nanoTime() - s > timeout * 1000000)
				throw new SocketTimeoutException();
		}
	}

	public SpeedAndTrafficAndDelayMonitorDataImpl getMonitor() {
		return monitor;
	}

	public String toString() {
		return remoteVaddr + "\t" +name+"\n" +status+"\t"+monitor.toString();
	}

	private String name;
	private LinkStatus status=new LinkStatus();
	private SpeedAndTrafficAndDelayMonitorDataImpl monitor;
	private volatile KLALBPacketLink kplink;
	private MultiProtocolSocketAddress bindAddress;
	private MultiProtocolSocketAddress socketAddress;


	public void setKplink(KLALBPacketLink kplink) {
		this.kplink = kplink;
	}

	public KLALBPacketLink getKplink() {
		return kplink;
	}

	public MultiProtocolSocketAddress getSocketAddress() {
		return socketAddress;
	}

	public MultiProtocolSocketAddress getBindAddress() {
		return bindAddress;
	}

	private IPv6AddressGroup addressGroup;
	private IPv6AddressGroup peerAddress;

	@Override
	public List<IPv6AddressGroup> getAddressGroups() {
		List<IPv6AddressGroup> list = new ArrayList<>();
		list.add(addressGroup);
		return list;
	}

	public IPv6AddressGroup getPeerAddress() {
		return peerAddress;
	}
 
	public KLALBRemoteLink(KLALBController controller, MultiProtocolSocketAddress mpa, IPv6AddressGroup address) {
		this(controller,mpa, null,address);
	}

	public KLALBRemoteLink(KLALBController controller,KLALBPacketLink kpl, IPv6AddressGroup address) {
		this.klalbController=controller;
		sysclk = klalbController.getClock();
		this.kplink = kpl;
		this.addressGroup = address;
		monitor=new SpeedAndTrafficAndDelayMonitorDataImpl(sysclk);
		name=kpl.toString();
	}

	private KLALBRemoteLink(KLALBController controller, MultiProtocolSocketAddress mpa, MultiProtocolSocketAddress bindaddr, IPv6AddressGroup address) {
		this.klalbController=controller;
		sysclk = klalbController.getClock();
		this.socketAddress = mpa;
		this.bindAddress = bindaddr;
		this.addressGroup = address;
		monitor=new SpeedAndTrafficAndDelayMonitorDataImpl(sysclk);
		if (bindaddr == null) {
			name="→" + mpa.toString();
		} else {
			name=bindaddr.toString() + "→" + mpa.toString();
		}
	}


	public KLALBRemoteLink(KLALBController controller, MultiProtocolSocketAddress mpa, MultiProtocolSocketAddress bind) {
		this(controller,mpa, bind, null);
	}

	public KLALBRemoteLink(KLALBController controller, MultiProtocolSocketAddress mpa) {
		this(controller,mpa, (IPv6AddressGroup) null);
	}

	public KLALBRemoteLink(KLALBController controller,KLALBPacketLink link) {
		this(controller,link, (IPv6AddressGroup) null);
	}



	private CongestionAlgorithm algorithm;

	private SendPacketSlidingWindow<UUID, IPv6Packet> sendWindow;
	
	
	
	private class RemoteSender implements Runnable {
		
		private MpscArrayQueue<KLALBPacket> sendQueue = new MpscArrayQueue<KLALBPacket>(8192);
		private WatchDogTimer watchdog = new WatchDogTimer(400000000L);
		
		private ThreadParker tlock = new ThreadParker();
		
		private volatile long time = System.nanoTime();
		private volatile long pingInterval = 100000000L;

		private Lock sendLock=new SpinLock();
		
		private boolean checkPingTime() {
			long cu = System.nanoTime();
			if (cu - time > pingInterval) {
				time = cu;
				return true;
			} else {
				return false;
			}

		}
		
		
		public Queue<KLALBPacket> getSendQueue() {
			return sendQueue;
		}


		public WatchDogTimer getWatchdog() {
			return watchdog;
		}

		public long getPingInterval() {
			return pingInterval;
		}
		public void setPingInterval(long pingInterval) {
			this.pingInterval = pingInterval;
		}

		private volatile long flushInterval=1000000L;
		
		public long getFlushInterval() {
			return flushInterval;
		}
		public void setFlushInterval(long flushInterval) {
			this.flushInterval = flushInterval;
		}
		private long flushTime=System.nanoTime();
		@Override
		public void run() {
			try {
				writeKLALBPacketToKPL(new ADDRREQPacket());
				writeKLALBPacketToKPL(new ADDRREQPacket());
				if (addressGroup != null) {
					writeKLALBPacketToKPL(new ADDRPacket(addressGroup));
					writeKLALBPacketToKPL(new ADDRPacket(addressGroup));
				}
				writeKLALBPacketToKPL(new VADDRREQPacket());
				writeKLALBPacketToKPL(new VADDRREQPacket());
				writeKLALBPacketToKPL(new VADDRPacket(klalbController.getSelf()));
				writeKLALBPacketToKPL(new VADDRPacket(klalbController.getSelf()));
				kplink.flush();
				while ((!kplink.isClosed()) && (!closed)) {
					if (checkPingTime()) {
						ping();
						if (remoteVaddr == null)
							sendPacket(new VADDRREQPacket());
						if (peerAddress == null)
							sendPacket(new ADDRREQPacket());

					}/*sendLock.lock();
					int n;
					try {
					n = sendQueue.drain((tkpp)->{
						
						try {
							kplink.writeKLALBPacket(tkpp);
						} catch (IOException e) {
							throw new RuntimeException(e);
						}
					},100);
					}finally {
						sendLock.unlock();
					}
					if (n>0) {
						watchdog.feed();
					} else {
						tlock.parkNanos(1000000L);
					}*/
					long curr=System.nanoTime();
					if(curr-flushTime>=flushInterval) {
						flushTime=curr;
						sendLock.lock();
						try {
						kplink.flush();
						watchdog.feed();
						}finally {
							sendLock.unlock();
						}
					}
					tlock.parkNanos(10000000L);
					
				}
			} catch (StreamCorruptedException sce) {
				sce.printStackTrace();
			} catch (IOException | UnresolvedAddressException e) {
				if (debug)
					e.printStackTrace();
			} finally {
				try {
					kplink.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
				tpk.unpark();
			}
		}


		public boolean send(KLALBPacket blk) {
			boolean b=sendQueue.add(blk);
			if(b) {
			tlock.unpark();
			}
			return b;
		}
		
		
		public void sendDirect(KLALBPacket blk) {
			sendLock.lock();
			try {
			writeKLALBPacketToKPL(blk);
			} catch (IOException | UnresolvedAddressException e) {
				if (debug)
					e.printStackTrace();
				try {
					kplink.close();
				} catch (IOException ex) {
					ex.printStackTrace();
				}
				tpk.unpark();
			}finally {
				sendLock.unlock();
			}
			watchdog.feed();
			tlock.unpark();
		}


		public long getQueueUsed() {
			long used=0;
			for(KLALBPacket pack:sendQueue) {
				used+=pack.getTotalLength();
			}
			return used;
		}

	}

	private class RemoteReceiver implements Runnable {

	    private WatchDogTimer watchdog = new WatchDogTimer(400000000L);
		private long rtt=Long.MAX_VALUE/1024;
		private long rttMin=Long.MAX_VALUE/1024;
		AtomicBoolean fst = new AtomicBoolean(true);
		//ReentrantLock readLock = new ReentrantLock();
		
		@Override
		public void run() {
			try {
				while ((!kplink.isClosed()) && (!closed)) {
					KLALBPacket kppb = readKLALBPacketFromKPL();
					
					if (kppb == null) {
						break;
					}
					watchdog.feed();
					KLALBPacket kpp = kppb;
					//SRv6Router.getDefaultExecutor().execute(() -> {
						debugShowPacket("RX", kpp);
						UUID val = null;
						long delay = Long.MAX_VALUE / 1024;
						switch (kpp.getType()) {
						case KLALBPacket.PING:
							long pct = System.nanoTime() - kpp.getRcvtime();
							Long128 curr = sysclk.getCurrentTimeNanos();
							Long128 rcv = curr.subtract(pct);

							Long128 local1281 = NTPTimestamps.nanosToNtp128BitTimestamp(curr);
							PONGPacket pong = new PONGPacket(((PINGPacket) kpp).getTime(),
									NTPTimestamps.ntp128To64(NTPTimestamps.nanosToNtp128BitTimestamp(rcv)).longValue(),
									NTPTimestamps.ntp128To64(NTPTimestamps.nanosToNtp128BitTimestamp(curr)).longValue(),
									monitor.getUploadBandwidth().calculateBandwidth(500000000L),
									monitor.getDownloadBandwidth().calculateBandwidth(500000000L));
							sendPacket(pong);
							/*
							 * long prcv1=pong.getTimepingrcv();
							 * System.out.println("prcv"+NTPTimestamps.nanosSince1970ToString(
							 * NTPTimestamps.ntp128BitToNanosTimestamp( NTPTimestamps.inferNtp64To128(
							 * prcv1,local1281)))); long posnd1=pong.getTimepongsnd();
							 * System.out.println("posnd"+NTPTimestamps.nanosSince1970ToString(
							 * NTPTimestamps.ntp128BitToNanosTimestamp( NTPTimestamps.inferNtp64To128(
							 * posnd1,local1281))));
							 */
							break;
						case KLALBPacket.PONG:
							PONGPacket png = (PONGPacket) kpp;
							/*
							 * long ul = png.getRcvtime() - png.getTimepingsnd() - (png.getTimepongsnd() -
							 * png.getTimepingrcv());
							 */
							Long128 local = sysclk.getCurrentTimeNanos();
							Long128 local128 = NTPTimestamps.nanosToNtp128BitTimestamp(local);

							Long128 psnd = NTPTimestamps.ntp128BitToNanosTimestamp(
									NTPTimestamps.inferNtp64To128(png.getTimepingsnd(), local128));
							// System.out.println("psnd"+NTPTimestamps.nanosSince1970ToString(
							// NTPTimestamps.ntp128BitToNanosTimestamp( NTPTimestamps.inferNtp64To128(
							// psnd,local128))));
							Long128 prcv = NTPTimestamps.ntp128BitToNanosTimestamp(
									NTPTimestamps.inferNtp64To128(png.getTimepingrcv(), local128));
							// System.out.println("prcv"+NTPTimestamps.nanosSince1970ToString(
							// NTPTimestamps.ntp128BitToNanosTimestamp( NTPTimestamps.inferNtp64To128(
							// prcv,local128))));
							Long128 posnd = NTPTimestamps.ntp128BitToNanosTimestamp(
									NTPTimestamps.inferNtp64To128(png.getTimepongsnd(), local128));
							// System.out.println("posnd"+NTPTimestamps.nanosSince1970ToString(
							// NTPTimestamps.ntp128BitToNanosTimestamp( NTPTimestamps.inferNtp64To128(
							// posnd,local128))));

							long DsndDelay = prcv.subtract(psnd).longValue();
							long DrcvDelay = local.subtract(posnd).longValue();
							// System.out.println(DsndDelay+" "+DrcvDelay);

							rtt = DsndDelay + DrcvDelay;
							if(rtt<rttMin) {
								rttMin=rtt;
							}else {
								rttMin=(rttMin*99+rtt)/100;
							}
							if (DsndDelay >= 0 && DrcvDelay >= 0) {
								delay = DrcvDelay;
								monitor.recordUploadDelay(DsndDelay);
								//sysclk.adjustClock(Long128.valueOf( DsndDelay-DrcvDelay), 0.001);
							} else {
								long Dfallback = rtt / 2;
								if (Dfallback >= 0) {
									delay = Dfallback;
									monitor.recordUploadDelay(Dfallback);
								}
									sysclk.adjustClock(Long128.valueOf( DsndDelay-DrcvDelay), 0.1);
							}

							algorithm.setCurrentBandwidth(png.getDownSpeed());
							break;
						case KLALBPacket.BWINF:
							BWINFPacket bwi = (BWINFPacket) kpp;
							algorithm.setCurrentBandwidth(bwi.getDownSpeed());
							break;
						case KLALBPacket.ADDRREQ:
							if (addressGroup != null)
								sendPacket(new ADDRPacket(addressGroup));
							break;
						case KLALBPacket.ADDR:
							peerAddress = ((ADDRPacket) kpp).getAddr();
							if (addressGroup == null) {
								byte[] ab = peerAddress.getAddress().toByteArray();
								ab[15] = 2;
								IPv6AddressGroup ardg2 = new IPv6AddressGroup(IPv6Address.valueOf(ab),
										peerAddress.getPrefixLength());
								if (ardg2 != null)
									addressGroup = ardg2;
							}
							KLALBRemoteLink.this.onAddressUpdate();

							break;
						case KLALBPacket.VADDRREQ:
							sendPacket(new VADDRPacket(klalbController.getSelf()));
							break;
						case KLALBPacket.VADDR:
							IPv6AddressGroup vdr = ((VADDRPacket) kpp).getVaddr();
							if(vdr.getAddress().equals(klalbController.getSelf().getAddress())){
								close();
							}else {
								remoteVaddr = vdr;
								sendPacket(new VADDRACKPacket());
								if (fst.compareAndSet(true, false)) {
									// coll.resetCoolingTime();
									status.setState(LinkStatus.UP);
									KLALBRemoteLink.this.onOnlineStateUpdate();
								}
								KLALBRemoteLink.this.onLocatorUpdate();
							}
							break;
						case KLALBPacket.VADDRACK:
							break;
						case KLALBPacket.TESTN:
							break;
						case KLALBPacket.IPV6OVERKLALB:

							IPv6OverKLALBPacket ivk = (IPv6OverKLALBPacket) kpp;
							IPv6Packet iv6 = ivk.getIPv6Packet();
							if (getSrv6Router() != null) {
								getSrv6Router().onReceive(KLALBRemoteLink.this, iv6);
							}
							
							KLALBOAMHopByHopTLV ktlv = null;

							long btimes = 0;
							IPv6HopByHopHeader hop = iv6.getHopByHopHeader();
							if (hop != null) {
							List<Long> ptlv = new ArrayList<>();
								List<IPv6HopByHopTLV> tlvs = hop.getTlvs();
								for (IPv6HopByHopTLV tlv : tlvs) {
									if (tlv instanceof KLALBOAMHopByHopTLV) {
										ktlv = (KLALBOAMHopByHopTLV) tlv;
										val = ktlv.getUUID();
										btimes = ktlv.getTimestamp();
										ptlv.add(btimes);
									} else if (tlv instanceof KLALBPassportHopByHopTLV) {
										ptlv.add(btimes + ((KLALBPassportHopByHopTLV) tlv).getTimestampOffsetLong());
									}
								}
							if (ptlv.size() >= 2) {
								long delta = ptlv.get(ptlv.size() - 1) - ptlv.get(ptlv.size() - 2);
								long deltaxnanos = (long) (delta * 1000000000.0 / (1L << 32));
								if (deltaxnanos > 0)
									delay = deltaxnanos;
							}
							}
							

							break;
						}

						long length = kpp.getTotalLength();
						UUID pid = val == null ? KLALBUtils.createGlobalUUID() : val;
						//monitor.getDownloadBandwidth().recordPacket(pid,0,1, delay);
						if(delay!=Long.MAX_VALUE / 1024) {
						monitor.recordDownloadDelay(delay);
						//System.out.println(name+" "+delay);
						}
						monitor.recordDownloadPacket((int)length);
						if (klalbController != null) {
							//klalbController.getLinkMonitor().getDownloadBandwidth().recordPacket(pid, (int) length,delay);
							klalbController.getLinkMonitor().recordDownloadPacket((int)length);
						}
					//});
				}
			} catch (StreamCorruptedException sce) {
				sce.printStackTrace();
			} catch (IOException | UnresolvedAddressException e) {
				if (debug)
					e.printStackTrace();
			} catch (Exception e) {
				e.printStackTrace();
			} finally {
				try {
					if (kplink != null)
						kplink.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
					tpk.unpark();
			}
		}
		public WatchDogTimer getWatchdog() {
			return watchdog;
		}
		public long getRTT() {
			return rtt;
		}
		public long getRTTMin() {
			return rttMin;
		}

	}
	private ThreadParker tpk=new ThreadParker();

	private StateManager stateManager;
	private MonitorUpdater monitorUpdater;
	private class MonitorUpdater implements Runnable{
		@Override
		public void run() {
			while (!closed) {
				status.updateReliability();
				if(status.getState()!=LinkStatus.DOWN) {
					monitor.update();
				}
				try {
					Thread.sleep(1);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				
			}
			
		}

	}
		private RemoteSender sender;
		private RemoteReceiver receiver;
	private class StateManager implements Runnable {

		private Thread currThread;
		@Override
		public void run() {
			currThread = Thread.currentThread();
			ReliabilityBackoffTimeClock coll = new ReliabilityBackoffTimeClock();
			while (true) {
				try {

					if (socketAddress == null) {
						if (kplink.isClosed()) {
							close();
							break;
						}
						if (addressGroup == null) {
							byte[] addr = new byte[16];
							SecureRandom scr = new SecureRandom();
							scr.nextBytes(addr);
							addr[0] = (byte) 0x24;
							addr[1] = (byte) 0x86;
							addr[2] = 0;
							addr[3] = 2;
							addr[14] = 0;
							addr[15] = 1;
							IPv6Address i6a = IPv6Address.valueOf(addr);
							addressGroup = new IPv6AddressGroup(i6a, 112);
						}
					} else {
						kplink = KLALBUtils.createKLALBPacketLink(bindAddress, socketAddress, true);
					}
					// startRecord();

					kplink.setSoTimeout(3000);
					
					
					algorithm=getCongestionAlgorithm();
					sender = new RemoteSender();
					receiver = new RemoteReceiver();
					
					

					sendWindow = new SendPacketSlidingWindow<UUID, IPv6Packet>(algorithm, 65535, HEADER_CALIBRATE);
					sendWindow.setResendConsumer(rerouteConsumer);
					sendWindow.setClosingConsumer(rerouteConsumer);
					algorithm.setWindowControlConsumer((sendwindow) -> {
						sendWindow.setWindowSize(sendwindow);
					});

					Thread t = ThreadTool.makeVDaemonThreadIfSupport("远程发送线程", sender);
					t.start();

					int cores = Runtime.getRuntime().availableProcessors();
					for (int i = 0; i < 1; i++) {
						ThreadTool.makeVDaemonThreadIfSupport("远程接收线程", receiver).start();
					}

					while (!kplink.isClosed()) {
						if(klalbController!=null&& klalbController.getConfigItem()!=null) {
							KLALBControllerConfigItem config=klalbController.getConfigItem();
						if(algorithm instanceof DetnetCongestionAlgorithm) {
							double v0=config.getBurstLimit();
							if(v0!=0) {
								((DetnetCongestionAlgorithm)algorithm).setBurstLimit(v0);
							}
							double v1=config.getDelayUpperBound();
							if(v1!=0) {
							((DetnetCongestionAlgorithm)algorithm).setUpperDelayBound(v1);
							}
							double v2=config.getDelayLowerBound();
							if(v2!=0) {
							((DetnetCongestionAlgorithm)algorithm).setLowerDelayBound(v2);
							}
						}
						sender.setFlushInterval( config.getLinkNagleDelayTime());

						
						}
						boolean b =  sender.getWatchdog().isBarking()
								|| receiver.getWatchdog().isBarking();
						boolean isup= (!b)&&(receiver.getRTT()<1000000000L);//&&(receiver.getRTT()<receiver.getRTTMin()*5+50000000L)
						int oldstate=status.getState();
						int newstate=isup?LinkStatus.UP:LinkStatus.UNSTABLE;
						status.setState(newstate);
						if(oldstate!=newstate) {
						onOnlineStateUpdate();
						}
						tpk.parkNanos(10000000L);
					}
				} catch (StreamCorruptedException sce) {
					sce.printStackTrace();
				}catch(BindException e) {
						close();
						if (debug)
							e.printStackTrace();
				}catch(NoRouteToHostException e){
					close();
					if (debug)
						e.printStackTrace();
				} catch(SocketException e) {
					String massage = e.getMessage();
					if (massage.toLowerCase().contains("unreachable")) {
						close();
					} else {
						if (debug)
							e.printStackTrace();
					}
				}catch (IOException | UnresolvedAddressException e) {
					if (debug)
						e.printStackTrace();
				} catch (Exception e) {
					e.printStackTrace();
				} finally {
					status.setState(LinkStatus.DOWN);
					try {
						if (kplink != null)
							kplink.close();
					} catch (IOException e) {
						e.printStackTrace();
					}
					if (sendWindow != null)
						sendWindow.close();
					onOnlineStateUpdate();
				}

				if (closed) {
					break;
				}
				LockSupport.parkNanos(coll.getCoolingTime(status.getReliability()));

			}
		}
		private CongestionAlgorithm getCongestionAlgorithm() {
			if(klalbController!=null&& klalbController.getConfigItem()!=null) {
				KLALBControllerConfigItem config=klalbController.getConfigItem();
				String cong=config.getCongestionAlgorithm();
				if(cong!=null) {
				CongestionAlgorithm alg=CongestionAlgorithms.get(cong);
				if(alg==null) {
					alg=new BBRCongestionAlgorithm();
				}
				return alg;
				}else {
					return new BBRCongestionAlgorithm();
				}
			}else {
				return new BBRCongestionAlgorithm();
			}
		}
		public void reconnectImmediately() {
			LockSupport.unpark(currThread);
		}
		public RemoteSender getSender() {
			return sender;
		}
		public RemoteReceiver getReceiver() {
			return receiver;
		}
		
	}
	
	protected void startIO() {
		stateManager=new StateManager();
		monitorUpdater=new MonitorUpdater();
		ThreadTool.makeVDaemonThread("线路性能采样线程", monitorUpdater).start();
		ThreadTool.makeVDaemonThread("远程线路状态线程", stateManager).start();
		
	}

	public void ping() throws IOException {

		PINGPacket ping = new PINGPacket(sysclk.getCurrentTimeNTP64().longValue(), monitor.getOutSpeed(),
				monitor.getInSpeed());
		/*
		 * Long128 local=sysclk.getCurrentTimeNanos(); BigInteger local128=
		 * NTPTimestamps.nanosToNtp128BitTimestamp(local);
		 * System.out.println("psnd0"+NTPTimestamps.nanosSince1970ToString(
		 * NTPTimestamps.ntp128BitToNanosTimestamp( NTPTimestamps.inferNtp64To128(
		 * ping.getTime(),local128))));
		 */
		sendPacket(ping);
	}

	private KLALBPacket readKLALBPacketFromKPL() throws IOException {
		KLALBPacket packet = kplink.readKLALBPacket();
		debugShowPacket("RX", packet);
		if (packet != null) {

		}
		return packet;
	}

	private void writeKLALBPacketToKPL(KLALBPacket packet) throws IOException {
		if (packet instanceof TESTNPacket)
			System.err.println(packet);
		kplink.writeKLALBPacket(packet);

		debugShowPacket("TX", packet);
	}

	private void debugShowPacket(String label, KLALBPacket packet) {
		if (showpacket) {
			if (!(packet instanceof PINGPacket))
				if (!(packet instanceof PONGPacket))
					if (!(packet instanceof BWINFPacket))
						System.err.println(label + ":" + packet);
		}
	}

	public void close() {
		closed = true;
		status.setState(LinkStatus.DOWN);
		try {
			if (kplink != null)
				kplink.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	public boolean isClosed() {
		return closed;
	}

	


	


	public void sendPacket(KLALBPacket vaddrreqPacket) {
		sendPacket(vaddrreqPacket, null);
	}

	protected void sendPacket(KLALBPacket blk, UUID val) {
		debugShowPacket("TX", blk);
		RemoteSender sender=stateManager.getSender();
		if(sender!=null) {
		blk.markJoinqueuetime();
		long length = blk.getTotalLength();
		UUID pid = val == null ? KLALBUtils.createGlobalUUID() : val;
		monitor.recordUploadPacket((int) length);
		if (klalbController != null) {
			klalbController.getLinkMonitor().recordUploadPacket((int) length);
		}
		sender.sendDirect(blk);
		}
	}


	public void reconnectImmediately() {
		StateManager stateManagerx=stateManager;
		if (stateManagerx != null) {
			stateManagerx.reconnectImmediately();
		}
	}

	public void dislink() {
		if (kplink != null) {
			try {
				kplink.close();
			} catch (IOException e) {
			}
		}
	}

	@Override
	public boolean isLoopBack() {
		return false;
	}
	BandwidthDistributer bandwidthDistributer=new BandwidthDistributer<>();
	@Override
	public List<Neighbor> getNeighborsInfo() {
		List<Neighbor> hs = new ArrayList<>(1);
		if (peerAddress != null) {
			hs.add(new Neighbor(peerAddress, remoteVaddr, monitor, bandwidthDistributer));
		}
		return hs;
	}


	private Consumer<IPv6Packet> rerouteConsumer;

	@Override
	public void sendPacket(IPv6Packet pack, IPv6Address inet6Address) throws IOException {
		// 只有已连接的链路才能传输数据
		if (status.getState() == LinkStatus.UP) {

			IPv6HopByHopHeader hop = pack.getHopByHopHeader();

			UUID val = null;
			if (hop != null) {
				List<IPv6HopByHopTLV> l = hop.getTlvs();
				for (int i = 0; i < l.size(); i++) {
					IPv6HopByHopTLV tlv = l.get(i);
					if (tlv instanceof KLALBOAMHopByHopTLV) {
						KLALBOAMHopByHopTLV seqs = (KLALBOAMHopByHopTLV) tlv;
						// 获得数据包的序号
						val = seqs.getUUID();

						break;
					}
				}

				if (val != null) {
					//if (!(pack.getPayload() instanceof ICMPv6PostcardPacket)) {
						// 放入滑动窗口中并记录缓冲区大小占用
						sendWindow.put(val, pack);
					//}
				}
			}
			
			sendIPv6PacketToLink(pack, val);
		

		} else {
			if (rerouteConsumer != null) {
				rerouteConsumer.accept(pack);
			}
		}
	}

	@Override
	public void setRerouteConsumer(Consumer<IPv6Packet> rerouteConsumer) {
		this.rerouteConsumer = rerouteConsumer;
	}

	private void sendIPv6PacketToLink(IPv6Packet pack, UUID val) {
		IPv6OverKLALBPacket kipv6 = new IPv6OverKLALBPacket(pack);
		sendPacketToLink(kipv6, val);
	}

	private void sendPacketToLink(KLALBPacket packet, UUID val) {
		sendPacket(packet, val);
	}

	@Override
	public boolean isCongestion(IPv6Packet iPv6Packet, double scale) {
		SendPacketSlidingWindow<UUID, IPv6Packet> sw = sendWindow;
		if (sw != null) {
			return  sw.isCongestion( scale);
		} else {
			return true;
		}
	}

	@Override
	public String getName() {
		return name;
	}

	

	@Override
	public boolean isUp() {
		return (!isClosed()) && (status.getState() == LinkStatus.UP) ;
	}

	@Override
	public List<RouteItem> getRouteItems() {
		List<RouteItem> rlist = new ArrayList<>();

	if(isUp()) {
		IPv6AddressGroup adg = addressGroup;
		/*if (adg != null)
			rlist.add(new RouteItem(new IPv6AddressGroup(adg.getAddress(), 128), adg.getAddress(), this, "Direct", 0, 0,
					null, "D", true));*/

		for (Iterator<Neighbor> iteratorx = getNeighborsInfo().iterator(); iteratorx.hasNext(); ) {
			Neighbor addresses = (Neighbor) iteratorx.next();
			RouteItem ri = new RouteItem(new IPv6AddressGroup(addresses.getAddress().getAddress(), 128),
					addresses.getAddress().getAddress(), this, "Direct", 0, 128, CostSupplierFactory.expectedDelaySupplier((DelayMonitorData) addresses.getMonitor(), status, algorithm::getRTO), "D", false);
			rlist.add(ri);

			if (addresses.getLocator() != null) {
				RouteItem ris = new RouteItem(addresses.getLocator(), addresses.getLocator().getAddress(), this,
						"KLALB SRv6", 13, 128, CostSupplierFactory.expectedDelaySupplier((DelayMonitorData) addresses.getMonitor(), status, algorithm::getRTO), "D", false);
				rlist.add(ris);
			}
		}
	}
		return rlist;
	}

	@Override
	public void setCongressCondition(Lock lock, Condition condition) {
		if (sendWindow != null)
			sendWindow.setCongressCondition(lock, condition);
	}

	public void onPostcardReceive(PostcardEntry postcard) {
		long deltaxnanos = (long) (postcard.getTimestampDeltaLong() * 1000000000.0 / (1L << 32));
		if (deltaxnanos > 0) {
			Long128 psnd = NTPTimestamps.ntp128BitToNanosTimestamp(
					NTPTimestamps.inferNtp64To128(postcard.getTimestampBase(), sysclk.getCurrentTimeNTP128()));
			monitor.recordUploadDelay(deltaxnanos);
		}
		sendWindow.ack(postcard.getPacketUUID(),false,deltaxnanos);
		
	}


	public int getState() {
		return status.getState();
	}


	public SendPacketSlidingWindow<UUID, IPv6Packet> getWindow() {
		return sendWindow;
	}


	public long getQueueUsed() {
		if(sender==null) {
			return 0;
		}
		return sender.getQueueUsed();
	}



}
