package ca.waterloo.dsg.graphflow.client.httpserver;

import ca.waterloo.dsg.graphflow.client.GraphflowClient;
import ca.waterloo.dsg.graphflow.query.result.Message;
import ca.waterloo.dsg.graphflow.server.ServerQueryString.ReturnType;
import com.sun.net.httpserver.Headers;
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;
import com.sun.net.httpserver.HttpServer;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.nio.charset.StandardCharsets;
import java.util.Scanner;

/**
 * Http server to handle queries from Graphflow web UI, which communicates with the
 * {@code GraphflowServer} using gRPC.
 */
public class PlanViewerHttpServer extends GraphflowClient implements Runnable {

    private static final Logger logger = LogManager.getLogger(PlanViewerHttpServer.class);
    private static final String PLAN_VIEWER_HTML_PATH = "src/main/ui/index.html";
    private static final String HTTP_HOST = "localhost";
    private static final int HTTP_PORT = 8000;
    private Thread thread;

    /**
     * Constructs a client of {@code GraphflowServer} connecting at {@code host:port}.
     *
     * @param grpcHost The gRPC server hostname.
     * @param grpcPort The gRPC server port.
     */
    public PlanViewerHttpServer(String grpcHost, int grpcPort) {
        super(grpcHost, grpcPort);
    }

    /**
     * Starts the http server for Graphflow web UI.
     */
    @Override
    public void run() {
        try {
            HttpServer server = HttpServer.create(new InetSocketAddress(HTTP_HOST, HTTP_PORT), 0);
            // Create a route for input query
            server.createContext("/query", new PlanViewerHttpHandler());
            // Create a default executor
            server.setExecutor(null);
            server.start();
            File webViewer = new File(PLAN_VIEWER_HTML_PATH);
            logger.info("Please open the Graphflow UI (link below) in a browser:");
            logger.info("file://" + webViewer.getAbsolutePath());
        } catch (IOException exception) {
            logger.error("GraphflowUIHttpServer: failed to start");
        }
    }

    /**
     * Starts a new thread for Graphflow web UI.
     */
    public void start() {
        if (null == thread) {
            thread = new Thread(this);
            thread.start();
        }
    }

    /**
     * Handler class to receive input query from Graphflow web UI
     */
    public class PlanViewerHttpHandler implements HttpHandler {

        @Override
        public void handle(HttpExchange exchange) throws IOException {
            InputStream inputStream = exchange.getRequestBody();
            String query;
            String response;
            try (Scanner scanner = new Scanner(inputStream, StandardCharsets.UTF_8.name())) {
                query = scanner.useDelimiter("\\A").next();
                logger.info("Query: " + query);
                inputStream.close();
                response = queryServer(query, ReturnType.JSON);
            } catch (Exception e) {
                logger.info("ERROR", e);
                response = new Message("ERROR: " + e.getMessage(), true).toJson().toString();
            }

            // Add the header to avoid error:
            // “No 'Access-Control-Allow-Origin' header is present on the requested resource”
            Headers responseHeaders = exchange.getResponseHeaders();
            responseHeaders.add("Access-Control-Allow-Origin", "*");
            exchange.sendResponseHeaders(200, response.length());

            OutputStream outputStream = exchange.getResponseBody();
            outputStream.write(response.getBytes());
            outputStream.close();
        }
    }
}