package meerkat.pollingstation;

import com.google.common.util.concurrent.FutureCallback;
import meerkat.pollingstation.controller.callbacks.ScanCallback;
import meerkat.pollingstation.controller.callbacks.ScanDataCallback;
import meerkat.pollingstation.controller.callbacks.ScanErrorCallback;
import meerkat.pollingstation.controller.commands.PollingStationCommand;
import meerkat.pollingstation.controller.commands.ReceivedScanCommand;
import meerkat.protobuf.PollingStation;
import meerkat.rest.ProtobufMessageBodyReader;
import meerkat.rest.ProtobufMessageBodyWriter;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.glassfish.jersey.server.ResourceConfig;
import org.glassfish.jersey.servlet.ServletContainer;

import java.nio.ByteBuffer;
import java.time.Instant;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.LinkedBlockingQueue;

/**
 * Created by Laura on 3/20/2017.
 */
public class ReceiverScanHandler implements PollingStationScanner.Consumer, Runnable {

    public final static String CALLBACKS_ATTRIBUTE_NAME = "controller";

    private final Server server;
    private final List<FutureCallback<PollingStation.ScannedData>> callbacks;

    private LinkedBlockingQueue<ScanCallback> queue;
    private LinkedBlockingQueue<PollingStationCommand> controllerQueue;
    private volatile boolean shutDownHasBeenCalled;

    public ReceiverScanHandler(int port, String subAddress) {

        callbacks = new LinkedList<>();

        server = new Server(port);

        queue = new LinkedBlockingQueue<>();
        shutDownHasBeenCalled = false;

        ServletContextHandler servletContextHandler = new ServletContextHandler(server, subAddress);
        servletContextHandler.setAttribute(CALLBACKS_ATTRIBUTE_NAME, (LinkedBlockingQueue<ScanCallback>) queue);


        ResourceConfig resourceConfig = new ResourceConfig(ReceiverWebAPI.class);
        resourceConfig.register(ProtobufMessageBodyReader.class);
        resourceConfig.register(ProtobufMessageBodyWriter.class);

        ServletHolder servletHolder = new ServletHolder(new ServletContainer(resourceConfig));

        servletContextHandler.addServlet(servletHolder, "/*");


    }

    public void setControllerQueue(LinkedBlockingQueue<PollingStationCommand> q) {
        this.controllerQueue = q;
    }

    private boolean wasShutDownCalled () {
        return shutDownHasBeenCalled;
    }

    @Override
    public void run() {
        try {
            start();
        } catch (Exception e) {
            e.printStackTrace();
            System.err.println("Could not start server: " + e.getMessage());
        }
    }

    @Override
    public PollingStation.ConnectionServerData start(boolean verifyScanner) throws Exception {
        server.start();
        while (! wasShutDownCalled()) {
            try {
                handleSingleCallback(queue.take());
            }
            catch (InterruptedException e) {
                System.err.println("Interrupted while reading from command queue " + e);
            }
        }
    }

    @Override
    public void stop() throws Exception {
        server.stop();
    }

    @Override
    public void subscribe(FutureCallback<PollingStation.ScannedData> scanCallback) {
        callbacks.add(scanCallback);
    }


    private void handleSingleCallback(ScanCallback callback) {
        if (callback instanceof ScanDataCallback) {
            doScannedData((ScanDataCallback) callback);
        }
        else if (callback instanceof ScanErrorCallback) {
            doScanError((ScanErrorCallback) callback);
        }
        else {
            System.out.println("in the else");
//            logger.error("handleSingleCommand: unknown type of PollingStationCommand received: " + command.getClass().getName());
//            doReportErrorAndForceRestart(systemMessages.get(StorageManager.SOMETHING_WRONG_MESSAGE));
//            doReportErrorAndForceRestart("error message to define");
        }
    }

    // TODO: 4/27/2017 set requestID properly
    private void doScannedData(ScanDataCallback cb) {
        PollingStation.ScannedData scan = cb.getScannedData();
//        String channel = scan.getChannel().toStringUtf8();
        int requestID = 1;
        long serialNumber = scan.getSignedEncryptedBallot().getEncryptedBallot().getSerialNumber();
        controllerQueue.add(new ReceivedScanCommand(requestID, serialNumber));
    }

    private void doScanError(ScanErrorCallback cb) {

    }

}