package ac.essex.gp.cluster;

import com.sun.net.httpserver.HttpServer;
import com.sun.net.httpserver.HttpHandler;
import com.sun.net.httpserver.HttpExchange;

import java.net.InetSocketAddress;

import java.io.*;

import ac.essex.gp.problems.Problem;
import ac.essex.gp.Evolve;
import ac.essex.gp.params.GPParams;
import ac.essex.gp.individuals.Individual;
import ac.essex.gp.interfaces.console.ConsoleListener;
import ac.essex.gp.interfaces.graphical.GraphicalListener;
import ac.essex.gp.interfaces.GPActionListener;


/**
 * Starts a basic HTTP Server
 */

public class GPServer {

    public static final int BLOCK_SIZE = 65536;

    public static final int PORT = 8070;

    public static final String APP_NAME = "SXGP Server 1.06";

    protected Problem p = null;

    protected Evolve e = null;

    protected GPActionListener c = null;

    class MyHandler implements HttpHandler {

        public void handle(HttpExchange t) throws IOException {

            try {


            String request = t.getRequestURI().toString();
            listener.onRequest(request);

            if (request.equals("/")) {

                sendOKResponse(t, APP_NAME + " on " + t.getLocalAddress());
                listener.onPing();

                return;

            }

            if (request.equals("/message")) {
                listener.onMessage(readToString(t.getRequestBody()));
                sendOKResponse(t, "OK.");
            }

            if (request.equals("/problemName")) {
                if (p != null)  {
                    sendOKResponse(t, p.getName());
                } else {
                    sendOKResponse(t, "No problem");
                }
            }

            if (request.equals("/problem")) {

                // get the problem
                listener.onServerStatusUpdate("Receiving problem...");

                try {

                    /*readToFile(t.getRequestBody(), problemFile);

                    long start = System.currentTimeMillis();
                    p = Problem.load(problemFile);
                    long time = System.currentTimeMillis() - start;*/

                    long start = System.currentTimeMillis();
                    p = (Problem) readToObject(t.getRequestBody());
                    long time = System.currentTimeMillis() - start;

                    sendOKResponse(t, "Received problem OK. Problem read in " + time + "ms.");

                    listener.onServerStatusUpdate("Received problem OK. Problem instantiated in " + time + "ms.");

                } catch (Exception e) {

                    listener.onServerStatusUpdate("Could not load problem: " + e.toString());
                    sendErrorResponse(t, "Cannot load problem: " + e.toString());
                    e.printStackTrace();

                }

                return;

            }


            if (request.equals("/start")) {

                if (p == null) {

                    sendErrorResponse(t, "Problem file not sent!");

                    return;

                }

                listener.onGPStarted();

                // return which generation it is on
                //c = new ConsoleListener(ConsoleListener.LOW_VERBOSITY);
                GraphicalListener.noInteractiveMessages = true;
                c = new GraphicalListener() {
                    public void onStopped() {
                        super.onStopped();
                        hide();
                    }
                };
                e = new Evolve(p, c);
                e.start();

                sendOKResponse(t, "Working on problem " + p.getName());

                listener.onServerStatusUpdate("Working on " + p.getName());

                return;

            }


            if (request.equals("/status")) {

                // return which generation it is on

                String status;

                if (p == null)  {
                    status = "Waiting for problem";
                } else {
                    if (c == null) {
                        status = "Ready";
                    } else {
                        if (!c.finished) {
                            Individual ind = e.getBestIndividual();
                            if (ind != null) {
                                status = c.getGeneration() + " " + ind.getKozaFitness() + " " + c.getTotalIndividualsEvaluated() + " " + e.getTimeElapsed();
                            } else {
                                status = c.getGeneration() + " " + -1 + " " + c.getTotalIndividualsEvaluated();
                            }
                        } else {
                            System.out.println("GP Finished: HITS:" + e.getBestIndividual().getHits());
                            listener.onGPFinished(e.getTotalEvaluations());
                            c.dispose();
                            listener.onServerStatusUpdate("Ready for next job");
                            status = "Ready. Evolution Finished";
                        }
                    }
                }

                listener.onGPStatusUpdate(status);
                sendOKResponse(t, status);

                return;

            }


            if (request.equals("/solution")) {

                // return the best solution
                if (c != null && c.finished) {
                    
                    listener.onGPStatusUpdate("Finished evolution.");

                    Individual ind = e.getBestIndividual();
                    sendObject(t, ind);
                    //ind.save(file);
                    //sendFile(t, file);

                } else {
                    sendErrorResponse(t, "Evolution not finished yet");
                }

                return;

            }


            if (request.equals("/stop")) {

                // return which generation it is on
                listener.onGPStatusUpdate("Evolution stopped");

                sendOKResponse(t, "Stopped OK");

            }

            } catch (IOException e) {
                listener.onServerError(e.toString());
                e.printStackTrace();
            }

        }


        public void sendOKResponse(HttpExchange t, String response) throws IOException {

            t.sendResponseHeaders(200, response.length());

            OutputStream os = t.getResponseBody();

            os.write(response.getBytes());

            os.close();

        }


        public void sendErrorResponse(HttpExchange t, String response) throws IOException {

            System.err.println(response);

            t.sendResponseHeaders(500, response.length());

            OutputStream os = t.getResponseBody();

            os.write(response.getBytes());

            os.close();

        }


        private String readToString(InputStream is) throws IOException {

            StringBuffer buffer = new StringBuffer();

            BufferedReader rd = new BufferedReader(new InputStreamReader(is));

            String line;

            while ((line = rd.readLine()) != null) {

                buffer.append(line);

            }

            rd.close();

            return buffer.toString();

        }


        private void readToFile(InputStream is, File f) throws IOException {

            BufferedInputStream bis = new BufferedInputStream(is);

            FileOutputStream os = new FileOutputStream(f);

            // Create file
            BufferedOutputStream bos = new BufferedOutputStream(os);

            byte[] buf = new byte[BLOCK_SIZE];

            int read;

            while ((read = bis.read(buf)) > 0) {

                 bos.write(buf, 0, read);

            }

            bos.flush();
            bos.close();

        }

        public Object readToObject(InputStream is) throws IOException, ClassNotFoundException {

            ObjectInputStream ois = new ObjectInputStream(new BufferedInputStream((is)));

            return ois.readObject();

            // don't need to close the stream (the ois does it for us)

        }

        public void sendObject(HttpExchange t, Object o) throws IOException {

            // send the okay header. Don't say how long the response will be
            t.sendResponseHeaders(200, 0);

            ObjectOutputStream os = new ObjectOutputStream(new BufferedOutputStream(t.getResponseBody()));

            os.writeObject(o);

            os.close();

        }


    }


    public File getTempDir() {

        String homeDirectory = System.getProperty("user.home");

        File tempDir = new File(homeDirectory, ".sxGP");

        if (!tempDir.exists()) {

            tempDir.mkdirs();

            //tempDir.deleteOnExit();

        }

        return tempDir;

    }


    HttpServer server;
    GPServerListener listener;

    public GPServer(GPServerListener listener) throws IOException {

        server = HttpServer.create(new InetSocketAddress(PORT), 0);

        this.listener = listener;

        listener.onServerStatusUpdate("Started GP Server on port " + PORT);

        server.createContext("/", new MyHandler());

        server.setExecutor(null); // creates a default executor

        server.start();

    }

    public void stopGP() {
        if (e != null) {
            e.stopFlag = true;
            listener.onGPFinished(e.getTotalEvaluations());
        }
    }

    public void stop() {
        server.stop(0);
    }

}


