
package gui;

import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Dimension;
import java.awt.event.KeyListener;
import java.awt.event.WindowListener;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;

import java.awt.BorderLayout;
import javax.swing.BoxLayout;

import javax.swing.event.ChangeListener;
import javax.swing.JFrame;
//import javax.swing.JCheckBox;
import javax.swing.JCheckBoxMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JDialog;
import javax.swing.JPanel;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JTextField;
import javax.swing.JTextArea;
import javax.swing.JScrollPane;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.BorderFactory;
//import javax.swing.JSpinner;
//import javax.swing.SpinnerNumberModel;

import algo.Path;
import algo.PathByteArray;
import algo.Point;

/**
 * Main class to setup the GUI and the controls.
 * @author Heinz Kredel.
 */
public class TSPguiMain extends JFrame implements TSPguiUpdate {

    static Color background = new Color(0xFF,0xFF,0xF5);
    static Color text       = new Color(0xCC,0,0);
    static Color text2      = Color.black;
    static Font  schrift    = new Font("SansSerif", Font.PLAIN, 18);
    static Font  schrift2   = new Font("SansSerif", Font.PLAIN, 14);
    static Font  schrift3   = new Font("SansSerif", Font.PLAIN, 10);

    TSPguiModel model;
    KeyListener keyControl;
    WindowListener windowControl;
    ActionListener actionControl;
    ChangeListener changeControl;

    private JPanel graphPanel = null;
    private JTextField statusLabel = null;
    private JTextArea statusArea = null;
    private JTextField algorithmText = null;
    
/**
 * @param model the TSPguiMode.
 * @param keyControl the KeyListener.
 * @param windowControl the WindowListener.
 * @param actionControl the ActionListener.
 * @param changeControl the ChangeListener.
 */
    public TSPguiMain(TSPguiModel model,
                      KeyListener keyControl, 
                      WindowListener windowControl, 
                      ActionListener actionControl, 
                      ChangeListener changeControl) {
        super("Graphical user interface for TSP");
        this.model = model;
        this.keyControl = keyControl;
        this.windowControl = windowControl;
        this.actionControl = actionControl;
        this.changeControl = changeControl;

        addKeyListener(keyControl);
        addWindowListener(windowControl);

        setContentPane( makeGraphPane() );
        setJMenuBar( makeTopMenu() );

        setLocation(200,100);
        pack();
        setSize(950,650);

        setVisible(true);
        model.setUpdater(this);
    }

    /* never use this in swing
    public void paint(Graphics g) {
        super.paint( g );
        g.drawString("Welcome at TSPgui",10,50);
        g.drawString("Algorithm = "+model.algorithm,10,100);
    }
    */

/**
 * @return top menue bar.
 */
    protected JMenuBar makeTopMenu() {
        JMenuBar topMenu = new JMenuBar();
        topMenu.setBackground( background );
        topMenu.setForeground( text );
        topMenu.add( makeFileMenu() );
        topMenu.add( makeAlgoMenu() );
        topMenu.add( makeProblemMenu() );
        topMenu.add( makeHelpMenu() );
        return topMenu;
    }

/**
 * @return file selection menue item.
 */
    protected JMenu makeFileMenu() {
        JMenu file = new JMenu("File");
        file.setMnemonic(KeyEvent.VK_F);
        file.setBackground( background );
        file.setForeground( text );
        JCheckBoxMenuItem standAlone = new JCheckBoxMenuItem("Standalone",model.getStandAlone());
        standAlone.setMnemonic(KeyEvent.VK_A);
        standAlone.setBackground( background );
        standAlone.setForeground( text );
        standAlone.setActionCommand("standAlone");
        standAlone.addActionListener(actionControl);
        file.add(standAlone);
        JMenuItem exit = new JMenuItem("Exit");
        exit.setMnemonic(KeyEvent.VK_E);
        exit.setBackground( background );
        exit.setForeground( text );
        exit.setActionCommand("exit");
        exit.addActionListener(actionControl);
        file.add(exit);
        return file;
    }

/**
 * @return algorithm selection menue item.
 */
        protected JMenu makeAlgoMenu() {
        JMenu algo = new JMenu("Algorithms");
        algo.setMnemonic(KeyEvent.VK_A);
        algo.setBackground( background );
        algo.setForeground( text );
        JMenuItem seq = new JMenuItem("Sequential");
        seq.setMnemonic(KeyEvent.VK_S);
        seq.setBackground( background );
        seq.setForeground( text );
        seq.setActionCommand("sequential");
        seq.addActionListener(actionControl);
        algo.add(seq);
        JMenuItem para = new JMenuItem("Parallel");
        para.setMnemonic(KeyEvent.VK_P);
        para.setBackground( background );
        para.setForeground( text );
        para.setActionCommand("parallel");
        para.addActionListener(actionControl);
        algo.add(para);
        JMenuItem dist = new JMenuItem("Distributed");
        dist.setMnemonic(KeyEvent.VK_D);
        dist.setBackground( background );
        dist.setForeground( text );
        dist.setActionCommand("distributed");
        dist.addActionListener(actionControl);
        algo.add(dist);
        return algo;
    }

/**
 * @return problem handling menue item.
 */
    protected JMenu makeProblemMenu() {
        JMenu prob = new JMenu("Problem");
        prob.setMnemonic(KeyEvent.VK_P);
        prob.setBackground( background );
        prob.setForeground( text );
        JMenuItem gener = new JMenuItem("Generate");
        gener.setMnemonic(KeyEvent.VK_G);
        gener.setBackground( background );
        gener.setForeground( text );
        gener.setActionCommand("generate");
        gener.addActionListener(actionControl);
        prob.add(gener);
        JMenuItem sol = new JMenuItem("Solve");
        sol.setMnemonic(KeyEvent.VK_S);
        sol.setBackground( background );
        sol.setForeground( text );
        sol.setActionCommand("solve");
        sol.addActionListener(actionControl);
        prob.add(sol);
        JMenuItem stp = new JMenuItem("Stop");
        stp.setMnemonic(KeyEvent.VK_T);
        stp.setBackground( background );
        stp.setForeground( text );
        stp.setActionCommand("stop");
        stp.addActionListener(actionControl);
        prob.add(stp);
        return prob;
    }

/**
 * @return help menue item.
 */
    protected JMenu makeHelpMenu() {
        JMenu help = new JMenu("Help");
        help.setMnemonic(KeyEvent.VK_H);
        help.setBackground( background );
        help.setForeground( text );
        JMenuItem about = new JMenuItem("About...");
        about.setMnemonic(KeyEvent.VK_A);
        about.setBackground( background );
        about.setForeground( text );
        about.setActionCommand("about");
        about.addActionListener(actionControl);
        help.add(about);
        return help;
    }
    
/**
 * @return center panel with control planel, status and graph panel.
 */
    protected JPanel makeGraphPane() {
        JPanel centerPanel = new JPanel();
        centerPanel.setBorder( BorderFactory.createEtchedBorder() );
        centerPanel.setBackground( background );
        centerPanel.setForeground( text );
        centerPanel.setFont( schrift );
        // set its layout
        centerPanel.setLayout(new BorderLayout());

        JLabel mesg = new JLabel("                  Welcome at TSPgui");
        mesg.setForeground( text );
        mesg.setFont( schrift );
        centerPanel.add(mesg,BorderLayout.NORTH);

        JComponent control = makeControlPane();
        centerPanel.add(control,BorderLayout.WEST);

        JPanel graph = new GraphPanel(model);
        graph.setBackground( background );
        graph.setForeground( text );
        graph.setFont( schrift );
        centerPanel.add(graph,BorderLayout.CENTER);
        graphPanel = graph; // global static
        
        JTextArea status = new JTextArea(4,0);
        status.setFont( schrift2 );
        JScrollPane scroll = new JScrollPane( status );
        scroll.setBackground( background );
        scroll.setBorder( BorderFactory.createTitledBorder("Status") );
        centerPanel.add(scroll,BorderLayout.SOUTH);
        statusArea = status; // global static

        return centerPanel;
    }
 

/**
 * @return right status and control panel.
 */
    protected JComponent makeControlPane() {
        final int cols = 16;
        //  Box rightPanel = new Box( BoxLayout.Y_AXIS );
        JPanel rightPanel = new JPanel();
        BoxLayout lay = new BoxLayout( rightPanel, BoxLayout.Y_AXIS );
        rightPanel.setLayout( lay );
        //rightPanel.setBorder( BorderFactory.createEtchedBorder() );
        rightPanel.setBorder( BorderFactory.createTitledBorder("Parameters") );
        rightPanel.setBackground( background );
        rightPanel.setForeground( text );
        rightPanel.setFont( schrift2 );

        JLabel sizeL = new JLabel("Problem size:");
        sizeL.setForeground( text );
        sizeL.setFont( schrift2 );
        rightPanel.add(sizeL);
        JTextField size = new JTextField(""+model.getProbSize(),cols);
        size.setForeground( text2 );
        size.setFont( schrift2 );
        size.setHorizontalAlignment( JTextField.RIGHT );
        size.setMaximumSize(size.getPreferredSize());
        size.setActionCommand("size");
        size.addActionListener(actionControl);
        rightPanel.add(size);
        
        JLabel probF = new JLabel("Problem file:");
        probF.setForeground( text );
        probF.setFont( schrift2 );
        rightPanel.add(probF);
        JTextField prob = new JTextField("random",cols);
        prob.setForeground( text2 );
        prob.setFont( schrift2 );
        prob.setMaximumSize(prob.getPreferredSize());
        prob.setActionCommand("file");
        prob.addActionListener(actionControl);
        rightPanel.add(prob);

        JLabel algoL = new JLabel("Algorithm:");
        algoL.setForeground( text );
        algoL.setFont( schrift2 );
        rightPanel.add(algoL);
        JTextField algo = new JTextField(""+model.getAlgorithm(),cols);
        algo.setForeground( text2 );
        algo.setBackground( background );
        algo.setFont( schrift2 );
        algo.setEditable(false);
        algo.setMaximumSize(algo.getPreferredSize());
        rightPanel.add(algo);
        algorithmText = algo; // global static

        JLabel maxT = new JLabel("max Threads:");
        maxT.setForeground( text );
        maxT.setFont( schrift2 );
        rightPanel.add(maxT);
        /*
        SpinnerNumberModel thmodel = new SpinnerNumberModel(
                                         model.getThreads(), 1, 1000, 1); 
        JSpinner thspin = new JSpinner( thmodel );
        thspin.setForeground( text2 );
        thspin.setFont( schrift2 );
        thspin.setMaximumSize(thspin.getPreferredSize());
        thspin.addChangeListener(changeControl);
        //rightPanel.add(thspin);
        */
        JTextField maxth = new JTextField(""+model.getThreads(),cols);
        maxth.setForeground( text2 );
        maxth.setFont( schrift2 );
        maxth.setHorizontalAlignment( JTextField.RIGHT );
        maxth.setMaximumSize(maxth.getPreferredSize());
        maxth.setActionCommand("threads");
        maxth.addActionListener(actionControl);
        rightPanel.add(maxth);

        JLabel maxI = new JLabel("max Iterations:");
        maxI.setForeground( text );
        maxI.setFont( schrift2 );
        rightPanel.add(maxI);
        JTextField maxiter = new JTextField(""+model.getMaxIterations(),cols);
        maxiter.setForeground( text2 );
        maxiter.setFont( schrift2 );
        maxiter.setHorizontalAlignment( JTextField.RIGHT );
        maxiter.setMaximumSize(maxiter.getPreferredSize());
        maxiter.setActionCommand("maxiter");
        maxiter.addActionListener(actionControl);
        rightPanel.add(maxiter);

        JLabel iterL = new JLabel("Iterations:");
        iterL.setForeground( text );
        iterL.setFont( schrift2 );
        rightPanel.add(iterL);
        JTextField stat = new JTextField("0",cols);
        stat.setForeground( text );
        stat.setBackground( background );
        stat.setFont( schrift2 );
        stat.setEditable(false);
        stat.setHorizontalAlignment( JTextField.RIGHT );
        stat.setMaximumSize(stat.getPreferredSize());
        rightPanel.add(stat);
        statusLabel = stat; // global static

        JLabel serv = new JLabel("Server:");
        serv.setForeground( text );
        serv.setFont( schrift2 );
        rightPanel.add(serv);
        JTextField server = new JTextField(""+model.getServer(),cols);
        server.setForeground( text2 );
        server.setFont( schrift2 );
        server.setMaximumSize(server.getPreferredSize());
        server.setActionCommand("server");
        server.addActionListener(actionControl);
        rightPanel.add(server);

        JLabel port = new JLabel("Port:");
        port.setForeground( text );
        port.setFont( schrift2 );
        rightPanel.add(port);
        JTextField serverPort = new JTextField(""+model.getPort(),cols);
        serverPort.setForeground( text2 );
        serverPort.setFont( schrift2 );
        serverPort.setHorizontalAlignment( JTextField.RIGHT );
        serverPort.setMaximumSize(serverPort.getPreferredSize());
        serverPort.setActionCommand("port");
        serverPort.addActionListener(actionControl);
        rightPanel.add(serverPort);

        //rightPanel.setMaximumSize(rightPanel.getPreferredSize());
        return rightPanel;
    }


    public synchronized void modelUpdated() {
        if ( graphPanel != null ) {
           graphPanel.repaint();
        }
        updateStatusLabel();
        updateAlgorithm();
    }

    public synchronized void modelStatus() {
        updateStatusArea();
    }

    protected void updateStatusArea() {
        if ( statusArea == null ) {
            return;
        }
        StringBuffer s = new StringBuffer();
        s.append(" Algorithm( "  + model.getAlgorithm());
        if ( ! model.getAlgorithm().equals("sequential") ) {
           s.append("/"  + model.getThreads() );
        }
        s.append(" ) ");
        s.append("Iterations( " + model.getIterations() );
        s.append(", " + model.getIterPercent() );
        s.append(" % of " + (model.getProbSize()-1) + "! ) " );
        Path path = model.getActualBestPath();
        if ( path != null ) {
           if ( model.isBestPath() ) {
              s.append("best");
           }
           s.append("Path = " + path );
        }
        s.append("\n");
        statusArea.append( s.toString() );
    }

    protected void updateStatusLabel() {
        if ( statusLabel == null ) {
            return;
        }
        statusLabel.setText( ""+model.getIterations() );
    }

    protected void updateAlgorithm() {
        if ( algorithmText == null ) {
            return;
        }
        algorithmText.setText( model.getAlgorithm() );
    }
    
/**
 * @return true if user requests exit, else false.
 */
    protected static boolean doExitDialog() {
        JOptionPane pane = new JOptionPane("Wollen Sie TSP verlassen?",
                                           JOptionPane.WARNING_MESSAGE,
                                           JOptionPane.YES_NO_OPTION);
        pane.setBackground( background );
        pane.setForeground( text );
        pane.setFont( schrift2 );
        pane.setOpaque(true);
        JDialog dialog = pane.createDialog(null, "Exit Dialog");
        dialog.setBackground( background );
        dialog.setForeground( text );
        dialog.setFont( schrift2 );
        /*
        pane.getRootPane().setOpaque(true);
        dialog.getLayeredPane().setOpaque(true);
        pane.getRootPane().setBackground( background );
        pane.getRootPane().setForeground( text );
        dialog.getContentPane().setBackground( background );
        dialog.getContentPane().setForeground( text );
        dialog.getGlassPane().setBackground( background );
        dialog.getGlassPane().setForeground( text );
        dialog.getLayeredPane().setBackground( background );
        dialog.getLayeredPane().setForeground( text );
        */
        dialog.show();
        Object selectedValue = pane.getValue();
        if ( selectedValue == null ) {
               return false;
        }
        if ( ! (selectedValue instanceof Integer) ) {
           return false;
        }
        int r = ((Integer)selectedValue).intValue();
        if ( r == JOptionPane.OK_OPTION ) {    
           return true;
        }
        return false;
        /*      
        int r = JOptionPane.showConfirmDialog(
                null,
                "Wollen Sie TSP verlassen?",
                "Exit Dialog",
                JOptionPane.YES_NO_OPTION,
                JOptionPane.WARNING_MESSAGE
                );
        */
    }

    protected static void doAboutDialog() {
        JOptionPane pane = new JOptionPane(
                           "A GUI for the Traveling Salesman Problem (TSP)\n\n"
                         + "Author:\n" 
                         + "Heinz Kredel, kredel@rz.uni-mannheim.de\n"
                         + "with the help of students from BA Mannheim\n"
                         + "Date: December 2004\n"
                         , JOptionPane.INFORMATION_MESSAGE);
        pane.setBackground( background );
        pane.setForeground( text );
        pane.setFont( schrift2 );
        pane.setOpaque(true);
        JDialog dialog = pane.createDialog(null, "About TSPgui");
        dialog.setBackground( background );
        dialog.setForeground( text );
        dialog.setFont( schrift2 );
        dialog.show();
    }
}

/**
 * Class to handle the display of the points, paths and graphs.
 */
class GraphPanel extends JPanel {

    protected TSPguiModel model;
    protected byte[] oldPath = null;
    
/**
 * @param model the TSPguiModel.
 */
    public GraphPanel(TSPguiModel model) {
        super();
        this.model = model;
        setPreferredSize(new Dimension(850,500));
        setBorder( BorderFactory.createTitledBorder("Points and Path") );
    }
        
    public void paintComponent(Graphics g0) {
        super.paintComponent(g0);
        Point[] points = model.getScaledPoints();
        if ( points == null ) {
               return;
        }
        Graphics2D g = null;
        if ( g0 instanceof Graphics2D ) {
           g = (Graphics2D)g0;
        } else {
           return;
        }
        g.setColor( TSPguiMain.text );
        double scaleX = this.getWidth()*0.9;
        double scaleY = this.getHeight()*0.9;
        double offX   = this.getWidth()*0.05; // 5 % leerer rand
        double offY   = this.getHeight()*0.05;
        int[] X = new int[ points.length ];
        int[] Y = new int[ points.length ];
        for ( int i = 0; i < points.length; i++ ) {
            X[i] = (int)(offX + points[i].x * scaleX); 
            Y[i] = (int)(offY + points[i].y * scaleY); 
            g.fillRect(X[i]-5,Y[i]-5,10,10);
            g.drawString(""+points[i].n,X[i]+8,Y[i]+8);
        }
        PathByteArray bestPath = (PathByteArray)model.getActualBestPath();
        if ( bestPath == null ) {
            oldPath = null;
            return;
        }
        if ( bestPath.length() > points.length ) {
            oldPath = null;
            return;
        }
        if ( oldPath != null ) {
            if ( oldPath.length == bestPath.length() ) {
               g.setColor( Color.green );
               drawPath(g, X, Y, oldPath);
            }
        }
        if ( bestPath.used != null ) {
           g.setColor( Color.darkGray );
           drawPath(g, X, Y, bestPath.used);
           oldPath = bestPath.used;
        }
    }

/**
 * @param g a Graphics2D context.
 * @param X array of x-coordinates.
 * @param Y array of x-coordinates.
 * @param path to display.
 */
    protected void drawPath(Graphics2D g, int[] X, int[] Y, byte[] path) {
        for ( int i = 0; i < path.length-1 && i < X.length-1; i++ ) {
            int i0 = path[i]; 
            int i1 = path[i+1]; // % path.length];
            g.drawLine(X[i0],Y[i0],X[i1],Y[i1]);
        }
        if (path.length == X.length) {
            int i0 = path[path.length-1]; 
            int i1 = path[0];
            g.drawLine(X[i0],Y[i0],X[i1],Y[i1]);
        }
    }
        
}
