package org.kne.cloud.network.klalb;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.BindException;
import java.net.ConnectException;
import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.NoRouteToHostException;
import java.net.SocketAddress;
import java.net.SocketException;
import java.net.SocketImpl;
import java.net.SocketOptions;
import java.net.SocketTimeoutException;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;

import org.kne.cloud.network.VirtualSocketImpl;
import org.kne.cloud.network.ipv6.IPv6Address;
import org.kne.cloud.network.kltp.KLTPInputStream;
import org.kne.cloud.network.kltp.KLTPOutputStream;
import org.kne.cloud.network.kltp.KLTPSessionPacket;

public class KLALBVirtualSocketImpl extends VirtualSocketImpl implements BindableConsumer<KLTPSessionPacket>{

	private static final boolean debug = false;
	
	private static final int DEFAULT_TIMEOUT=10000;
	
	private KLALBController controller;

		protected IPv6Address remoteaddr;
		protected IPv6Address localaddr=IPv6Address.UNSPECIFIED;
		
		private volatile KLTPInputStream vin;
		private volatile KLTPOutputStream vout;
		
		

		private boolean ignoreBindCheck;
		
		private boolean connected;
		
		private boolean closed;

		private BlockingQueue<KLTPSessionPacket> backlogQueue;

		protected boolean isListening() {
			return backlogQueue != null;
		}
	

	public KLALBController getController() {
		return controller;
	}
	public KLALBVirtualSocketImpl(KLALBController kc) {
		super();
		this.controller = kc;
	}

	@Override
	public void setOption(int optID, Object value) throws SocketException {
			switch(optID) {
			/*case SocketOptions.TCP_NODELAY:
				nodelay=(boolean) value;
				break;
			case SocketOptions.SO_RCVBUF:
				inputchachesize=(int) value;
				break;
			case SocketOptions.SO_SNDBUF:
				outputchachesize=(int)value;
				break;*/
			case SocketOptions.SO_TIMEOUT:
				vin.setSoTimeout((int)value);
				break;
			}
	}

	@Override
	public Object getOption(int optID) throws SocketException {
		switch(optID) {
		/*case SocketOptions.TCP_NODELAY:
			return nodelay;
		case SocketOptions.SO_RCVBUF:
			return inputchachesize;
		case SocketOptions.SO_SNDBUF:
			return outputchachesize;*/
		case SocketOptions.SO_TIMEOUT:
			return vin.getSoTimeout();
		case SocketOptions.SO_BINDADDR:
			return localaddr;
					default:
						return null;		
		}
		
	}

	@Override
	protected void create(boolean stream) throws IOException {
		if (!stream) {
			throw new RuntimeException("please use KLALBVirtualDatagramSocket to process udp packet");
		}
	}

	@Override
	protected void connect(String host, int port) throws IOException {
		connect(InetAddress.getByName(host), port);
	}

	@Override
	protected void connect(InetAddress address, int port) throws IOException {
		connect(new InetSocketAddress(address, port), DEFAULT_TIMEOUT);
	}
	
	
	
	@Override
	protected void connect(SocketAddress address, int timeout) throws IOException {
		if(!ignoreBindCheck)
			if (!controller.getKLTPregister().checkIsBind(this)) {
				bind(Inet6Address.getByName("::0"), 0);
			}
			port = ((InetSocketAddress) address).getPort();
			this.address = (Inet6Address) ((InetSocketAddress) address).getAddress();
			this.remoteaddr=IPv6Address.valueOf(this.address);
			
			if(connected)
				throw new SocketException("already connected");
			controller.getKLTPregister().connect(this);
			
			
			try {
			vout=new KLTPOutputStream(controller, remoteaddr, KLALBUtils.createGlobalUUID());
			KLTPSessionPacket session=new KLTPSessionPacket(localport,port);
			session.writeToChannel(vout);
			vout.flush();
			System.out.println("发起连接："+session+" "+localport);
			if(timeout==0) {
				timeout=DEFAULT_TIMEOUT;
			}
			long start=System.nanoTime();
			while(vin==null) {
				long curr=System.nanoTime();
				if(curr-start>(timeout*1000000L)) {
					throw new SocketTimeoutException("connect time out");
				}
			}
			
			connected=true;
			
		
			
			}catch(NoRouteToHostException e) {
				throw e;
			}catch(SocketException e) {
				e.printStackTrace();
				throw new ConnectException("connect refused");
			}finally {
				//connectionPending=false;
			}
	}

	@Override
	protected void bind(InetAddress host, int port) throws IOException {
		bind(host,port,false);
	}
	protected void bind(InetAddress host, int port,boolean ignoreBindCheck) throws IOException {
		if (host.equals(Inet4Address.getByName("0.0.0.0"))) {
			host = Inet6Address.getByName("::0");
		}
		if (!(host instanceof Inet6Address)) {
			throw new IllegalArgumentException("invalid address type, KLALB socket can only use IPV6 virtualaddress");
		}
		if ((!host.isAnyLocalAddress()) && (! IPv6Address.valueOf( host).equals(controller.getSelf().getAddress()))) {
			throw new BindException("must bind to self");
		}
		localaddr = IPv6Address.valueOf( host);
		localport=port;
		this.ignoreBindCheck=ignoreBindCheck;
		if(!ignoreBindCheck) {
		controller.getKLTPregister().bind(this);
		}
		//System.out.println("绑定："+host.getHostAddress()+":"+localport);
	}

	@Override
	public int getPort() {
		return super.getPort();
	}

	@Override
	protected void listen(int backlog) throws IOException {
		backlogQueue = new ArrayBlockingQueue<>(backlog);
		controller.getKLTPregister().listen(this);
		address=localaddr.toInet6Address();
	}

	@Override
	protected void accept(SocketImpl s) throws IOException {
		//System.out.println("Enter accept=============================");
			KLALBVirtualSocketImpl kvsi = (KLALBVirtualSocketImpl) s;
			
			KLTPSessionPacket p=null;
			while(true) {
				if (isClosed())
				throw new SocketException("Socket is closed");
			p=backlogQueue.poll();
			if(p!=null) {
				break;
			}
			try {
				Thread.sleep(1);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			}

			System.out.println(p);
			KLTPInputStream input=p.getInputstream();
			kvsi.address = input.getRemoteAddress().toInet6Address();
			kvsi.remoteaddr=input.getRemoteAddress();
			
			kvsi.bind(localaddr.toInet6Address(), localport,true);
			kvsi.accept(p);
			kvsi.connect(new InetSocketAddress(kvsi.address, p.getSrcPort()),10);
			//System.out.println("Exit accept=============================");
		
	}


	
	@Override
	public InputStream getInputStream() throws IOException {
		
		return vin;
	}
	
	@Override
	public OutputStream getOutputStream() throws IOException {
		return vout;
	}

	@Override
	protected int available() throws IOException {
		return getInputStream().available();
	}

	@Override
	protected void shutdownInput() throws IOException {
		throw new UnsupportedOperationException("Unsupported Shutdown Input");
		//getInputStream().close0();
	}

	@Override
	protected void shutdownOutput() throws IOException {
		throw new UnsupportedOperationException("Unsupported Shutdown Output");
		//getOutputStream().close0();
	}

	@Override
	public String toString() {
		return "KLALBVirtualSocketImpl [vin=" + vin + ", vout=" + vout + "]";
	}



	@Override
	protected void close() throws IOException {
		
		//new Exception().printStackTrace();
		closed=true;
		try {
			if(vout!=null)
		vout.close();
		}finally {
			if(vin!=null)
		vin.close();
					
					if(!isListening() ) {
				
				controller.getKLTPregister().disconnect(this);
				
					}else {
					//System.out.println("unlisten"+getLocalPort());
				controller.getKLTPregister().unlisten(this);
					}
				if(!ignoreBindCheck)
						controller.getKLTPregister() .unbind(this);
		}
				
	}

	

	private boolean isClosed() {//||(vin!=null&&vin.isClosed())&&(vout!=null&&vout.isClosed())
		return closed;
	}

	@Override
	protected void sendUrgentData(int data) throws IOException {
		throw new UnsupportedOperationException("urgent data unspuuorted");
		
	}

	@Override
	public int getLocalPort() {
		return super.getLocalPort();
	}


	@Override
	public void accept(KLTPSessionPacket stream) {
		 if(isListening()) {
			 if(!backlogQueue.contains(stream)) {
				 backlogQueue.add(stream);
				 System.out.println("添加到监听队列："+stream);
			 }
		 }else {
			 System.out.println("匹配反向连接："+stream);
			 if(this.vin==null) {
			 this.vin=stream.getInputstream();
			 }
		 }
	}


	

	@Override
	public InetAddress getRemoteInetAddress() {
		return remoteaddr.toInet6Address();
	}

	@Override
	public InetAddress getLocalInetAddress() {
		return localaddr.toInet6Address();
	}

	@Override
	public void setLocalPort(int i) {
		localport=i;
	}
	
	
	public void setCompress(int i) {
		// TODO 自动生成的方法存根
		
	}
	public int getCompress() {
		// TODO 自动生成的方法存根
		return 0;
	}
	public boolean isConnectionPending() {
		// TODO 自动生成的方法存根
		return false;
	}

	

	

}
