package ac.essex.gp.cluster;

import ac.essex.gp.problems.Problem;
import ac.essex.gp.problems.examples.maths.MathsProblem;
import ac.essex.gp.individuals.Individual;

import java.net.URLEncoder;
import java.net.URL;
import java.net.URLConnection;
import java.io.*;
import java.util.StringTokenizer;

/**
 * Client can send problems to the GP server and have them evaluated.
 *
 * @author Olly Oechsle, University of Essex, Date: 04-Aug-2008
 * @version 1.0
 */
public class GPClient {

    public static final int UPDATE_INTERVAL = 5000;

    protected Problem p;
    protected Individual i;
    protected String hostname;
    protected GPClientListener listener;
    protected int id;
    public String name = "Undetermined";

    protected boolean running = false;

    public GPClient(String hostname) {

        this.hostname = hostname;

    }

    public void setID(int id) {
        this.id = id;
    }

    public int getID() {
        return this.id;
    }

    public void setProblem(Problem p) {
        this.p = p;
    }

    public Problem getProblem() {
        return p;
    }

    public void setListener(GPClientListener listener) {
        this.listener = listener;
    }

    public GPClientListener getListener() {
        return listener;
    }

    public String pingServer() throws IOException {
        // ping the server
        return send(hostname, "/", "");
    }

    public boolean isWorking() {
        try {
            name = pingServer();
            return true;
        } catch (Exception e) {
            return false;
        }
    }        

    public Individual getSolution() {
        return i;
    }

    public boolean isRunning() {
        return running;
    }

    public String toString() {
        return hostname + ", alive=" + isWorking() + ", running=" + running;
    }

    public String getHostname() {
        return hostname;
    }

    public void sendMessage(String message)  {
        try {
            send(hostname, "/message", message);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public String getProblemName() {
        try {
            return send(hostname, "/problemName", "");
        } catch (Exception e) {
            e.printStackTrace();
            return "";
        }
    }

    public void start() {
        new Thread() {
            public void run() {
                runClient();
            }
        }.start();
    }

    public void test() throws IOException {
       File temp = new File("/home/ooechs/SuperClassifier");
        final File problemFile = new File(temp, "gpclient1.problem");
        send(hostname, "/problem", problemFile);
    }
    
    public void runClient() {

        if (running) {
            throw new RuntimeException("Cannot run the GP client - it is already running");
        }

        running = true;

        if (p == null) {
            throw new RuntimeException("Cannot run the GP client without a problem set");
        }

        try {

            pingServer();

        } catch (Exception e) {
            listener.onServerError("No response from server " + hostname);
            running = false;
            return;
        }

        try {

            String response;

            // send the problem to the server
            long start = System.currentTimeMillis();
            send(hostname, "/problem", p);
            long time = System.currentTimeMillis() - start;

            send(hostname, "/status", "");

            send(hostname, "/start", "");

            // check for status updates
            while (true) {

                try {
                    Thread.sleep(UPDATE_INTERVAL);

                    //System.err.println("CLIENT: GETTING STATUS");

                    response = send(hostname, "/status", "");

                    System.err.println(response);

                    if (response.indexOf("Finished") > 0) {
                        //System.err.println("CLIENT: FINISHED");
                        i = (Individual) getObject(hostname, "/solution");
                        listener.onFinish(i);
                        break;
                    } else {

                        // split up the status

                        try {
                            StringTokenizer t = new StringTokenizer(response);
                            int generations = Integer.parseInt(t.nextToken());
                            double fitness = Double.parseDouble(t.nextToken());
                            int evaluations = Integer.parseInt(t.nextToken());
                            long timeElapsed = Long.parseLong(t.nextToken());
                            //System.err.println("CLIENT: GOT STATUS");
                            listener.onStatusUpdate(generations, fitness, evaluations, (int) (timeElapsed / 1000));
                        } catch (Exception e) {
                            //System.err.println("CLIENT: ERROR: " + e.toString());
                            e.printStackTrace();
                        }
                    }

                } catch (Exception e) {
                    //System.err.println("CLIENT: " + e.toString());
                    listener.onServerError("Error while getting status: " + e.toString());
                    break;
                }
            }

            running = false;

        } catch (Exception e) {
            e.printStackTrace();
            //System.err.println("CLIENT: ERROR: " + e.toString());
            listener.onServerError("Generic Error: " + e.toString());
            running = false;
        }

    }

    

    public String send(String hostname, String URI, Object obj) throws IOException {

        long start = System.currentTimeMillis();

        URL url = new URL("http://" + hostname + ":" + GPServer.PORT + URI);

        URLConnection conn = url.openConnection();

        conn.setDoOutput(true);
        conn.setDoInput(true);

        ObjectOutputStream oos = new ObjectOutputStream(new BufferedOutputStream(conn.getOutputStream()));

        oos.writeObject(obj);

        oos.close();

        String response = getResponse(conn);
        long end = System.currentTimeMillis();
        long time = end - start;

        return response;

    }

    public String send(String hostname, String URI, File f) throws IOException {

            // Send data

        long start = System.currentTimeMillis();
            URL url = new URL("http://" + hostname + ":" + GPServer.PORT + URI);

            URLConnection conn = url.openConnection();

            conn.setDoOutput(true);

            DataOutputStream s = new DataOutputStream(conn.getOutputStream());

            BufferedOutputStream bs = new BufferedOutputStream(s);

            FileInputStream is = new FileInputStream(f);

            BufferedInputStream bis = new BufferedInputStream(is);

            byte[] buf = new byte[GPServer.BLOCK_SIZE];

            int read;

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

                bs.write(buf, 0, read);

            }

            bs.flush();

            bs.close();

            // Get the response
            String response = getResponse(conn);
        long end = System.currentTimeMillis();
        long time = end - start;

        return response;

    }

    public String getResponse(URLConnection conn) throws IOException {

        StringBuffer buffer = new StringBuffer();

        BufferedReader rd = new BufferedReader(new InputStreamReader(conn.getInputStream()));

        String line;

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

            buffer.append(line);

        }

        rd.close();

        return buffer.toString();

    }

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

            // Create file

            FileOutputStream fos = new FileOutputStream(f);

            byte[] buf = new byte[GPServer.BLOCK_SIZE];

            int read = 0;

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

                fos.write(buf, 0, read);

            }

            fos.flush();
            fos.close();

        }

    public Object getObject(String hostname, String request) {

        try {

            // Send data

            URL url = new URL("http://" + hostname + ":" + GPServer.PORT + request);

            URLConnection conn = url.openConnection();

            conn.setDoOutput(false);
            conn.setDoInput(true);

            // Get the response
            ObjectInputStream ois = new ObjectInputStream(new BufferedInputStream(conn.getInputStream()));

            return ois.readObject();

        } catch (Exception e) {
            e.printStackTrace();
            listener.onServerError("Cannot get object from " + hostname);
            return null;
        }

    }

    private void getFile(String hostname, String URI, File f) {

        try {

            // Send data

            URL url = new URL("http://" + hostname + ":" + GPServer.PORT + URI);

            URLConnection conn = url.openConnection();

            conn.setDoOutput(false);
            conn.setDoInput(true);

            // Get the response
            readToFile(conn.getInputStream(), f);

        } catch (Exception e) {
            e.printStackTrace();
        }

    }


    public String send(String hostname, String URI, String data) throws IOException {

            // Send data

            URL url = new URL("http://" + hostname + ":" + GPServer.PORT + URI);

            URLConnection conn = url.openConnection();

            conn.setDoOutput(true);

            OutputStreamWriter wr = new OutputStreamWriter(conn.getOutputStream());

            wr.write(data);

            wr.flush();

            wr.close();

            // Get the response
            return getResponse(conn);



    }


    public static void main(String[] args) throws IOException {

      GPClientListener l = new GPClientListener() {

          public void onServerError(String message) {
              System.err.println(message);
          }

          public void onStatusUpdate(int generation, double fitness, int evaluations, int timeElapsed) {
              System.out.println("G=" + generation);
          }

          public void onFinish(Individual i) {
              System.out.println("Found solution: " + i.getHits());
              System.out.println(i.toJava());
          }

      };

      GPClient c = new GPClient("localhost");
      //GPClient c = new GPClient("155.245.65.123");
      c.setProblem(new MathsProblem());
      c.setListener(l);
      c.runClient();
      //*/

        //GPClient c = new GPClient("155.245.65.123");
 /*       GPClient c = new GPClient("localhost");
        c.test();*/

    }

    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        GPClient gpClient = (GPClient) o;

        if (hostname != null ? !hostname.equals(gpClient.hostname) : gpClient.hostname != null) return false;

        return true;
    }

    public int hashCode() {
        return (hostname != null ? hostname.hashCode() : 0);
    }
}

