package cs451.net.handler;

import cs451.net.event.NetEvent;
import cs451.parser.Host;
import cs451.parser.Parser;
import cs451.tools.Pair;

import java.util.Map;
import java.util.Set;
import java.util.concurrent.*;

/**
 * NetEventHandler for Uniform Reliable Broadcast
 *
 * DeliverIf: pending has been acked by all correct processes (and has not been delivered)
 *
 * Send: Add to pending and send
 * Deliver: Ack and Add to pending
 * Crash: Remove host from correct processes
 *
 * @author C. Hölzl
 */
public class NetHandlerURB extends NetEventHandlerAbstract {


    private final Set<Integer> correct = ConcurrentHashMap.newKeySet();
    private final Map<Pair<Integer,Integer>, Set<Integer>> ack = new ConcurrentHashMap<>();
    private final Set<Pair<Integer,Integer>> delivered = ConcurrentHashMap.newKeySet();
    private final Set<Pair<Integer,Integer>> pending = ConcurrentHashMap.newKeySet();

    private Integer myId;


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

    @Override
    public synchronized void deliverIf(){
        pending.stream().filter(smp -> ack.getOrDefault(smp,ConcurrentHashMap.newKeySet()).containsAll(correct)).
                filter(delivered::add).
                forEach(smp -> deliverNextAsync(NetEvent.Message(smp.first(),smp.second())));
    }

    @Override
    public void broadcast(NetEvent ne) {
        pending.add(new Pair<>(myId, ne.message.id));
        ne.message.src = myId;
        broadcastNextSync(ne);
    }

    @Override
    public void deliver(NetEvent ne) {
        Pair<Integer,Integer> smp = new Pair<>(ne.message.src,ne.message.id);
        ack.compute(smp,(k, v) -> {
            if(v == null){
                v = ConcurrentHashMap.newKeySet();
            }
            v.add(ne.peer.getId());
            return v;
        });
        if(pending.add(smp)){
            broadcastNextAsync(ne);
        }
        deliverIf();
    }

    @Override
    public void crash(NetEvent ne) {
        correct.remove(ne.peer.getId());
        deliverIf();
    }

    @Override
    public boolean isDone() {
        return delivered.containsAll(pending);
    }

    @Override
    public void start(Host h, Parser p) {
        super.start(h,p);
        p.hosts().forEach(ch-> correct.add(ch.getId()));
        myId = h.getId();
    }
}