package org.kne.cloud.network.klalb;

import java.io.IOException;
import java.net.BindException;
import java.net.DatagramPacket;
import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.SocketAddress;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.nio.BufferOverflowException;
import java.nio.ByteBuffer;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.LockSupport;

import org.kne.cloud.network.VirtualDatagramSocketImpl;
import org.kne.cloud.network.ipv6.IPv6Address;
import org.kne.cloud.network.ipv6.IPv6Packet;
import org.kne.cloud.network.tcp.UDPPacket;

public class KLALBVirtualDatagramSocketImpl extends VirtualDatagramSocketImpl implements BindableConsumer<IPv6Packet>{

	private KLALBController controller; 
	
	private volatile boolean closed=false;
	
	protected Inet6Address remoteaddr;
	protected Inet6Address localaddr;

	protected int remotePort;{
		try {
			localaddr=(Inet6Address) Inet6Address.getByName("::0");
		} catch (UnknownHostException e) {
			e.printStackTrace();
		}
	}
	private boolean ignoreBindCheck=false;

	private int ttl=255;
	

	private volatile int inputchachesize = 1024*1024;
	
	public KLALBVirtualDatagramSocketImpl(KLALBController controller) {
		this.controller=controller;
	}

	@Override
	public void setOption(int optID, Object value) throws SocketException {
		// TODO 自动生成的方法存根

	}

	@Override
	public Object getOption(int optID) throws SocketException {
		// TODO 自动生成的方法存根
		return null;
	}

	@Override
	protected void create() throws SocketException {
		// TODO 自动生成的方法存根

	}

	@Override
	protected void bind(int port, InetAddress host) throws SocketException {
		try {
			if (host.equals(Inet4Address.getByName("0.0.0.0"))) {
				host = Inet6Address.getByName("::0");
			}
		} catch (UnknownHostException e) {
			e.printStackTrace();
		}
		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 = (Inet6Address) host;
		localPort=port;
		if(!ignoreBindCheck)
		controller.getUDPregister().bind(this);
		controller.getUDPregister().listen(this);
	}

	@Override
	protected void send(DatagramPacket p) throws IOException {
		UDPPacket udp=new UDPPacket(localPort, p.getPort());
		udp.getData().put(p.getData(), p.getOffset(), p.getLength());
		udp.getData().flip();
		controller.getIpv6Router().enqueuePacketSendTask(()->{
			IPv6Packet uip=controller.createPacketToAddress(IPv6Address.valueOf( p.getAddress()),0, udp);
		return uip;
		});
	}

	@Override
	protected int peek(InetAddress i) throws IOException {
		return peekNextPacket().getSrcPort();
	}

	@Override
	protected int peekData(DatagramPacket p) throws IOException {
		UDPPacket pack=peekNextPacket();
		copyTo(pack, p);
		return p.getPort();
	}

	@Override
	protected void receive(DatagramPacket p) throws IOException {
		UDPPacket pack=pollNextPacket();
		copyTo(pack, p);
	}

	private void copyTo(UDPPacket pack, DatagramPacket p) throws IOException {
		p.setAddress(pack.getParent().getSourceAddress2());
		p.setPort(pack.getSrcPort());
		ByteBuffer buffer= ByteBuffer.wrap(p.getData(),p.getOffset(),p.getLength());
		
			try {
				buffer.put(pack.getData());
				pack.getData().rewind();
			}catch(BufferOverflowException e) {
				
			}
		buffer.flip();
		p.setLength(buffer.limit());
	}
	
	protected void setTTL(byte ttl) throws IOException {
		this.ttl=ttl;
	}

	protected byte getTTL() throws IOException {
		return (byte) ttl;
	}

	@Override
	protected void setTimeToLive(int ttl) throws IOException {
		this.ttl=ttl;
	}

	@Override
	protected int getTimeToLive() throws IOException {
		return ttl;
	}

	@Override
	protected void join(InetAddress inetaddr) throws IOException {
		// TODO 自动生成的方法存根

	}

	@Override
	protected void leave(InetAddress inetaddr) throws IOException {
		// TODO 自动生成的方法存根

	}

	@Override
	protected void joinGroup(SocketAddress mcastaddr, NetworkInterface netIf) throws IOException {
		// TODO 自动生成的方法存根

	}

	@Override
	protected void leaveGroup(SocketAddress mcastaddr, NetworkInterface netIf) throws IOException {
		// TODO 自动生成的方法存根

	}

	@Override
	protected void close() {
		controller.getUDPregister().unlisten(this);
		if(!ignoreBindCheck)
		controller.getUDPregister().unbind(this);
		closed=true;
	}

	private volatile Thread parkThread;
	private UDPPacket peekNextPacket() {
		UDPPacket pol=null;
		while(true) {
			pol=recvQueue.peek();
			if(pol!=null) {
				recvQueueUsed.addAndGet((int) -pol.getDataLength());
				return pol;
			}
			parkThread=Thread.currentThread();
			LockSupport.parkNanos(1000000L);
		}
	}
	private UDPPacket pollNextPacket() {
		UDPPacket pol=null;
		while(true) {
			pol=recvQueue.poll();
			if(pol!=null) {
				recvQueueUsed.addAndGet((int) -pol.getDataLength());
				return pol;
			}
			parkThread=Thread.currentThread();
			LockSupport.parkNanos(1000000L);
		}
	}
	
	private Queue<UDPPacket> recvQueue = new ConcurrentLinkedQueue<UDPPacket>();
	private AtomicInteger recvQueueUsed=new AtomicInteger(0);
	@Override
	public void accept(IPv6Packet t) {
		if(t.getPayload() instanceof UDPPacket) {
		UDPPacket u=(UDPPacket) t.getPayload();
		if(recvQueueUsed.get()<=inputchachesize) {
			if(recvQueue.offer(u)) {
				recvQueueUsed.addAndGet((int) u.getDataLength());
				LockSupport.unpark(parkThread);
			}
			}
		}
	}

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

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

	@Override
	public int getPort() {
		return remotePort;
	}

	@Override
	public void setLocalPort(int i) {
		this.localPort=i;
	}

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

	

}
