package org.kne.cloud.network.srv6;

import java.io.IOException;
import java.lang.ref.Cleaner;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.function.BiConsumer;
import java.util.function.Consumer;

import org.kne.cloud.network.MultiProtocolSocketAddress;
import org.kne.cloud.network.congestion.NOCongestionAlgorithm;
import org.kne.cloud.network.congestion.SendPacketSlidingWindow;
import org.kne.opencl64.Releaser;

public class KLALBRoutingProtocolAPIClient {
	private KLALBRoutingProtocol routingProtocol;
	private SendPacketSlidingWindow<UUID, JsonDataPacket> window = new SendPacketSlidingWindow<UUID, JsonDataPacket>(
			new NOCongestionAlgorithm(3000000000L), 1024 * 1024);

	private static final Cleaner clr = Cleaner.create();

	private BiConsumer<SocketAddress, JsonDataPacket> rec = (addr, data) -> {
		KLALBRoutingProtocolJsonData dataobj = data.getDecodedData();
		InetSocketAddress addrs = (InetSocketAddress) addr;
		UUID ruid = dataobj.getUuid();
		JsonDataPacket relate = null;
		//System.out.println(ruid + " " + window.getSendmap());
		switch (dataobj.getType()) {
		case KLALBRoutingProtocolJsonData.OPEN_LINES_RESP:

			if ((relate = window.ack(ruid)) != null) {
				List<?> connects = (List<?>) dataobj.getData();
				List<MultiProtocolSocketAddress> connectsm = new ArrayList<MultiProtocolSocketAddress>(connects.size());
				for (Object open : connects) {
					if (open instanceof MultiProtocolSocketAddress) {
						connectsm.add((MultiProtocolSocketAddress) open);
					} else {
						connectsm.add(new MultiProtocolSocketAddress((String) open));

					}
				}
				((Consumer<List<MultiProtocolSocketAddress>>) relate.getUserCallback()).accept(connectsm);
			}
			break;
		}

	};

	public KLALBRoutingProtocolAPIClient(KLALBRoutingProtocol routingProtocol) {
		this.routingProtocol = routingProtocol;
		routingProtocol.addReceiver(rec);
		this.releaser = new KLALBRoutingProtocolAPIClientReleaser(this.routingProtocol, rec, window);
		clr.register(this, releaser);
	}

	public void requestOpenLines(SocketAddress addr, Consumer<List<MultiProtocolSocketAddress>> callback)
			throws IOException {
		UUID suid = UUID.randomUUID();
		KLALBRoutingProtocolJsonData json = new KLALBRoutingProtocolJsonData(
				KLALBRoutingProtocolJsonData.OPEN_LINES_REQ, suid, null);
		JsonDataPacket packet = new JsonDataPacket(json);
		packet.setUserCallback(callback);
		window.put(suid, packet);
		routingProtocol.sendJsonPacketToAddress(packet, addr);

	}

	public KLALBRoutingProtocol getRoutingProtocol() {
		return routingProtocol;
	}

	private KLALBRoutingProtocolAPIClientReleaser releaser;

	public void close() {
		releaser.run();
	}

	public boolean isClosed() {
		return releaser.isReleased();
	}
}

class KLALBRoutingProtocolAPIClientReleaser extends Releaser<BiConsumer<SocketAddress, JsonDataPacket>> {

	private KLALBRoutingProtocol routingProtocol;
	private SendPacketSlidingWindow<UUID, JsonDataPacket> window;

	public KLALBRoutingProtocolAPIClientReleaser(KLALBRoutingProtocol routingProtocol,
			BiConsumer<SocketAddress, JsonDataPacket> resource, SendPacketSlidingWindow<UUID, JsonDataPacket> window) {
		super(resource);
		this.routingProtocol = routingProtocol;
		this.window = window;
	}

	@Override
	protected void release(BiConsumer<SocketAddress, JsonDataPacket> resource) {
		window.close();
		routingProtocol.removeReceiver(resource);
	}
}