package cs451.net.handler;

import cs451.net.event.Message;
import cs451.net.NetManager;
import cs451.net.event.*;
import cs451.parser.Host;
import cs451.parser.Parser;

import java.io.IOException;
import java.net.*;
import java.nio.ByteBuffer;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * NetEventHandler for Socket
 *
 * Send: Sends Message through socket
 * Deliver: Read Message from socket (OWN LOOP)
 *
 * @author C. Hölzl
 */
public class NetHandlerSCKT extends NetEventHandlerAbstract {

    private static final Integer BUFF_SIZE = 128;

    private static final ExecutorService tex = Executors.newSingleThreadExecutor();

    private List<Host> hosts;

    private DatagramSocket socket = null;


    public NetHandlerSCKT(Class<? extends NetEventHandlerAbstract> deliverLayer, Class<? extends NetEventHandlerAbstract> broadcastLayer) {
        super(deliverLayer,broadcastLayer);
    }

    @Override
    public void broadcast(NetEvent event) {
        if(socket.isClosed()){
            NetManager.error(NetEventType.BCST,new SocketException("Socket Closed"));
            return;
        }
        ByteBuffer b = ByteBuffer.allocate(BUFF_SIZE);
        event.message.ToBuffer(b);
        DatagramPacket datagram = new DatagramPacket(b.array(), b.position(), event.peer.getAddr());
        try {
            socket.send(datagram);
        } catch (IOException e) {
            NetManager.error(NetEventType.BCST,e);
        }
    }

    @Override
    public void deliver(NetEvent event) {
        if(socket.isClosed()){
            NetManager.error(NetEventType.DLVR,new SocketException("Socket Closed"));
            return;
        }

        byte[] buff = new byte[BUFF_SIZE];
        DatagramPacket datagram = new DatagramPacket(buff, buff.length);
        try {
            socket.receive(datagram);
            Optional<Integer> rhid = hosts.stream().filter(h -> h.getAddr().equals(datagram.getSocketAddress())).map(Host::getId).findFirst();

            deliverNextAsync(NetEvent.Message(
                            new Host((InetSocketAddress) datagram.getSocketAddress(), rhid.orElse(-1)),
                            Message.FromBuffer(ByteBuffer.wrap(datagram.getData()))
            ));
        } catch (IOException e) {
            NetManager.error(NetEventType.DLVR,e);
        }
    }


    @Override
    public void start(Host h, Parser p) {
        super.start(h,p);
        hosts = p.hosts();

        try {
            this.socket = new DatagramSocket(h.getPort());
        } catch (SocketException e) {
            throw new Error(e);
        }

        tex.execute(()->{
            while (active.get()) {
                deliver(NetEvent.EMPTY());
            }
        });
    }

    @Override
    public void stop() {
        super.stop();
        tex.shutdown();
        socket.close();
    }
}
