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.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 java.util.function.BiConsumer;
import java.util.function.Supplier;

import org.kne.cloud.network.VirtualRawSocketImpl;
import org.kne.cloud.network.ipv6.IPv6Packet;
import org.kne.cloud.network.ipv6.IPv6Packet.IPv6DefaultPayload;
import org.kne.cloud.network.ipv6.IPv6ProtocolRegister;
import org.kne.cloud.network.ipv6.IPv6Address;
import org.kne.cloud.network.ipv6.IPv6AddressGroup;
import org.kne.cloud.network.ipv6.IPv6NetworkLink;
import org.kne.cloud.network.ipv6.LoopbackIPv6NetworkLink;
import org.kne.io.KNEChannels;

public class KLALBVirtualRawSocketImpl extends VirtualRawSocketImpl implements IPv6ProtocolRegister{

	private boolean ipHeaderInclude=false;

	private LoopbackIPv6NetworkLink link;
	
	
	protected volatile Inet6Address remoteaddr;
	protected volatile Inet6Address localaddr;

	private int bindProtocolNumber;

	private volatile int inputchachesize = 1024*1024;
	
	protected LoopbackIPv6NetworkLink getLoopbackLink() {
		return link;
	}
	
	public KLALBVirtualRawSocketImpl(LoopbackIPv6NetworkLink link) {
		super();
		this.link = link;
	}

	@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 IOException {
		
	}

	@Override
	protected void setIPHeaderInclude(boolean ipHeaderInclude) throws IOException {
		this.ipHeaderInclude=ipHeaderInclude;
	}

	@Override
	protected boolean getIPHeaderInclude() throws IOException {
		return ipHeaderInclude;
	}

	@Override
	protected void setUseSelectTimeout(boolean useSelect) throws IOException {
		// TODO 自动生成的方法存根

	}

	@Override
	protected boolean getUseSelectTimeout() throws IOException {
		// TODO 自动生成的方法存根
		return false;
	}

	@Override
	protected void setSendTimeout(int timeout) throws IOException {
		// TODO 自动生成的方法存根

	}

	@Override
	protected int getSendTimeout() throws IOException {
		// TODO 自动生成的方法存根
		return 0;
	}

	@Override
	protected void setReceiveTimeout(int timeout) throws IOException {
		// TODO 自动生成的方法存根

	}

	@Override
	protected int getReceiveTimeout() throws IOException {
		// TODO 自动生成的方法存根
		return 0;
	}

	@Override
	protected void close() {
		link.getProtocolNumberRegister()[bindProtocolNumber]=null;
	}

	@Override
	protected void bind(InetAddress address,int protocolNumber) throws SocketException{
		if(protocolNumber<0) {
			throw new UnsupportedOperationException("unsupprted bind all protocols");
		}
		try {
			if (address.equals(Inet4Address.getByName("0.0.0.0"))) {
				address = Inet6Address.getByName("::0");
			}
		} catch (UnknownHostException e) {
			e.printStackTrace();
		}
		if (!(address instanceof Inet6Address)) {
			throw new IllegalArgumentException("invalid address type, KLALB socket can only use IPV6 address");
		}
		if (!address.isAnyLocalAddress() ) {
			boolean contains=false;
			for(IPv6AddressGroup adg:link.getAddressGroups()) {
				if(adg.checkMatch(IPv6Address.valueOf( address))) {
					contains=true;
					break;
				}
			}
			if(!contains) {
			throw new BindException("must bind to self");
			}
		}
		
		localaddr=(Inet6Address) address;
		this.bindProtocolNumber=protocolNumber;
		if(link.getProtocolNumberRegister()[protocolNumber]!=null) {
			throw new BindException("protocol number already bind");
		}else {
			link.getProtocolNumberRegister()[protocolNumber]=this;
		}
	}

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

	}

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

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

	}

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

	}

	@Override
	protected void send(DatagramPacket p) throws IOException {
		ByteBuffer buf= ByteBuffer.wrap(p.getData(), p.getOffset(), p.getLength());
		if(ipHeaderInclude) {
			
		}else {
			IPv6DefaultPayload pl=new IPv6DefaultPayload(bindProtocolNumber);
		pl.getData().put(buf);
		pl.getData().flip();
		BiConsumer<IPv6NetworkLink, Supplier<IPv6Packet>>cons=link.getReceiveConsumer();
		if(cons!=null) {
			cons.accept(link,() -> {
		IPv6Packet ipv=new IPv6Packet();
		ipv.setVersion(6);
		ipv.setTrafficClass(0);
		ipv.setFlowLabel(0);
		ipv.setHopLimit(255);
		if(!localaddr.isAnyLocalAddress()) {
		ipv.setSourceAddress2(localaddr);
		}else {
			ipv.setSourceAddress(link.getRouter().getLocator().getAddress());
		}
		InetAddress address= p.getAddress();
		if (!(address instanceof Inet6Address)) {
			throw new IllegalArgumentException("invalid address type, KLALB socket can only use IPV6 address");
		}
		ipv.setDestinationAddress2((Inet6Address)address);
		//ipv.enableECN();
		ipv.setPayload(pl);
			return ipv;
			});
		}
		}
	}

	@Override
	protected InetAddress peek() throws IOException {
		return peekNextPacket().getSourceAddress2();
	}

	@Override
	protected void peekData(DatagramPacket p) throws IOException {
		IPv6Packet pack=peekNextPacket();
		copyTo(pack, p);
	}

	private void copyTo(IPv6Packet pack, DatagramPacket p) throws IOException {
		p.setAddress(pack.getSourceAddress2());
		ByteBuffer buffer= ByteBuffer.wrap(p.getData(),p.getOffset(),p.getLength());
		if(ipHeaderInclude) {
			 
		}else {
			try {
			pack.getPayload().writeToChannel(KNEChannels.newWritableChannel(buffer));
			}catch(BufferOverflowException e) {
				
			}
		}
		buffer.flip();
		p.setLength(buffer.limit());
	}

	@Override
	protected void receive(DatagramPacket p) throws IOException {
		IPv6Packet pack=pollNextPacket();
		copyTo(pack, p);
	}
	private volatile Thread parkThread;
	private IPv6Packet peekNextPacket() {
		IPv6Packet pol=null;
		while(true) {
			pol=recvQueue.peek();
			if(pol!=null) {
				recvQueueUsed.addAndGet((int) -pol.getPayload().getTotalLength());
				return pol;
			}
			parkThread=Thread.currentThread();
			LockSupport.parkNanos(1000000L);
		}
	}
	private IPv6Packet pollNextPacket() {
		IPv6Packet pol=null;
		while(true) {
			pol=recvQueue.poll();
			if(pol!=null) {
				recvQueueUsed.addAndGet((int) -pol.getPayload().getTotalLength());
				return pol;
			}
			parkThread=Thread.currentThread();
			LockSupport.parkNanos(1000000L);
		}
	}
	private Queue<IPv6Packet> recvQueue = new ConcurrentLinkedQueue<IPv6Packet>();
	private AtomicInteger recvQueueUsed=new AtomicInteger(0);
	
	@Override
	public boolean onaccept(IPv6Packet packx) throws IOException {
		if(recvQueueUsed.get()<=inputchachesize) {
		if(recvQueue.offer(packx)) {
			recvQueueUsed.addAndGet((int) packx.getPayload().getTotalLength());
			LockSupport.unpark(parkThread);
		}
		}
		return true;
	}

}
