package the8472.mldht.indexing;

import java.io.IOException;
import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.StandardProtocolFamily;
import java.net.StandardSocketOptions;
import java.nio.ByteBuffer;
import java.nio.channels.DatagramChannel;
import java.util.Collection;
import java.util.concurrent.LinkedTransferQueue;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TransferQueue;

import lbms.plugins.mldht.kad.DHT;
import lbms.plugins.mldht.kad.messages.GetPeersRequest;
import lbms.plugins.mldht.kad.messages.MessageBase;
import lbms.plugins.mldht.kad.messages.MessageBase.Method;
import the8472.mldht.Component;
import the8472.utils.ConfigReader;

public class OpentrackerLiveSync implements Component {
	
	TransferQueue<ByteBuffer> toSend = new LinkedTransferQueue<>();
	DatagramChannel channel;
	
	byte[] id = new byte[4];
	Thread t = new Thread(this::send);
	volatile boolean running = true;
	
	private static final int HEADER_LENGTH = 0x08;
	private static final int PEER_LENGTH = 0x1C;
	private static final int PEERS_PER_PACKET = 50;
	
	public OpentrackerLiveSync() {
		ThreadLocalRandom.current().nextBytes(id);
	}
	
	@Override
	public void start(Collection<DHT> dhts, ConfigReader config) {
		try {
			channel = DatagramChannel.open(StandardProtocolFamily.INET);
			channel.setOption(StandardSocketOptions.IP_MULTICAST_TTL, 1);
			channel.setOption(StandardSocketOptions.SO_REUSEADDR, true);
			// we only need to send, not to receive, so need to bind to a specific port
			channel.bind(new InetSocketAddress(0));
			channel.connect(new InetSocketAddress(InetAddress.getByAddress(new byte[] {(byte) 224,0,23,5}), 9696));
		} catch (IOException e) {
			e.printStackTrace();
			return;
		}
		
		t.setDaemon(true);
		t.setName("opentracker-sync");
		t.start();
		
		// OT-sync only supports ipv4 atm
		dhts.stream().filter(d -> d.getType().PREFERRED_ADDRESS_TYPE == Inet4Address.class).forEach(d -> {
			d.addIncomingMessageListener(this::incomingPacket);
		});

	}
	
	void incomingPacket(DHT dht, MessageBase msg) {
		if(!running)
			return;
		if(msg.getType() != MessageBase.Type.REQ_MSG || msg.getMethod() != Method.GET_PEERS)
			return;
		
		GetPeersRequest req = (GetPeersRequest) msg;
		
		ByteBuffer buf = ByteBuffer.allocate(PEER_LENGTH);
		
		buf.put(req.getTarget().getHash());
		buf.put(req.getOrigin().getAddress().getAddress());
		buf.putShort((short) req.getOrigin().getPort());
		buf.putShort((short) 0);
		buf.flip();
		toSend.add(buf);
	}
	
	
	
	void send() {
		ByteBuffer sendBuffer = ByteBuffer.allocate(HEADER_LENGTH);
		sendBuffer.put(id);
		sendBuffer.put(new byte[4]);
		sendBuffer.flip();
		
		ByteBuffer[] buffers = new ByteBuffer[1 + PEERS_PER_PACKET];
		buffers[0] = sendBuffer;

		try {
			while(running) {
				for(int i = 1;i<buffers.length;i++) {
					buffers[i] = toSend.take();
				}
				
				channel.write(buffers);
				
				buffers[0].rewind();
				
			}

		} catch (IOException | InterruptedException e) {
			running = false;
			e.printStackTrace();
		}
		
				
	}

	@Override
	public void stop() {
		running = false;
		try {
			channel.close();
		} catch (IOException e) {
			e.printStackTrace();
		}

	}

}


// === OT format
// [mcast address seems to be outdated]

/*
Syncing is done as udp packets in the multicast domain 224.0.42.5 port 9696

Each tracker should join the multicast group and send its live sync packets
to that group, using a ttl of 1

Format of all sync packets is straight forward, packet type determines
which kind of packet this is:

  0x0000 0x04 id of tracker instance
  0x0004 0x04 packet type

                           ########
######## PEER SYNC PROTOCOL ########
########

Each tracker instance accumulates announce requests until its buffer is
full or a timeout is reached. Then it broadcasts its live sync packer:

packet type SYNC_LIVE
[ 0x0008 0x14 info_hash
  0x001c 0x04 peer's ipv4 address
  0x0020 0x02 peer's port
  0x0024 0x02 peer flags v1 ( SEEDING = 0x80, COMPLETE = 0x40, STOPPED = 0x20 )
]*

*/
support export to opentracker through its livesync feature