package cs451.net.handler;

import cs451.net.event.Message;
import cs451.net.event.*;
import cs451.parser.Host;
import cs451.parser.Parser;
import cs451.tools.Pair;

import java.util.*;
import java.util.concurrent.*;

/**
 * NetEventHandler for Stuborn Link
 *
 * SendIf: Sends a message again if not Acknowledged
 *
 * Send: Add Message to "Awaiting Acknowledge" queue
 * Deliver: delivers to next layer (Drops ACK messages and removes peer from "Awaiting Acknowledge"
 * Crash: Mark peer as timed out to stop further sends/acknowledgements
 *
 * @author C. Hölzl
 */
public class NetHandlerSL extends NetEventHandlerAbstract {

    private List<Host> hosts;
    Set<Integer> hasTimeout = ConcurrentHashMap.newKeySet();

    PriorityBlockingQueue<Pair<Integer,Message>> sending = new PriorityBlockingQueue<>();

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

    @Override
    public synchronized void broadcastIf(){
        sending.removeIf(ppm -> hasTimeout.contains(ppm.first()));
        sending.forEach(ppm -> hosts.stream().filter(hl->hl.getId()==ppm.first()).findFirst()
                .ifPresent(host -> broadcastNextSync(NetEvent.Message(host, ppm.second()))));
    }

    @Override
    public void broadcast(NetEvent ne) {
        sending.add(new Pair<>(ne.peer.getId(),Message.MSG(ne.message)));
        broadcastNextSync(ne);
    }

    @Override
    public void deliver(NetEvent ne) {
        switch (ne.message.tpe){
            case ACK:
                sending.removeIf(ppm -> (ne.peer.getId()==ppm.first() &&
                        ppm.second().equals(ne.message)));
                break;
            case DATA:
                broadcastNextSync(NetEvent.MessageACK(ne.peer,ne.message));
                deliverNextSync(ne);
                break;
            default:
                break;
        }
    }

    @Override
    public void crash(NetEvent ne) {
        hasTimeout.add(ne.peer.getId());
        crashNextSync(ne);
    }

    @Override
    public void recover(NetEvent ne) {
        hasTimeout.remove(ne.peer.getId());
        recoverNextSync(ne);
    }


    @Override
    public boolean isDone() {
        return sending.isEmpty();
    }

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

}
