package cs451;

import cs451.net.event.Message;
import cs451.net.NetManager;
import cs451.net.event.NetEventType;
import cs451.parser.Coordinator;
import cs451.parser.Host;
import cs451.parser.Parser;
import cs451.tools.Logger;
import cs451.tools.Pair;
import cs451.tools.ParamDetector;

import java.io.FileWriter;
import java.io.IOException;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;

public class Main {

    private static String formatOutput(Integer id, Integer host, Message.TYPE tpe){
        switch (tpe){
            case DATA:
                return "d "+host+" "+id+"\n";
            case ACK:
                return "d "+host+" "+id+"\n";
            case BCST:
                return "b "+id+"\n";
            default:
                return "";
        }
    }

    private static void writeOutput(String filepath) throws IOException {
        FileWriter writer = new FileWriter(filepath);

        for(Pair<Pair<Integer,Integer>, Message.TYPE> mhpt : mh_tpe){
            try {
                writer.write(formatOutput(mhpt.first().first(),mhpt.first().second(),mhpt.second()));
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        writer.close();
    }

    private static void handleSignal() {
        //immediately stop network packet processing
        System.out.println("Immediately stopping network packet processing.");


        System.out.println("Stopping NetManager");
        NetManager.stop();

        System.out.println("Writing output.");
        try {
            writeOutput(parser.output());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private static void initSignalHandlers() {
        Runtime.getRuntime().addShutdownHook(new Thread(Main::handleSignal));
    }

    private static Host me = null;
    private static final Queue<Pair<Pair<Integer,Integer>,Message.TYPE>> mh_tpe = new ConcurrentLinkedQueue<>();

    private static Parser parser = null;


    public static void main(String[] args) throws InterruptedException {
        parser = new Parser(args);
        parser.parse();

        initSignalHandlers();

        // example
        long pid = ProcessHandle.current().pid();
        System.out.println("My PID is " + pid + ".");
        System.out.println("Use 'kill -SIGINT " + pid + " ' or 'kill -SIGTERM " + pid + " ' to stop processing packets.");

        System.out.println("My id is " + parser.myId() + ".");
        System.out.println("List of hosts is:");
        for (Host host : parser.hosts()) {
            if (host.getId() == parser.myId()) {
                me = host;
            }
            System.out.println(host.getId() + ", " + host.getIp() + ", " + host.getPort());
        }

        System.out.println("Barrier: " + parser.barrierIp() + ":" + parser.barrierPort());
        System.out.println("Signal: " + parser.signalIp() + ":" + parser.signalPort());
        System.out.println("Output: " + parser.output());
        // if config is defined; always check before parser.config()
        if (parser.hasConfig()) {
            System.out.println("Config: " + parser.config());
        }
        Coordinator coordinator = new Coordinator(parser.myId(), parser.barrierIp(), parser.barrierPort(), parser.signalIp(), parser.signalPort());

        ParamDetector.detectAndSet(me, parser);
        NetManager.start(me,parser,(t,ne) -> {
            Logger.error(t+"(" + ne.message.src + "):" + ne.message.toString());
            if(t == NetEventType.DLVR){
                Pair<Pair<Integer, Integer>, Message.TYPE> mhpt = new Pair<>(new Pair<>(ne.message.id, ne.message.src), ne.message.tpe);
                System.out.print(formatOutput(mhpt.first().first(),mhpt.first().second(),mhpt.second()));
                mh_tpe.add(mhpt);
            }else if(t== NetEventType.BCST){
                Pair<Pair<Integer, Integer>, Message.TYPE> mhpt = new Pair<>(new Pair<>(ne.message.id, ne.message.src), ne.message.tpe);
                System.out.print(formatOutput(mhpt.first().first(),mhpt.first().second(),mhpt.second()));
                mh_tpe.add(new Pair<>(new Pair<>(ne.message.id,ne.message.src),ne.message.tpe));
            }
        }, (t,ne) -> Logger.error("ERR"+t+" - "+ne.getMessage()));

        System.out.println("Waiting for all processes for finish initialization");
        coordinator.waitOnBarrier();

        System.out.println("Broadcasting messages...");
        NetManager.startBroadcast();

        while(!NetManager.isDone()) {
            Thread.sleep(500);
        }

        System.out.println("Signaling end of broadcasting messages");
        coordinator.finishedBroadcasting();

        while (true) {
            // Sleep for 1 hour
            Thread.sleep(60 * 60 * 1000);
        }
    }
}
