package org.kne.cloud.network.ipv6;

import java.io.Closeable;
import java.io.IOException;
import java.net.InetAddress;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.LockSupport;
import java.util.function.BiConsumer;
import java.util.function.Supplier;

import org.kne.cloud.network.NetworkPacket;
import org.kne.cloud.network.ipv6.IPv6Packet.IPv6HopByHopHeader;
import org.kne.cloud.network.ipv6.IPv6Packet.IPv6SegmentRoutingHeader;
import org.kne.cloud.network.klalb.CONST;
import org.kne.cloud.network.monitor.SpeedAndTrafficMonitorDataImpl;
import org.kne.cloud.network.srv6.DetNetSRv6TLV;
import org.kne.cloud.network.srv6.IPv6SegmentRoutingTLV;
import org.kne.cloud.network.srv6.SRv6Router;
import org.kne.cloud.network.tun.InetAddressRow;
import org.kne.cloud.network.tun.TUNChannel;
import org.kne.cloud.network.tun.TUNNetworkDevice;
import org.kne.concurrent.SpinLock;
import org.kne.io.KNEChannels;

public class IPv6TUNLoopbackNetworkLink extends AbstractIPv6NetworkLink
		implements IPv6NetworkLink, Closeable, AutoCloseable {


	private TUNNetworkDevice tun;

	private IPv6AddressGroup hostAddress;

	private Thread tr;

	private Lock slok = new SpinLock();

	public IPv6TUNLoopbackNetworkLink(String name,IPv6AddressGroup hostAddress, int mtu, List<InetAddress> dns) throws IOException {
		if (tun != null)
			throw new IllegalStateException("already open!");
		try {
			tun = TUNNetworkDevice.createDevice(name, CONST.KLALB_DECENTRALIZED_S_RV6_NETWORK);
			this.hostAddress = hostAddress;
			if (hostAddress != null) {
				List<InetAddressRow>rows=new ArrayList<>();
				rows.add(new InetAddressRow(hostAddress.getAddress().toInet6Address(), hostAddress.getPrefixLength()));
				tun.setIPAddress(rows);
			}
			if (dns != null) {
				tun.setDNSAddress(dns);
			}
			try {
				tun.setMTU(mtu);
			} catch (IOException e) {
				e.printStackTrace();
			}
			
			TUNChannel channel= tun.openSession();
			Thread tb = new Thread(() -> {
				while (true) {
					ByteBuffer tmp = NetworkPacket.bufferAllocator.allocateNative(65535);
					try {
						channel.read(tmp);
						tmp.flip();
						// System.out.println(tmp);
						if (IPv6Packet.getIPVersion(tmp.get(0)) == 6) {

							try {
								IPv6Packet ipp = new IPv6Packet();
								ipp.readFromChannel(KNEChannels.newReadableChannel(tmp), mtu);

								// NetworkPacket.databufferpool_65535.back(tmp);
								BiConsumer<IPv6NetworkLink, Supplier<IPv6Packet>> con = super.getReceiveConsumer();
								if (con != null) {
									if (ipp.getPayload().getProtocolNumber() == 6) {
										SRv6Router router = getSrv6Router();

										if (router != null) {
											router.insertHopByHopHeader(ipp);
										}
									}

									con.accept(IPv6TUNLoopbackNetworkLink.this, () -> {
										UUID pid = ipp.getPacketID();
										if (pid != null) {
											monitor.getUploadBandwidth().recordPacket(pid,1, (int) ipp.getTotalLength());
										}
										// ipp.setDisposeAfterSend(true);
										// ipp.getPayload().setDisposeAfterSend(true);
										return ipp;
									});
								} else {
									// ipp.dispose();
								}
							} catch (IOException e) {
								e.printStackTrace();
							}

						} else {
							// NetworkPacket.databufferpool_65535.back(tmp);
						}
					} catch (Exception e) {
						// NetworkPacket.databufferpool_65535.back(tmp);
						e.printStackTrace();
					}
				}
			});
			tb.setPriority(Thread.MAX_PRIORITY - 1);
			tb.start();
			tr = new Thread(() -> {
				while (true) {
					ByteBuffer tmp = sendQueue.poll();
					if (tmp != null) {

						slok.lock();
						try {
							channel.write(tmp);
						} catch (IOException e) {
							e.printStackTrace();
						} finally {
							slok.unlock();
							// NetworkPacket.databufferpool_65535.back(tmp);
						}
					} else {
						
						LockSupport.parkNanos(1000000L);
					}

				}
			});
			tr.setPriority(Thread.MAX_PRIORITY - 1);
			tr.start();

		} catch (UnsatisfiedLinkError e) {
			throw new IOException(e);
		}
		onOnlineStateUpdate();
	}

	private LinkedBlockingQueue<ByteBuffer> sendQueue = new LinkedBlockingQueue<ByteBuffer>();

	private SpeedAndTrafficMonitorDataImpl monitor;

	@Override
	public void sendPacket(IPv6Packet pack, IPv6Address next) throws IOException {
		if (tun != null) {

			try {

				IPv6HopByHopHeader hbh = pack.getHopByHopHeader();
				KLALBOAMHopByHopTLV oamtlv = null;
				if (hbh != null) {
					List<IPv6HopByHopTLV> ll = hbh.getTlvs();

					for (int i = 0; i < ll.size(); i++) {
						IPv6HopByHopTLV tlv = ll.get(i);
						if (tlv instanceof KLALBOAMHopByHopTLV) {
							oamtlv = (KLALBOAMHopByHopTLV) tlv;
							break;
						}
					}
				}

				IPv6SegmentRoutingHeader srh = pack.getSegmentRoutingHeader();
				DetNetSRv6TLV seqs = null;
				if (srh != null) {
					List<IPv6SegmentRoutingTLV> l = srh.getTlvs();

					for (int i = 0; i < l.size(); i++) {
						IPv6SegmentRoutingTLV tlv = l.get(i);
						if (tlv instanceof DetNetSRv6TLV) {
							seqs = (DetNetSRv6TLV) tlv;
							break;
						}
					}
				}

				sendPacket0(pack, oamtlv, seqs);

			} catch (IOException e) {
				e.printStackTrace();
			}

		} else {
		}
	}

	private void sendPacket0(IPv6Packet pack, KLALBOAMHopByHopTLV oamtlv, DetNetSRv6TLV detNet) throws IOException {

		
			ByteBuffer tmp = NetworkPacket.bufferAllocator.allocate(65535);
			pack.writeToChannel(KNEChannels.newWritableChannel(tmp));
			UUID pid = pack.getPacketID();
			if (pid != null) {
				monitor.getUploadBandwidth().recordPacket(pid, 1,(int) pack.getTotalLength());
			}
			tmp.flip();
			sendQueue.add(tmp);
			LockSupport.unpark(tr);
	}

	

	@Override
	public boolean isLoopBack() {
		return true;
	}

	@Override
	public List<Neighbor> getNeighborsInfo() {
		List<Neighbor> hs = new ArrayList<>();
		// if (hostAddress != null)
		// hs.put(hostAddress, null);
		return hs;
	}

	/*
	 * @Override public boolean isCongress(IPv6Packet iPv6Packet,double scale) {
	 * return sendQueue.size() > 1000*scale; }
	 */

	@Override
	public String getName() {
		return "tunLoopBack";
	}

	@Override
	public boolean isUp() {
		return tun.isOpen();
	}

	public void close() throws IOException {
		if (tun != null) {
			tun.close();
			tun = null;
		}
		onOnlineStateUpdate();
	}

	public void setMonitor(SpeedAndTrafficMonitorDataImpl monitor) {
		this.monitor = monitor;
	}

	public SpeedAndTrafficMonitorDataImpl getMonitor() {
		return monitor;
	}

	@Override
	public List<IPv6AddressGroup> getAddressGroups() {
		return List.of(hostAddress);
	}

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

		for (IPv6AddressGroup grp : getAddressGroups()) {
			rlist.add(new RouteItem(new IPv6AddressGroup(grp.getAddress(), 128), grp.getAddress(), this, "Direct", 0, 1,
					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, null, "D", false);
			rlist.add(ri);

			RouteItem ris = new RouteItem(addresses.getLocator(), addresses.getLocator().getAddress(), this,
					"KLALB SRv6", 13, 128, null, "D", false);
			rlist.add(ris);

		}
		return rlist;
	}

}
