package org.kne.cloud.network.klalb;

import java.io.IOException;
import java.net.BindException;
import java.net.ConnectException;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.NetworkInterface;
import java.net.NoRouteToHostException;
import java.net.Socket;
import java.net.SocketAddress;
import java.net.SocketException;
import java.net.SocketTimeoutException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Scanner;
import java.util.Set;
import java.util.Timer;
import java.util.UUID;
import java.util.Vector;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Supplier;

import javax.management.openmbean.ArrayType;
import javax.net.ServerSocketFactory;
import javax.net.SocketFactory;

import org.kne.cloud.network.MultipurposeSocketAddress;
import org.kne.cloud.network.NetworkService;
import org.kne.cloud.network.Proxy;
import org.kne.cloud.network.SocketType;
import org.kne.cloud.network.ThreadTool;
import org.kne.debug.TimeDebugger;

import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import javassist.bytecode.Bytecode;
import javassist.bytecode.CodeAttribute;
import javassist.bytecode.CodeIterator;

public class KLALBController {
	private SocketType socketType= new KLALBSocketType();
	public class KLALBSocketType extends SocketType{

		public KLALBSocketType() {
			super(new KLALBVirtualSocketFactory(KLALBController.this), new KLALBVirtualServerSocketFactory(KLALBController.this));
		}
		
	}
	public SocketType getSocketType() {
		return socketType;
	}

	private Supplier<String> selflineTableSupplier=()->{return null;};
	

	public Supplier<String> getSelflineTableSupplier() {
		return selflineTableSupplier;
	}

	public void setSelflineTableSupplier(Supplier<String> selflineTableSupplier) {
		this.selflineTableSupplier = selflineTableSupplier;
	}

	private Inet6Address self;

	public Inet6Address getSelf() {
		return self;
	}
	private List<KLALBRemoteLine> lines=new ArrayList<>();
	//private ReadWriteLock lineslock=new ReentrantReadWriteLock();
	
	public List<KLALBRemoteLine> getLines() {
		return lines;
	}

	private PortBinder streamPortBinder=new PortBinder(this);
	
	protected PortBinder getStreamPortBinder() {
		return streamPortBinder;
	}


	public void reconnectImmediately() {
		synchronized (lines) {
			for (Iterator<KLALBRemoteLine> iterator = lines.iterator(); iterator.hasNext();) {
				KLALBRemoteLine klalbRemoteLine = (KLALBRemoteLine) iterator.next();
				klalbRemoteLine.reconnectImmediately();
			}
		}
	}
	
	private PacketReceiver prc=new PacketReceiver();
	private class PacketReceiver implements KLALBPacketConsumer{

		@Override
		public void accept(KLALBRemoteLine krs, KLALBPacket rec) {
			try {
				if(rec instanceof PortPacket&&krs.getRemoteVaddr()!=null) {
					PortPacket pt=(PortPacket) rec;
					if(!streamPortBinder.distributePacketToConsumer(krs, pt)) {
						if(!(pt instanceof RSTPacket))
						sendPacketToAddress(krs.getRemoteVaddr(), new RSTPacket(pt.getDport(), pt.getSport()),
								0,2);
					}
				}else {
				switch (rec.getType()) {
				case KLALBPacket.ADDLINES:
					ADDLINESPacket lpt=(ADDLINESPacket) rec;
					String s=lpt.getLines();
					Scanner scn=new Scanner(s);
					while(scn.hasNext()) {
						String sn=scn.nextLine();
						MultipurposeSocketAddress msa=	new MultipurposeSocketAddress(sn);
						addRemoteLines(msa);
						
					}
					break;
				default:
					break;
				}
				}
			} catch (IOException e) {
				// TODO 自动生成的 catch 块
				e.printStackTrace();
			}
		}

		
	}
	public void addRemoteLines(MultipurposeSocketAddress target) throws SocketTimeoutException, SocketException {
		synchronized (lines) {
			
				try {
					Enumeration<NetworkInterface>eu= NetworkInterface.getNetworkInterfaces();
					while (eu.hasMoreElements()) {
						NetworkInterface networkInterface = (NetworkInterface) eu.nextElement();
						if(networkInterface.isUp()) {
						//System.out.println(networkInterface+" "+networkInterface.isUp());
						Enumeration<InetAddress>ei= networkInterface.getInetAddresses();
						while (ei.hasMoreElements()) {
							InetAddress inetAddress = (InetAddress) ei.nextElement();
							MultipurposeSocketAddress bind=new MultipurposeSocketAddress(inetAddress.getHostAddress(),0);
							if(!checkContainsTargetAndBind(target,bind)) {
								//System.out.println(target+" "+bind);
							addRemoteLine( new KLALBRemoteLine(target,bind));
							}
						}
						}
					}
				} catch (SocketException e) {
					if(!checkContainsTarget(target))
					addRemoteLine( new KLALBRemoteLine(target));
					throw e;
				}
			
		}
	}
	public Inet6Address getRemoteVaddrBySocketAddress(MultipurposeSocketAddress target) throws SocketTimeoutException {
		KLALBRemoteLine kr=null;
		synchronized(lines) {
		for (Iterator iterator = lines.iterator(); iterator.hasNext();) {
			KLALBRemoteLine klalbRemoteLine = (KLALBRemoteLine) iterator.next();
			if(target.equals(klalbRemoteLine.getSocketAddress())) {
				kr=klalbRemoteLine;
			}
		}
		}
		if(kr==null) {
		kr=new KLALBRemoteLine(target);
	addRemoteLine(kr);
	kr.waitForRemoteVaddrAvaliable(20000);
		}else {
			kr.reconnectImmediately();
			kr.waitForRemoteVaddrAvaliable(20000);
		}
		return kr.getRemoteVaddr();
	}
	private boolean checkContainsTargetAndBind(MultipurposeSocketAddress target,MultipurposeSocketAddress bind) {
		boolean b=false;
		for (Iterator<KLALBRemoteLine> iterator = lines.iterator(); iterator.hasNext();) {
			KLALBRemoteLine klalbRemoteLine = (KLALBRemoteLine) iterator.next();
			if(bind.equals(klalbRemoteLine.getBindAddress())&&target.equals(klalbRemoteLine.getSocketAddress())) {
				b=true;
				break;
			}
		}
		return b;
	}

	private boolean checkContainsTarget(MultipurposeSocketAddress target) {
		boolean b=false;
		for (Iterator<KLALBRemoteLine> iterator = lines.iterator(); iterator.hasNext();) {
			KLALBRemoteLine klalbRemoteLine = (KLALBRemoteLine) iterator.next();
			if(target.equals(klalbRemoteLine.getSocketAddress())) {
				b=true;
				break;
			}
		}
		return b;
	}

	public void addRemoteLine(KLALBRemoteLine krs) throws SocketTimeoutException {
		krs.setPacketReceiver(prc);
		krs.setLocalVaddrSupplier(()->{return self;});
		krs.startIO();
		String selflineTable=selflineTableSupplier.get();
		if(selflineTable!=null)
		krs.sendPacket(new ADDLINESPacket(selflineTable), 0);
		synchronized (lines) {
		lines.add(krs);
		}
	}


	public KLALBController(Inet6Address self) {
		this.self = self;
	}

	public KLALBController() {
		this.self=KLALBUtils.uuidToIP(UUID.randomUUID());
	}

	protected KLALBVirtualSocketImpl createVirtualImpl() {
		return new KLALBVirtualSocketImpl(this);
	}

	

	protected void sendPacketToAddress(Inet6Address addr, KLALBPacket syntPacket, int priority)
			throws IOException {
		sendPacketToAddress(addr, syntPacket, priority, 1);
	}
	
			private void updateLines2(Inet6Address addr) throws SocketTimeoutException {
				List<KLALBRemoteLine> l=new ArrayList();
				synchronized (lines) {
				for (int i = 0; i < lines.size(); i++) {
					KLALBRemoteLine klalbRemoteLine = lines.get(i);
					if(klalbRemoteLine.isClosed()) {
						lines.remove(i);
						i--;
						continue;
					}
					if(addr.equals(klalbRemoteLine.getRemoteVaddr())&&klalbRemoteLine.getMonitor().getState()==Monitor.ONLINE) {
						l.add(klalbRemoteLine);
					}
				}
		}
				if(l.isEmpty()) {
					lines2.remove(addr);
				}else {
				lines2.put(addr, l);
				}
			}
			private	Map<Inet6Address,List<KLALBRemoteLine>> lines2 = new ConcurrentHashMap<>();
			private volatile long itm=System.nanoTime();
	protected void sendPacketToAddress(Inet6Address addr, KLALBPacket packet, int priority, int count)
			throws IOException {
		/*if(packet instanceof RSTPacket) {
			new Exception("-RST-").printStackTrace();
		}*/
		//TimeDebugger tdb=new TimeDebugger();
		//tdb.putTime("start");
		
		List<KLALBRemoteLine> lines2x;
			//loop:while(true) {
				long cur=System.nanoTime();
			if(cur-itm>10000000L) {
				itm=cur;
				lines2.clear();
			}
				lines2x=lines2.get(addr);
				if (lines2x == null || lines2x.isEmpty()) {
					updateLines2(addr);
					lines2x=lines2.get(addr);
				}
				if (lines2x == null || lines2x.isEmpty()) {
					throw new NoRouteToHostException("address unreachable: " + addr);
				}
			//tdb.putTime("selectLines");
			/*	for (Iterator<KLALBRemoteLine> iterator = lines2x.iterator(); iterator.hasNext();) {
					KLALBRemoteLine klalbRemoteLine = (KLALBRemoteLine) iterator.next();
					if(klalbRemoteLine.statLengthBefore(priority)<=65536*10) {
						break loop;
					}
				}
				try {
					//System.out.println("slp");
					Thread.sleep(1);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}*/
			List<KLALBRemoteLine> l2 = (List<KLALBRemoteLine>) ((ArrayList<KLALBRemoteLine>) lines2x).clone();
			l2.removeAll(packet.getSendRecord());
			if(l2.isEmpty()) {
				l2 = (List<KLALBRemoteLine>) ((ArrayList<KLALBRemoteLine>) lines2x).clone();
			}

			//tdb.putTime("findAvaliable");
			
			int count0 = Math.min(count, l2.size());
			l2.forEach((r)->{
				r.runPredict(packet,priority);
			});
			//Collections.shuffle(l2);
			Collections.sort(l2);
			//tdb.putTime("makeDecision");
			//System.out.println(l2);
			for (int i = 0; i < l2.size(); i++) {
				KLALBRemoteLine krst =l2.get(i);
				krst.sendPacket(packet, priority);
				packet.getSendRecord().add(krst);
				count0--;
				if (count0 <= 0)
					break;
			}
			//tdb.putTime("sendPacket");
			//tdb.print();
			/*if(packet instanceof RSTPacket)
			new Exception().printStackTrace();*/
	}
	protected void removeFromSend(Inet6Address addr,KLALBPacket klalbPacket) {
		synchronized (lines) {
			for (Iterator<KLALBRemoteLine> iterator = lines.iterator(); iterator.hasNext();) {
				KLALBRemoteLine klalbRemoteLine = (KLALBRemoteLine) iterator.next();
				klalbRemoteLine.remoeFromSendQueue(klalbPacket);
			}
		}
	}
private Timer t=new Timer("数据包发送计时器", true);
	public Timer getTimer() {
		return t;
	}
	
	
	public void registerToProxyTypeAs(String proxyname) {
		MultipurposeSocketAddress.getSocketTypeRegister().put(proxyname+"_Stream",socketType);
		//ProxyProfileEntry.getRegister().put(proxyname, this);
	}


	/*@Override
	public void listen(MultipurposeSocketAddress msa) {
		// TODO 自动生成的方法存根
		
	}

	@Override
	public void unlisten(MultipurposeSocketAddress msc) {
		// TODO 自动生成的方法存根
		
	}

	@Override
	public void connect(MultipurposeSocketAddress msa) {
		// TODO 自动生成的方法存根
		
	}

	@Override
	public void unconnect(MultipurposeSocketAddress msc) {
		// TODO 自动生成的方法存根
		
	}*/

	
}
