
package gui;

import java.util.Random;
import java.io.IOException;
import java.io.Serializable;

import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;

import javax.swing.Timer;
//import javax.swing.SwingUtilities;

import comm.ExecutableServer;
import thread.ThreadPool;
import comm.ChannelFactory;
import comm.SocketChannel;
import comm.RemoteExecutable;
import algo.Point;
import algo.Graph;
import algo.Path;
import algo.PathByteArray;
import algo.PlaneGraph;
import algo.TSPInf;
import algo.DistTSP;
import algo.ParByteLocalTSP;
import algo.SeqByteTSP;

/**
 * Model class to be viewed and controled by the GUI.
 * @author Heinz Kredel.
 */
public class TSPguiModel  {

    protected String algorithm = "sequential";
    protected int algo = 0;
    protected String server = "localhost";
    protected int port = 9000;
    protected boolean standAlone = false;
    protected ExecutableServer es = null;


    protected int probSize;
    protected Point[] points = null;
    protected Graph graph = null;
    protected Point[] scaledPoints = null;
    protected boolean isBestPath = false;
    protected Path bestPath = null;
    protected Path actualBestPath = null;
    protected long iter;
    protected long maxIter;
    protected int numThread;
    protected double iterPercent;

    protected boolean done;
    private TSPModelConnectThread thread = null;
    private Timer wecker = null;
    private TSPguiUpdate updater = null;
    
    public TSPguiModel() {
        probSize = 12;
        done = false;
        iter = 0l;
        maxIter = Long.MAX_VALUE;
        numThread = 1;
        iterPercent = 0.0;
    }

    public void generateProblem() {
        assureStopped();
        points = new Point[probSize];
        double m = 100;
        Random r = new Random();
        double mx = 0.0;
        for ( int i = 0; i < probSize; i++ ) {
            Point p = Point.random(i,r,m);
            points[i] = p;
            if ( p.x > mx ) {
                   mx = p.x;
            }
            if ( p.y > mx ) {
                   mx = p.y;
             }
         }
        graph = new PlaneGraph(points);
        scaledPoints = new Point[probSize];
        double scale = 1.0 / mx;
        for ( int i = 0; i < probSize; i++ ) {
            scaledPoints[i] = Point.scaleTo(points[i],scale);
        }
        iter = 0l;
        bestPath = PathByteArray.standardPath(graph);
        actualBestPath = bestPath;
        isBestPath = false;
        doStatus();
    }

    public void solveProblem() {
        if ( points == null ) {
            return;
        }
        assureStopped();
        if ( standAlone && "localhost".equals(server) ) {
            if ( es == null ) {
                es = new ExecutableServer(port);
                System.out.println("es = "+es);
                es.start();
            }
        }
        thread = new TSPModelConnectThread(points,
                                           algo,
                                           numThread,
                                           maxIter,
                                           server,
                                           port,
                                           this);
        thread.start();
    } 
 
/**
 * @return true if thread is running, else false.
 */
    public boolean isRunning() {
        if ( thread == null ) {
            return false;
        }
        return thread.isRunning();
    }

    // todo: collect history
    public void waitProblem() {
        if ( thread == null ) {
                return;
        }
        try {
            thread.join();
            thread = null;
        } catch (InterruptedException ignored) {
        }
        // doStatus();
    } 

    public void assureStopped() {
        if ( thread != null ) {
            if ( thread.isRunning() ) {
               long curi = thread.getIterations() / numThread;
               thread.setMaxIterations(curi);
               //waitProblem();
               thread = null;
            }
        }
    } 

    public void stopProblem() {
        if ( thread == null ) {
                return;
        }
        ActionListener joiner = new ActionListener() {
           public void actionPerformed(ActionEvent evt) {
               if ( wecker != null ) {
                  wecker.stop();
               }
               assureStopped();
           }
        };
        if ( wecker != null ) {
            wecker.stop();
        }
        wecker = new Timer(100, joiner);
        //wecker.setLogTimers(true);
        wecker.setRepeats(false);
        wecker.start();
    } 

    
/**
 * @return points of the cities.
 */
    public Point[] getPoints() {
        return points;
    }

/**
 * @return scaled points of the cities, i.e. all coordiates lie within 0.0 and 1.0.
 */
    public Point[] getScaledPoints() {
       return scaledPoints;
    }

/**
 * @return the graph.
 */
    public Graph getGraph() {
        return graph;
    }
    
/** 
 * @return true if the actual path is the best path, else false.
 */
    public boolean isBestPath() {
        return isBestPath;
    }

/**
 * @return return best path if known.
 */
    public Path getBestPath() {
        if ( thread != null ) {
           bestPath = thread.getBestPath();
           isBestPath = thread.isBestPath();
        }
        return bestPath;
    }

/**
 * @return return actual best path
 */
    public Path getActualBestPath() {
        if ( thread != null ) {
           actualBestPath = thread.getActualBestPath();
           isBestPath = thread.isBestPath();
        }
        return actualBestPath;
    }

/**
 * @return number of threads used.
 */
    public int getThreads() {
        return numThread;
    }

/**
 * @param n number of threads to be used.
 */
    public void setThreads(int n) {
        numThread = n;
    }

/**
 * @return iteration count.
 */
    public long getIterations() {
        if ( thread != null ) {
           iter = thread.getIterations();
        }
        return iter;
    }

/**
 * @return maximal iteration count.
 */
    public long getMaxIterations() {
        if ( thread != null ) {
           maxIter = thread.getMaxIterations();
        }
        return maxIter;
    }

/**
 * @return per centage of used iterations of total number of possible paths.
 */
    public double getIterPercent() {
        iter = getIterations();
        double ip = 100.0*((double)iter)/((double)facul(points.length-1));
        iterPercent = ((int)(ip*100.0))/100.0;
        return iterPercent;
    }

/**
 * @param n input.
 * @return n * facul( n-1 ).
 */
    protected long facul(long n) {
        if ( n <= 1l ) {
            return 1l;
        }
        return n * facul( n-1 );
    }

/**
 * Set maximal iteration count.
 * @param m new maximal iteration count.
 * @return old maximal iteration count.
 */
    public long setMaxIterations(long m){
        maxIter = m;
        if ( thread == null ) {
               return 0l;
        }
        return thread.setMaxIterations(m);
    }
    
/**
 * @return true if user requests exit.
 */
    public boolean isDone() {
        return done;
    }

    public synchronized void setDone() {
        // do cleanup
        assureStopped();
        if ( es != null ) {
            es.terminate();
            try {
                es.join();
            } catch (InterruptedException ignored) {
            }
        }
        done = true;
        notify();
    }

    public synchronized void waitDone() {
        if ( done ) {
               return;
        }
        try {
              wait(); 
        } catch(InterruptedException ignored) {
        }
    }

/**
 * @return problem size, i.e. number of points / nodes / cities.
 */
    public int getProbSize() {
        return probSize;
    }

/**
 * @param s new problem size.
 */
    public void setProbSize(int s) {
        probSize = s;
    }

/**
 * @return the algorithm to be used.
 */
    public String getAlgorithm() {
        return algorithm;
    }

/**
 * @param alg the new algorithm to be used.
 */
    public void setAlgorithm(String alg) {
        int a = -1;
        if ( alg == null ) {
            return;
        }
        if ( alg.compareToIgnoreCase("sequential") == 0 ) {
            // setThreads(1); doUpdate();
            a = 0;
        }
        if ( alg.compareToIgnoreCase("parallel") == 0 ) {
            //if ( getThreads() <= 1 ) {
            //   setThreads(4); doUpdate();
            //}
            a = 1;
        }
        if ( alg.compareToIgnoreCase("distributed") == 0 ) {
            //if ( getThreads() <= 1 ) {
            //   setThreads(2); doUpdate();
            //}
            a = 2;
        }
        if ( a >= 0 ) {
            algo = a;
            algorithm = alg;
        }
    }

/**
 * @return name of compute server.
 */
    public String getServer() {
        return server;
    }

/**
 * @param s name of compute server.
 */
    public void setServer(String s) {
        server = s;
    }

/**
 * @return port of compute server.
 */
    public int getPort() {
        return port;
    }

/**
 * @param p port of compute server.
 */
    public void setPort(int p) {
        port = p;
    }

/**
 * @return true if no remote compute server should be used.
 */
    public boolean getStandAlone() {
        return standAlone;
    }

/**
 * @param a true if no remote compute server should be used.
 */
    public void setStandAlone(boolean a) {
        standAlone = a;
    }

/**
 * @param u the TSPguiUpdate.
 */
    public void setUpdater(TSPguiUpdate u) {
        updater = u;
    }

    public void doUpdate() {
        if ( updater != null ) {
           updater.modelUpdated();
        }
    }

    public void doStatus() {
        if ( updater != null ) {
           updater.modelStatus();
        }
    }

}

/**
 * Class to communicate with the thread on the compute server.
 */
class TSPModelConnectThread extends Thread {

    private Point[] points;
    private Path path;
    private int numThread;
    private int algo;
    private long iter;
    private long maxIter;
    private long maxIterTemp;
    private boolean isBestPath;
    private String server = "localhost";
    private int port = 9000;
    private String guiHost = "localhost";
    private int guiPort = 9009;
    private int guiClientPort = port + 100;
    private TSPguiModel model;
    private ChannelFactory cf;
    private SocketChannel comm = null;
    private ThreadPool pool = null;
    

/**
 * @param points array city coordinates.
 * @param algo algorithm to use.
 * @param threads number of thread to use.
 * @param mit maximal iteration count.
 * @param server host name of compute server.
 * @param port of compute server.
 * @param model the TSPguiModel.
 */
    public TSPModelConnectThread(Point[] points, 
                                 int algo, 
                                 int threads, 
                                 long mit, 
                                 String server,
                                 int port,
                                 TSPguiModel model) {
        this.points = points;   
        this.algo = algo;
        this.numThread = threads;
        this.model = model;
        this.server = server;
        this.port = port;
        guiClientPort = port + 100;
        path = null;
        iter = 0l;
        maxIter = mit;
        isBestPath = false;
        cf = new ChannelFactory(guiPort);
        pool = new ThreadPool(3);
    }

    /* (non-Javadoc)
     * @see java.lang.Runnable#run()
     */
    public void run() {
        SocketChannel sc = null;
        try {
            sc = cf.getChannel(server,port);
        } catch (IOException e) {
            e.printStackTrace();
            cf.terminate();
            return;
        }
        guiHost = sc.getSocket().getLocalAddress().getHostAddress();
        // guiPort = sc.getSocket().getLocalPort();
        //System.out.println("sc.getSocket() = " + sc.getSocket()
        //                   + " guiHost = " + guiHost);

        TSPModelRemote thread = new TSPModelRemote(points,
                                                   algo,
                                                   numThread,
                                                   maxIter,
                                                   guiHost,
                                                   guiPort,
                                                   guiClientPort);
        try {
            sc.send( thread );
        } catch (IOException e) {
            e.printStackTrace();
            sc.close();
            cf.terminate();
            return;
        }
        try {
              comm = cf.getChannel(server,guiClientPort); // wg firewall
           // comm = cf.getChannel(); // @ guiPort
        } catch (IOException e) {
            e.printStackTrace();
            sc.close();
            cf.terminate();
            return;
        }
        Object o = null;
        try {
            o = sc.receive();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        if ( o != null ) {
           if ( o instanceof String ) {
              String s = (String)o;
              if ( s.equals(ExecutableServer.DONE) ) {
                 System.out.println("normal end");
              }
           }
        }
        sc.close();
        cf.terminate();
        model.doStatus();
    }

/**
 * @return true if thread is runnning.
 */
    public boolean isRunning() {
        return (comm != null);
    }

/**
 * @return true if path is best path, else false.
 */
    public boolean isBestPath() {
        // communication done by getActualBestPath
        return isBestPath; 
    }

/**
 * @return iteration count.
 */
    public long getIterations() {
        // communication done by getActualBestPath
        return iter; 
    }

/**
 * @return actual best path.
 */
    public Path getActualBestPath() {
        if ( comm == null ) {
               return path;
        }
        Runnable getter = new Runnable() {
              public void run() {
                  Object o = sendReceive( new TransportContainer() );
                  if ( o != null ) {
                     if ( o instanceof TransportContainerGet ) {
                        TransportContainerGet t = (TransportContainerGet)o;
                        path       = t.path;
                        iter       = t.iter;
                        isBestPath = t.best;
                     }
                  }
              }
            };
        pool.addJob( getter );
        return path; // tsp.actualBest();
    }

/**
 * @return best known path.
 */
    public Path getBestPath() {
        if ( comm == null ) {
               return path;
        }
        Runnable getter = new Runnable() {
              public void run() {
                  Object o = sendReceive( new TransportContainer() );
                  if ( o != null ) {
                     if ( o instanceof TransportContainerGet ) {
                        TransportContainerGet t = (TransportContainerGet)o;
                        path       = t.path;
                        iter       = t.iter;
                        isBestPath = t.best;
                     }
                  }
              }
            };
        pool.addJob( getter );
        return path; // tsp.getBest();
    }

/**
 * @return maximal iteration count.
 */
    public long getMaxIterations() {
        if ( comm == null ) {
               return maxIter;
        }
        Runnable getter = new Runnable() {
              public void run() {
                  Object o = sendReceive( new TransportContainerMax(maxIter) );
                  if ( o != null ) {
                     if ( o instanceof TransportContainerMax ) {
                        TransportContainerMax t = (TransportContainerMax)o;
                        maxIter = t.maxIter;
                     }
                  }
              }
            };
        pool.addJob( getter );
        return maxIter; // tsp.getmaxIterations();
    }

/**
 * @param m new maximal iteration count.
 * @return old maximal iteration count.
 */
    public long setMaxIterations(final long m){
        maxIterTemp = maxIter;
        maxIter = m;
        if ( comm == null ) {
               return maxIterTemp;
        }
        Runnable getter = new Runnable() {
              public void run() {
                  Object o = sendReceive( new TransportContainerMax(maxIter) );
                  if ( o != null ) {
                     if ( o instanceof TransportContainerMax ) {
                        TransportContainerMax t = (TransportContainerMax)o;
                        maxIterTemp = t.maxIter;
                     }
                  }
              }
            };
        pool.addJob( getter );
        return maxIterTemp; // tsp.setMaxIterations(m);
    }

/**
 * RPC style communication.
 * @param m a TransportContainer.
 * @return received Object.
 */
    protected Object sendReceive(TransportContainer m) {
        Object r = null;
        try {
            //synchronized( comm ) {
            comm.send( m );
            //}
            r = comm.receive();
        } catch (IOException e) {
            //e.printStackTrace();
        } catch (ClassNotFoundException e) {
            //e.printStackTrace();
        }
        return r;
    }

}


/**
 * Objects send to the compute server to execute TSP algorithm.
 */
class TSPModelRemote implements RemoteExecutable {

    private Point[] points;
    private Path path;
    private int threads;
    private int algo;
    private TSPInf tsp = null;
    private long iter;
    private long maxIter;
    private boolean isBestPath;
    private String guiHost = "localhost";
    private int guiPort = 9009;
    private int guiClientPort = 9100;
    
/**
 * @param points array of cities.
 * @param algo algorithm.
 * @param threads number of threads.
 * @param mit maximal iterations.
 * @param guiHost host name of gui server. Unused because of firewalls.
 * @param guiPort port of gui server. Unused because of firewalls.
 * @param guiClientPort port at compute server.
 */
    public TSPModelRemote(Point[] points, 
                          int algo, 
                          int threads, 
                          long mit, 
                          String guiHost,
                          int guiPort,
                          int guiClientPort) {
        this.points = points;   
        this.algo = algo;
        this.threads = threads;
        this.guiHost = guiHost;
        this.guiPort = guiPort;
        this.guiClientPort = guiClientPort;
        path = null;
        iter = 0l;
        maxIter = mit;
        isBestPath = false;
    }

    public void run() {
        TSPModelCommRemote communi = new TSPModelCommRemote(this,
                                                            guiHost,
                                                            guiPort,
                                                            guiClientPort);
        communi.start();
        switch ( algo ) {
         case 0: tsp = new SeqByteTSP(points);
                 break;
         case 1: tsp = new ParByteLocalTSP(points,threads);
                 break;
         case 2: tsp = new DistTSP(points,threads);
                 break;
        default: tsp = new SeqByteTSP(points);
        }
        long mi = maxIter;
        path = tsp.getBest(maxIter); // blocks
        if ( mi <= maxIter ) {
           isBestPath = true;
           //System.out.println("isBestPath = " + isBestPath );
        } 
        // model.doStatus();
        tsp = null;
        communi.terminate();
    }
        
/**
 * @return true if tsp is executing.
 */
    public boolean isRunning() {
        return (tsp != null);
    }

/**
 * @return best known path.
 */
    public Path getBestPath() {
        return path;
    }

/**
 * @return true if path is best path.
 */
    public boolean isBestPath() {
        return isBestPath;
    }

/**
 * @return actual best path.
 */
    public Path getActualBestPath() {
        if ( tsp == null ) {
               return path;
        }
        return tsp.actualBest();
    }

/**
 * @return iteration count.
 */
    public long getIterations() {
        if ( tsp != null ) {
           iter = tsp.getIterations();
        }
        return iter;
    }

/**
 * @return maximal iteration count.
 */
    public long getMaxIterations() {
        if ( tsp != null ) {
           return tsp.getMaxIterations();
        }
        return maxIter;
    }

/**
 * @param m new maximal iteration count.
 * @return old maximal iteration count.
 */
    public long setMaxIterations(long m){
        long x = maxIter;
        maxIter = m;
        if ( tsp != null ) {
           x = tsp.setMaxIterations(m);
        }
        return x;
    }

}


/**
 * Container for transport of path, iterations, etc.
 */
class TransportContainer implements Serializable {
    public TransportContainer() {
    }
}

/**
 * Get message.
 */
class TransportContainerGet extends TransportContainer {
    final Path path;
    final long iter;
    final boolean best;

    /**
     * @param p a path.
     * @param i iteration count.
     * @param b true if p is best path.
     */
    public TransportContainerGet(Path p, long i, boolean b) {
        super();
        path = p;
        iter = i;
        best = b;
    }
}

/**
 * Set maximal iteration count message.
 */
class TransportContainerMax extends TransportContainer {
    final long maxIter;

    /**
     * @param m maximal iteration count.
     */
    public TransportContainerMax(long m) {
        super();
        maxIter = m;
    }
}


/**
 * Class to communicate with the gui server.
 */
class TSPModelCommRemote extends Thread {
    /*
    public static final String COMM_getActualBestPath = "getActualBestPath";
    public static final String COMM_getBestPath       = "getBestPath";
    public static final String COMM_getMaxIterations  = "getMaxIterations";
    public static final String COMM_setMaxIterations  = "setMaxIterations";
    */
    private TSPModelRemote rem;
    private SocketChannel comm;
    private String guiHost = "localhost";
    private int guiPort = 9009;
    private int guiClientPort = 9100;

/**
 * @param rem master process for call back.
 * @param guiHost host name of gui server. Unused because of firewalls.
 * @param guiPort port of gui server. Unused because of firewalls.
 * @param guiClientPort my local port for communication with gui.
 */
    public TSPModelCommRemote(TSPModelRemote rem,
                              String guiHost,
                              int guiPort,
                              int guiClientPort) {
        this.rem = rem;
        this.guiHost = guiHost;
        this.guiPort = guiPort;
        this.guiClientPort = guiClientPort;
        ChannelFactory cf = new ChannelFactory(guiClientPort);
        try {
            //comm = cf.getChannel(guiHost,guiPort);
            comm = cf.getChannel(); // wg. firewall
        } catch (InterruptedException e) {
            e.printStackTrace();
            return;
        }
        cf.terminate();
    }

    public void terminate() {
        try { // wg isBestPath before comm.close()
            Thread.sleep(200); 
        } catch (InterruptedException e) { 
        }
        if ( comm != null ) {
           comm.close();
        }
        try { 
            while ( this.isAlive() ) {
                  //System.out.print(".");
                  this.interrupt(); 
                  this.join(100);
            }
            //System.out.println("TSPModelCommRemote terminated");
        } catch (InterruptedException e) { 
        }
    }

    public void run() {
        if ( comm == null ) {
            return;
        }
        while ( true ) { // protocoll: receive send
              Object m = null;
              try {
                  m = comm.receive();
              } catch (IOException e) {
                  //e.printStackTrace();
              } catch (ClassNotFoundException e) {
                  e.printStackTrace();
              }
              //System.out.println("TSPModelCommRemote, receive = " + o);
              if ( m == null ) {
                  break;
              }
              if ( ! (m instanceof TransportContainer) ) {
                  break;
              }
              TransportContainer t = (TransportContainer)m;
              Object response = null;
              if ( t instanceof TransportContainerMax ) {
                 TransportContainerMax x = (TransportContainerMax)t;
                 long ml = x.maxIter;
                 response = new TransportContainerMax(
                                                  rem.setMaxIterations(ml)
                                                     );
              } else {
                 // TransportContainerGet g = (TransportContainerGet)t;
                 response = new TransportContainerGet(
                                                  rem.getActualBestPath(),
                                                  rem.getIterations(),
                                                  rem.isBestPath()
                                                     );
                 //} else if ( t instanceof TransportContainerGet ) {
              }
              try {
                  comm.send( response );
              } catch (IOException e) {
                  //e.printStackTrace();
                  break;
              }
        }
        System.out.println("TSPModelCommRemote done");
        comm.close();
    }

}