/*
 * Copyright (C) 2017 Dimitrios Laskaris
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package gr.unipi.ds.graphcolorer;

import com.mxgraph.layout.*;
import com.mxgraph.model.mxGeometry;
import com.mxgraph.swing.*;
import com.mxgraph.util.mxConstants;
import com.mxgraph.view.mxPerimeter;
import com.mxgraph.view.mxStylesheet;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.*;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.*;

/**
 * 
 * @author Dimitrios Laskaris
 * @since February 13, 2017
 */
public class GraphColorer extends JApplet {
    
    private static final int DEFAULT_NUMBER_OF_VERTICES = 50;
    private static final int DEFAULT_NUMBER_OF_EDGES = 100;
    
    private GraphAdapter<Vertex, Edge> jGraphXAdapter;
    private ColoredListenableUndirectedGraph<Vertex, Edge> graphT;
    
    private final ButtonListener buttonListener = new ButtonListener();
    
    private final JToolBar toolBar = new JToolBar();
        private static final String NEW_BUTTON_TEXT = "New";
        private final JButton newButton = new JButton(NEW_BUTTON_TEXT);
        
        private static final String RUN_DSA_A_BUTTON_TEXT = "Run DSA-A";
        private final JButton runDSAaButton = new JButton(RUN_DSA_A_BUTTON_TEXT);
        
        private static final String RUN_THREADED_DSA_A_BUTTON_TEXT = "Run threaded DSA-A";
        private final JButton runThreadedDSAaButton = new JButton(RUN_THREADED_DSA_A_BUTTON_TEXT);
        
        private static final String RUN_THREADED_DSA_B_BUTTON_TEXT = "Run threaded DSA-B";
        private final JButton runThreadedDSAbButton = new JButton(RUN_THREADED_DSA_B_BUTTON_TEXT);
        
        private static final String REFRESH_BUTTON_TEXT = "Refresh";
        private final JButton refreshButton = new JButton(REFRESH_BUTTON_TEXT);
        
        private static final String EDIT_TOGGLE_BUTTON_TEXT = "Editable";
        private final JToggleButton editToggleButton = new JToggleButton(EDIT_TOGGLE_BUTTON_TEXT);
        
        private static final JLabel VERTICES_SPIN_LABEL = new JLabel("    # of vertices: ");
        private final JSpinner verticesSpinner = new JSpinner();
        
        private static final JLabel EDGES_SPIN_LABEL = new JLabel("    # of edges: ");
        private final JSpinner edgesSpinner = new JSpinner();
    
    private mxGraphComponent centerView;
    
    private final JPanel statusbar = new JPanel();
        private static final JLabel VERTICES_COUNT_LABEL = new JLabel("Vertices:");
        private final JLabel verticesCount = new JLabel();
        
        private static final JLabel EDGES_COUNT_LABEL = new JLabel(" Edges:");
        private final JLabel edgesCount = new JLabel();
        
        private static final JLabel SUCCESS_COUNT_LABEL = new JLabel(" Success:");
        private final JLabel successCount = new JLabel();
    
    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) {
	GraphColorer applet = new GraphColorer();
        applet.init();

        JFrame frame = new JFrame();
        frame.getContentPane().add(applet);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setTitle("GraphColorer");
//        URL iconURL = GraphColorer.class.getResource("res/icon.png");
//        Image icon = new ImageIcon(iconURL).getImage();
//        frame.setIconImage(icon);
        
        frame.pack();
        //frame.setResizable(false);
        frame.setMinimumSize(new Dimension(320, 240));
        frame.setMaximumSize(new Dimension(640, 380));
        frame.setLocationRelativeTo(null); //centers frame
        frame.setVisible(true);
    }
    
    /**
     * {@inheritDoc}
     */
    @Override
    public void init() {
	createGraph();
        createToolBar();
        createStatusBar();
    }
    
    /**
     * Generates a random number between two values
     * @param min minimum value (if it's a negative value, then it's set to 0)
     * @param max maximum value
     * @return a random integer between min and max (both included)
     */
    public static int randomBoundedNumber(int min, int max) {
        Random rng = new Random();
        if(min<0) {
            min = 0;
        }
	int rn = rng.nextInt(max-min+1);
        return rn+min;
    }
    
    /**
     * 
     * @param vertices
     * @param edges 
     */
    private void randomGraphGenerator(int vertices, int edges) {
        //random vertices generation
	for(int i=0;i<vertices;i++) {
	    graphT.addVertex(new Vertex(i));
	}
        
        //( graphT).randomizeColors();
        	
	//random edges generation
	Vertex[] ar = graphT.vertexSet().toArray(new Vertex[1]);
	for(int i=0;i<edges;i++) {
	    int s = randomBoundedNumber(0, ar.length-1);
	    int t = randomBoundedNumber(0, ar.length-1);
	    while(t==s) {
		t = randomBoundedNumber(0, ar.length-1);
	    }
	    graphT.addEdge(ar[s], ar[t]);
	}
    }
    
    /**
     * Initiates top toolbar
     */
    private void createToolBar() {
        toolBar.setFloatable(false);
        
        SpinnerModel verticesSpinModel =
        new SpinnerNumberModel(DEFAULT_NUMBER_OF_VERTICES, //initial value
                               1, //min
                               1000000, //max
                               5); //step
        verticesSpinner.setModel(verticesSpinModel);
        verticesSpinner.setMinimumSize(new Dimension(48,0));
        
        SpinnerModel edgesSpinModel =
        new SpinnerNumberModel(DEFAULT_NUMBER_OF_EDGES, //initial value
                               0, //min
                               1000000, //max
                               5); //step
        edgesSpinner.setModel(edgesSpinModel);
        edgesSpinner.setMinimumSize(new Dimension(48,0));
        
        toolBar.add(newButton);
        toolBar.add(runDSAaButton);
        toolBar.add(runThreadedDSAaButton);
        toolBar.add(runThreadedDSAbButton);
        toolBar.add(refreshButton);
        toolBar.add(editToggleButton);
        toolBar.add(VERTICES_SPIN_LABEL);
        toolBar.add(verticesSpinner);
        toolBar.add(EDGES_SPIN_LABEL);
        toolBar.add(edgesSpinner);
        
        editToggleButton.addActionListener(buttonListener);
        newButton.addActionListener(buttonListener);
        runDSAaButton.addActionListener(buttonListener);
        runThreadedDSAaButton.addActionListener(buttonListener);
        runThreadedDSAbButton.addActionListener(buttonListener);
        refreshButton.addActionListener(buttonListener);
        
        add(toolBar, BorderLayout.NORTH);
    }
    
    /**
     * Initiates bottom status bar
     */
    private void createStatusBar() {
        verticesCount.setBorder(BorderFactory.createLoweredBevelBorder());
        edgesCount.setBorder(BorderFactory.createLoweredBevelBorder());
        successCount.setBorder(BorderFactory.createLoweredBevelBorder());
        
        statusbar.add(VERTICES_COUNT_LABEL);
        statusbar.add(verticesCount);
        statusbar.add(EDGES_COUNT_LABEL);
        statusbar.add(edgesCount);
        statusbar.add(SUCCESS_COUNT_LABEL);
        statusbar.add(successCount);
        
        add(statusbar, BorderLayout.SOUTH);
        
        updateStatusBar();
    }
    
    private void createGraph() {
        graphT = new ColoredListenableUndirectedGraph(Edge.class);
        jGraphXAdapter = new GraphAdapter<>(graphT);
        centerView = new mxGraphComponent(jGraphXAdapter);
        defineGraphStyle();
        defineGraphBehavior();
        randomGraphGenerator(DEFAULT_NUMBER_OF_VERTICES,DEFAULT_NUMBER_OF_EDGES);
        arrangeLayout();
    }
    
    private void defineGraphStyle() {
        // define style
	Map<String, Object> vertexStyle = new HashMap<>();
	vertexStyle.put(mxConstants.STYLE_SHAPE, mxConstants.SHAPE_ELLIPSE);
	vertexStyle.put(mxConstants.STYLE_PERIMETER,
                        mxPerimeter.EllipsePerimeter);
	vertexStyle.put(mxConstants.STYLE_VERTICAL_ALIGN,
                        mxConstants.ALIGN_MIDDLE);
	vertexStyle.put(mxConstants.STYLE_ALIGN, mxConstants.ALIGN_CENTER);
	vertexStyle.put(mxConstants.STYLE_FILLCOLOR, Color.NONE.getHex());
        vertexStyle.put(mxConstants.STYLE_STROKECOLOR, "#000000");
        vertexStyle.put(mxConstants.STYLE_STROKEWIDTH, 2);
        
        Map<String, Object> edgeStyle = new HashMap<>();
        edgeStyle.put(mxConstants.STYLE_SHAPE, mxConstants.SHAPE_CONNECTOR);
        edgeStyle.put(mxConstants.STYLE_VERTICAL_ALIGN,
                      mxConstants.ALIGN_MIDDLE);
        edgeStyle.put(mxConstants.STYLE_ALIGN, mxConstants.ALIGN_CENTER);
        edgeStyle.put(mxConstants.STYLE_STROKECOLOR, "#000000");
        edgeStyle.put(mxConstants.STYLE_STROKEWIDTH, 2);
        edgeStyle.put(mxConstants.STYLE_BENDABLE, 0);
        
	// insert style
	mxStylesheet defaultStyle = new mxStylesheet();
	defaultStyle.setDefaultVertexStyle(vertexStyle);
        defaultStyle.setDefaultEdgeStyle(edgeStyle);

        // create a visualization using JGraph, via an adapter
	jGraphXAdapter.setStylesheet(defaultStyle);
	jGraphXAdapter.setLabelsVisible(false);
        
        add(BorderLayout.CENTER, centerView);
    }
    
    private void arrangeLayout() {
        mxOrganicLayout layout = new mxOrganicLayout(jGraphXAdapter);
        layout.execute(jGraphXAdapter.getDefaultParent());
    }
    
    /**
     * 
     */
    private void updateStatusBar() {
        verticesCount.setText(" " + graphT.vertexSet().size() + "  ");
        edgesCount.setText(" " + graphT.edgeSet().size() + "  ");
        int correct = graphT.correctlyColoredEdges();
        int total = graphT.totalEdges();
        
        successCount.setText(" " + correct + "/" + total + " edges, " + ((((double)correct)/total)*100) + "%  ");
    }

    private void defineGraphBehavior() {
        jGraphXAdapter.setCellsDisconnectable(false);
        centerView.setConnectable(false);
    }
    
    /**
     * 
     */
    private class ButtonListener implements ActionListener {
        /**
         * 
         */
        boolean editToggle = true;
        
        /**
         * 
         * @param event 
         */
        @Override
        public void actionPerformed(ActionEvent event) {
            switch (event.getActionCommand()) {
                case NEW_BUTTON_TEXT:
                    Vertex.removeListener(jGraphXAdapter);
                    createGraph();
                    runDSAaButton.setEnabled(true);
                    runThreadedDSAaButton.setEnabled(true);
                    runThreadedDSAbButton.setEnabled(true);
                    updateStatusBar();
                    jGraphXAdapter.setEnabled(true);
                    jGraphXAdapter.refresh();
                    break;
                case RUN_DSA_A_BUTTON_TEXT:
                    jGraphXAdapter.setEnabled(false);
                    graphT.runDSA_A();
                    jGraphXAdapter.setEnabled(true);
                    runDSAaButton.setEnabled(false);
                    runThreadedDSAaButton.setEnabled(false);
                    runThreadedDSAbButton.setEnabled(false);
                    jGraphXAdapter.repaint();
                    updateStatusBar();
                    jGraphXAdapter.refresh();
                    break;
                case RUN_THREADED_DSA_A_BUTTON_TEXT:
                    jGraphXAdapter.setEnabled(false);
                    try {
                        graphT.runThreadedDSA_A();
                    } catch (InterruptedException ex) {
                        Logger.getLogger(GraphColorer.class.getName()).log(Level.SEVERE, null, ex);
                    }
                    jGraphXAdapter.setEnabled(true);
                    runDSAaButton.setEnabled(false);
                    runThreadedDSAaButton.setEnabled(false);
                    runThreadedDSAbButton.setEnabled(false);
                    jGraphXAdapter.repaint();
                    updateStatusBar();
                    jGraphXAdapter.refresh();
                    break;
                case RUN_THREADED_DSA_B_BUTTON_TEXT:
                    jGraphXAdapter.setEnabled(false);
                    try {
                        graphT.runThreadedDSA_B();
                    } catch (InterruptedException ex) {
                        Logger.getLogger(GraphColorer.class.getName()).log(Level.SEVERE, null, ex);
                    }
                    jGraphXAdapter.setEnabled(true);
                    runDSAaButton.setEnabled(false);
                    runThreadedDSAaButton.setEnabled(false);
                    runThreadedDSAbButton.setEnabled(false);
                    jGraphXAdapter.repaint();
                    updateStatusBar();
                    jGraphXAdapter.refresh();
                    break;
                case REFRESH_BUTTON_TEXT:
                    jGraphXAdapter.repaint();
                    jGraphXAdapter.refresh();
                    updateStatusBar();
                    break;
                case EDIT_TOGGLE_BUTTON_TEXT:
                    editToggle = !editToggle;
                    centerView.setEnabled(editToggle);
                    break;                    
            }
        }
    }
}
