/*
 * This class is based on code from PlugableRendererDemo from JUNG framework
 *
 * Copyright by Neuroph Project (c) 2004, the JUNG Project and the Regents of the University of
 * California All rights reserved.
 *
 * This software is open-source under the BSD license; see either "license.txt"
 * or http://jung.sourceforge.net/license.txt for a description.
 *
 * Authors: Danyel Fisher, Joshua O'Madadhain, Tom Nelson
 * Created on Nov 7, 2004
 *
 * Zoran Sevarac
 */

package org.neuroph.easyneurons;

import org.neuroph.core.*;
import org.neuroph.util.NeuralNetworkType;
import org.neuroph.easyneurons.view.RowLayersLayout;
import org.neuroph.easyneurons.view.SquareLayersLayout;
import org.neuroph.easyneurons.view.KohonenLayout;

import java.awt.BasicStroke;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.GradientPaint;
import java.awt.GridBagLayout;
import java.awt.GridLayout;
import java.awt.Paint;
import java.awt.Shape;
import java.awt.Stroke;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.geom.Point2D;
import java.lang.reflect.Constructor;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import javax.swing.AbstractAction;
import javax.swing.AbstractButton;
import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.ButtonGroup;
import javax.swing.DefaultListCellRenderer;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JRadioButton;

import java.awt.BasicStroke;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.GradientPaint;
import java.awt.GridLayout;
import java.awt.Paint;
import java.awt.Shape;
import java.awt.Stroke;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.geom.Point2D;
import java.awt.Point;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import javax.swing.AbstractAction;
import javax.swing.AbstractButton;
import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.ButtonGroup;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JRadioButton;

import org.apache.commons.collections15.Factory;
import org.apache.commons.collections15.Predicate;
import org.apache.commons.collections15.Transformer;
import org.apache.commons.collections15.functors.ConstantTransformer;
import org.apache.commons.collections15.functors.MapTransformer;

import edu.uci.ics.jung.algorithms.generators.random.MixedRandomGraphGenerator;
import edu.uci.ics.jung.algorithms.layout.FRLayout;
import edu.uci.ics.jung.algorithms.layout.GraphElementAccessor;
import edu.uci.ics.jung.algorithms.layout.Layout;
import edu.uci.ics.jung.algorithms.layout.DAGLayout;
import edu.uci.ics.jung.algorithms.layout.KKLayout;
import edu.uci.ics.jung.algorithms.layout.SpringLayout;
import edu.uci.ics.jung.algorithms.layout.CircleLayout;
import edu.uci.ics.jung.algorithms.layout.TreeLayout;
import edu.uci.ics.jung.algorithms.layout.StaticLayout;


import edu.uci.ics.jung.algorithms.scoring.VoltageScorer;
import edu.uci.ics.jung.algorithms.scoring.util.VertexScoreTransformer;
import edu.uci.ics.jung.algorithms.util.SelfLoopEdgePredicate;
import edu.uci.ics.jung.graph.Graph;
import edu.uci.ics.jung.graph.DirectedSparseGraph;
import edu.uci.ics.jung.graph.DirectedGraph;
import edu.uci.ics.jung.graph.util.Context;
import edu.uci.ics.jung.graph.util.EdgeType;
import edu.uci.ics.jung.visualization.GraphZoomScrollPane;
import edu.uci.ics.jung.visualization.RenderContext;
import edu.uci.ics.jung.visualization.VisualizationViewer;
import edu.uci.ics.jung.visualization.control.AbstractPopupGraphMousePlugin;
import edu.uci.ics.jung.visualization.control.CrossoverScalingControl;
import edu.uci.ics.jung.visualization.control.DefaultModalGraphMouse;
import edu.uci.ics.jung.visualization.control.EditingModalGraphMouse;
import edu.uci.ics.jung.visualization.control.EditingPopupGraphMousePlugin;
import edu.uci.ics.jung.visualization.control.ScalingControl;
import edu.uci.ics.jung.visualization.decorators.AbstractVertexShapeTransformer;
import edu.uci.ics.jung.visualization.decorators.EdgeShape;
import edu.uci.ics.jung.visualization.decorators.GradientEdgePaintTransformer;
import edu.uci.ics.jung.visualization.decorators.NumberFormattingTransformer;
import edu.uci.ics.jung.visualization.decorators.PickableEdgePaintTransformer;
import edu.uci.ics.jung.visualization.decorators.ToStringLabeller;
import edu.uci.ics.jung.visualization.picking.PickedInfo;
import edu.uci.ics.jung.visualization.picking.PickedState;
import edu.uci.ics.jung.visualization.renderers.Renderer;

import edu.uci.ics.jung.visualization.layout.LayoutTransition;
import edu.uci.ics.jung.visualization.util.Animator;
import edu.uci.ics.jung.graph.DelegateForest;
import edu.uci.ics.jung.graph.DelegateTree;
import edu.uci.ics.jung.graph.Tree;


import java.text.NumberFormat;
import java.text.DecimalFormat;
import org.neuroph.easyneurons.view.NetworkLayout;


/**
 * @author Zoran Sevarac
 */
public class NeuralGraphRenderer implements ActionListener {
	protected JCheckBox v_color;
	protected JCheckBox e_color;
	protected JCheckBox v_stroke;
	protected JCheckBox e_uarrow_pred;
	protected JCheckBox e_darrow_pred;
	protected JCheckBox v_shape;
	protected JCheckBox v_size;
	protected JCheckBox v_aspect;
	protected JCheckBox v_labels;
	protected JRadioButton e_line;
	protected JRadioButton e_bent;
	protected JRadioButton e_wedge;
	protected JRadioButton e_quad;
	protected JRadioButton e_cubic;
	protected JCheckBox e_labels;
	protected JCheckBox font;
	protected JCheckBox e_show_d;
	protected JCheckBox e_show_u;
	protected JCheckBox v_small;
	protected JCheckBox zoom_at_mouse;
	protected JCheckBox fill_edges;

	protected JRadioButton layout_FR;
	protected JRadioButton layout_KK;
	protected JRadioButton layout_Circle;
	protected JComboBox layoutCombo;

	protected JRadioButton no_gradient;
	// protected JRadioButton gradient_absolute;
	protected JRadioButton gradient_relative;

	protected static final int GRADIENT_NONE = 0;
	protected static final int GRADIENT_RELATIVE = 1;
	// protected static final int GRADIENT_ABSOLUTE = 2;
	protected static int gradient_level = GRADIENT_NONE;

        protected SeedFillColor<Neuron> seedFillColor;
        protected SeedDrawColor<Neuron> seedDrawColor;
        protected EdgeWeightStrokeFunction<Connection> ewcs;
        protected VertexStrokeHighlight<Neuron,Connection> vsh;
        protected Transformer<Neuron,String> vs;
        protected Transformer<Neuron,String> vs_none;
        protected Transformer<Connection,String> es;
        protected Transformer<Connection,String> es_none;
        protected VertexFontTransformer<Neuron> vff;
        protected EdgeFontTransformer<Connection> eff;
        protected VertexShapeSizeAspect<Neuron,Connection> vssa;
        protected DirectionDisplayPredicate<Neuron,Connection> show_edge;
        protected DirectionDisplayPredicate<Neuron,Connection> show_arrow;
        protected VertexDisplayPredicate<Neuron,Connection> show_vertex;
        protected Predicate<Context<Graph<Neuron,Connection>,Connection>> self_loop;
        protected GradientPickedEdgePaintFunction<Neuron,Connection> edgeDrawPaint;
        protected GradientPickedEdgePaintFunction<Neuron,Connection> edgeFillPaint;


	protected final static Object ACTIVATION_KEY = "neuron_activation";
	protected final static Object WEIGHT_KEY = "connection_weight";

	protected final static Object TRANSPARENCY = "transparency";

	protected VisualizationViewer vv;
	protected DefaultModalGraphMouse<Neuron, Connection> graphMouse;
        protected Set<Neuron> seedVertices = new HashSet<Neuron>();
	//protected EditingModalGraphMouse<Neuron, Connection> graphMouse;

	protected Transformer affineTransformer;
        protected Transformer<Neuron, Double> voltages;

        protected Map<Neuron,Connection> transparency = new HashMap<Neuron,Connection>();
        protected Map<Connection,Number> edge_weight = new HashMap<Connection,Number>();

	protected Graph<Neuron, Connection> graph;
	protected NeuralNetwork neuralNet;
	protected JPanel graphPanel;

	protected Layout<Neuron, Connection> layout;
        protected Transformer<Neuron, Point2D> staticTranformer;

        private NetworkLayout netLayout;


	public NeuralGraphRenderer(NeuralNetwork neuralNet) {
		this.neuralNet = neuralNet;

		v_labels = new JCheckBox("show activation levels");
		v_labels.addActionListener(this);

		e_labels = new JCheckBox("show weights");
		e_labels.addActionListener(this);

                staticTranformer = new Transformer<Neuron, Point2D>() {
                    int layerY = 70;

                    @Override
                    public Point2D transform(Neuron n) {
                        int x, y;

                        Dimension size = layout.getSize();
                        x = size.width / 2;
                        y = layerY;

                        Layer parentLayer = n.getParentLayer();
                        int parentLayerSize = parentLayer.getNeuronsCount();
                        int neuronIdx = parentLayer.indexOf(n);
                        int layerIdx = parentLayer.getParentNetwork().indexOf(parentLayer);
                        int layerLayout = netLayout.getLayerLayout(layerIdx);

                        y  = (layerIdx+1)*70;

                        if (layerLayout == NetworkLayout.ROW_LAYOUT) {
                            if (parentLayerSize%2 !=0 ) { // neparni broj neurona
                                if (neuronIdx <= (parentLayerSize/2) ) {
                                   x = size.width / 2 - 70 * ((parentLayerSize/2) - neuronIdx) - 35;
                                } else {
                                   x = size.width / 2 + 70 * (neuronIdx - (parentLayerSize/2)-1) + 35;
                                }
                            } else { // parni broj neurona
                                if (neuronIdx < (parentLayerSize/2) ) {
                                   x = size.width / 2 - 70 * ((parentLayerSize/2) - neuronIdx);
                                } else {
                                   x = size.width / 2 + 70 * (neuronIdx - (parentLayerSize/2));
                                }
                            }

                           if (y>layerY) layerY=y;

                        } else { // NetworkLayout.SQUARE_LAYOUT
                            int rowNeurons = (int)Math.sqrt(parentLayerSize);
                            y = layerY + ((neuronIdx/rowNeurons)+1)* 70;

                            if (rowNeurons%2 !=0 ) { // neparni broj neurona u redu
                                if (((neuronIdx%rowNeurons)%2 ) <= (rowNeurons/2) ) {
                                   x = size.width / 2 - 70 * ((rowNeurons/2) - (neuronIdx%rowNeurons)) -35;
                                } else {
                                   x = size.width / 2 + 70 * ((rowNeurons/2) - (neuronIdx%rowNeurons)) -35;
                                }
                            } else { // parni broj neurona
                                if (((neuronIdx%rowNeurons)%2 ) <= (rowNeurons/2) ) {
                                   x = size.width / 2 - 70 * ((rowNeurons/2) - (neuronIdx%rowNeurons));
                                } else {
                                   x = size.width / 2 + 70 * ((rowNeurons/2) - (neuronIdx%rowNeurons));
                                }
                            }
                        }

                        Point2D neuronLocation = new Point(x, y);
                        return neuronLocation;
                    }
                };

		graph = createGraph();
                layout = new StaticLayout<Neuron, Connection>(graph, staticTranformer);

                try {
		vv = new VisualizationViewer<Neuron, Connection>(layout);
                } catch(Exception ex) {
                  ex.printStackTrace();
                }
		createGraphPanel();
	}

	public JPanel getGraphPanel() {
		return this.graphPanel;
	}

	public void createGraphPanel() {
		graph = createGraph();

                setNetLayout(neuralNet.getNetworkType());

               //layout = new FRLayout<Neuron, Connection>(graph);
                layout = new StaticLayout<Neuron, Connection>(graph, staticTranformer);
		vv = new VisualizationViewer<Neuron, Connection>(layout);
		// add Shape based pick support
//		vv.setPickSupport(new ShapePickSupport());
//		PickedState picked_state = vv.getPickedState();
                PickedState<Neuron> picked_state = vv.getPickedVertexState();

//		affineTransformer = vv.getLayoutTransformer();

		// create decorators
                seedFillColor = new SeedFillColor<Neuron>(picked_state);
                seedDrawColor = new SeedDrawColor<Neuron>(picked_state);
                ewcs =
                    new EdgeWeightStrokeFunction<Connection>(edge_weight);
                vsh = new VertexStrokeHighlight<Neuron,Connection>(graph, picked_state);
                vff = new VertexFontTransformer<Neuron>();
                eff = new EdgeFontTransformer<Connection>();
                vs_none = new ConstantTransformer(null);
                es_none = new ConstantTransformer(null);
                vssa = new VertexShapeSizeAspect<Neuron,Connection>(graph, voltages);
                show_edge = new DirectionDisplayPredicate<Neuron,Connection>(true, true);
                show_arrow = new DirectionDisplayPredicate<Neuron,Connection>(true, false);
                show_vertex = new VertexDisplayPredicate<Neuron,Connection>(false);

                vv.getRenderContext().setVertexFontTransformer(vff);
                vv.getRenderContext().setVertexShapeTransformer(vssa);
                vv.getRenderContext().setVertexStrokeTransformer(vsh);

                vv.getRenderContext().setEdgeFontTransformer(eff);
                vv.getRenderContext().setEdgeStrokeTransformer(ewcs);

                vv.getRenderContext().setEdgeShapeTransformer(new EdgeShape.Line<Neuron,Connection>());

                this.graphPanel = new JPanel();
		graphPanel.setLayout(new BorderLayout());

		vv.setBackground(Color.white);

                GraphZoomScrollPane scrollPane = new GraphZoomScrollPane(vv);
		scrollPane.setPreferredSize(new Dimension(930, 400));
		graphPanel.add(scrollPane);

//        Factory<Neuron> vertexFactory = new VertexFactory();
//        Factory<Connection> edgeFactory = new EdgeFactory();
//
//        graphMouse =  new EditingModalGraphMouse(vv.getRenderContext(),
//                                                 vertexFactory, edgeFactory);

        graphMouse = new DefaultModalGraphMouse<Neuron, Connection>();
        vv.setGraphMouse(graphMouse);
        graphMouse.add(new PopupGraphMousePlugin());

        vssa.setScaling(false);

        vv.setVertexToolTipTransformer(new VoltageTips<Number>());
        addControls(graphPanel);
	}

	/**
	 * Generates a mixed-mode random graph, runs VoltageRanker on it, and
	 * returns the resultant graph.
	 */
	public Graph createGraph() {

		Graph<Neuron, Connection> g = new DirectedSparseGraph<Neuron, Connection>();

		Iterator<Layer> li = this.neuralNet.getLayersIterator();
		while (li.hasNext()) {
			Layer l = li.next();
			Iterator<Neuron> ni = l.getNeuronsIterator();
			while (ni.hasNext()) {
				Neuron neuron = ni.next();
				g.addVertex(neuron);

				Iterator<Connection> ci = neuron.getInputConnections().iterator();
				while (ci.hasNext()) {
					Connection connection = ci.next();
					Neuron fromNeuron = connection.getConnectedNeuron();
					g.addEdge(connection, fromNeuron, neuron);

                                        // used for highlighting weights
                                        edge_weight.put(connection, connection.getWeight().getValue());
				}
			}
		}

                voltages = new Transformer<Neuron, Double>() {
                              @Override
                              public Double transform(Neuron n) {

                                return n.getOutput();
                            }
                };

                vs = new Transformer<Neuron, String>() {
                            @Override
                            public String transform(Neuron n) {
                                NumberFormat numberFormat = DecimalFormat.getNumberInstance();
                                numberFormat.setMaximumFractionDigits(4);

                                return numberFormat.format(n.getOutput());
                            } };

                es = new Transformer<Connection, String>() {
                            @Override
                            public String transform(Connection c) {
                                NumberFormat numberFormat = DecimalFormat.getNumberInstance();
                                numberFormat.setMaximumFractionDigits(5);

                                return numberFormat.format(c.getWeight().getValue());
                            } };

		return g;
	}

	/**
	 * @param jp
	 *            panel to which controls will be added
	 */
	protected void addControls(final JPanel jp) {
		final JPanel control_panel = new JPanel();
		jp.add(control_panel, BorderLayout.EAST);

		control_panel.setLayout(new GridBagLayout());

		java.awt.GridBagConstraints gridBagConstraints = new java.awt.GridBagConstraints();
		gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
		gridBagConstraints.insets = new java.awt.Insets(5, 5, 0, 5);
		gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST;

		// control_panel.setBackground(new Color(00, 255, 00));

		final Box vertex_panel = Box.createVerticalBox();
		vertex_panel.setBorder(BorderFactory.createTitledBorder("Neurons"));
		final Box edge_panel = Box.createVerticalBox();
		edge_panel.setBorder(BorderFactory.createTitledBorder("Connections"));

		final Box edges_and_vertex_panel = Box.createVerticalBox();
		// Box zoom_and_mode_panel = Box.createVerticalBox();
		// zoom_and_mode_panel.setBorder(BorderFactory.createTitledBorder("Other controls"));
//		final Box layout_panel = Box.createVerticalBox();
//		layout_panel.setBorder(BorderFactory.createTitledBorder("Layout"));
		// JPanel zoom_and_mode_panel = new JPanel();
		// zoom_and_mode_panel.setLayout(new GridBagLayout() );
		// //GridLayout(3,1)
		// zoom_and_mode_panel.setBackground(new Color(0,255,0));

		edges_and_vertex_panel.add(vertex_panel);
		edges_and_vertex_panel.add(edge_panel);
		// edges_and_vertex_panel.add(layout_panel);

		gridBagConstraints.gridx = 0;
		gridBagConstraints.gridy = 0;
		control_panel.add(edges_and_vertex_panel, gridBagConstraints);

		gridBagConstraints.gridx = 0;
		gridBagConstraints.gridy = 1;
//		control_panel.add(layout_panel, gridBagConstraints);

		// originalni layout
		// control_panel.add(vertex_panel, BorderLayout.WEST);
		// control_panel.add(edge_panel, BorderLayout.EAST);
		// control_panel.add(edges_and_vertex_panel, BorderLayout.CENTER);

		// set up vertex controls
		v_color = new JCheckBox("vertex seed coloring");
		v_color.addActionListener(this);
//		v_stroke = new JCheckBox("<html>stroke highlight selected</html>");
//		v_stroke.addActionListener(this);

		v_shape = new JCheckBox("vertex degree shapes");
		v_shape.addActionListener(this);
		v_size = new JCheckBox("activation size");
		v_size.addActionListener(this);
		v_size.setSelected(false);
		v_aspect = new JCheckBox("vertex degree ratio stretch");
		v_aspect.addActionListener(this);
		v_small = new JCheckBox("filter vertices of degree < "
				+ VertexDisplayPredicate.MIN_DEGREE);
		v_small.addActionListener(this);

		// vertex_panel.add(v_color);

		vertex_panel.add(v_labels);
		// vertex_panel.add(v_shape);
		vertex_panel.add(v_size);
		// vertex_panel.add(v_aspect);
		// vertex_panel.add(v_small);
		// vertex_panel.add(v_stroke);

		// set up edge controls
		// JPanel gradient_panel = new JPanel(new GridLayout(1, 0));
		// gradient_panel.setBorder(BorderFactory.createTitledBorder("Edge paint"));
		// no_gradient = new JRadioButton("Solid color");
		// no_gradient.addActionListener(this);
		// no_gradient.setSelected(true);
		// // gradient_absolute = new JRadioButton("Absolute gradient");
		// // gradient_absolute.addActionListener(this);
		// gradient_relative = new JRadioButton("Gradient");
		// gradient_relative.addActionListener(this);
		// ButtonGroup bg_grad = new ButtonGroup();
		// bg_grad.add(no_gradient);
		// bg_grad.add(gradient_relative);
		// //bg_grad.add(gradient_absolute);
		// gradient_panel.add(no_gradient);
		// //gradientGrid.add(gradient_absolute);
		// gradient_panel.add(gradient_relative);

//		JPanel shape_panel = new JPanel(new GridLayout(3, 2));
//		shape_panel.setBorder(BorderFactory
//				.createTitledBorder("Connection shape"));
//		e_line = new JRadioButton("line");
//		e_line.addActionListener(this);
//		e_line.setSelected(true);
//		// e_bent = new JRadioButton("bent line");
//		// e_bent.addActionListener(this);
//		e_wedge = new JRadioButton("wedge");
//		e_wedge.addActionListener(this);
//		e_quad = new JRadioButton("quad curve");
//		e_quad.addActionListener(this);
//		e_cubic = new JRadioButton("cubic curve");
//		e_cubic.addActionListener(this);
//		ButtonGroup bg_shape = new ButtonGroup();
//		bg_shape.add(e_line);
//		// bg.add(e_bent);
//		bg_shape.add(e_wedge);
//		bg_shape.add(e_quad);
//		bg_shape.add(e_cubic);
//		shape_panel.add(e_line);
//		// shape_panel.add(e_bent);
//		// shape_panel.add(e_wedge);
//		shape_panel.add(e_quad);
//		shape_panel.add(e_cubic);
//		// fill_edges = new JCheckBox("fill edge shapes");
//		// fill_edges.setSelected(false);
//		// fill_edges.addActionListener(this);
//		// shape_panel.add(fill_edges);
//		shape_panel.setOpaque(true);
		e_color = new JCheckBox("weight highlighting");
		e_color.addActionListener(this);
		e_uarrow_pred = new JCheckBox("undirected");
		e_uarrow_pred.addActionListener(this);
		e_darrow_pred = new JCheckBox("directed");
		e_darrow_pred.addActionListener(this);
		e_darrow_pred.setSelected(true);
		// JPanel arrow_panel = new JPanel(new GridLayout(1,0));
		// arrow_panel.setBorder(BorderFactory.createTitledBorder("Show arrows"));
		// arrow_panel.add(e_uarrow_pred);
		// arrow_panel.add(e_darrow_pred);

		e_show_d = new JCheckBox("directed");
		e_show_d.addActionListener(this);
		e_show_d.setSelected(true);
		e_show_u = new JCheckBox("undirected");
		e_show_u.addActionListener(this);
		e_show_u.setSelected(true);
		JPanel show_edge_panel = new JPanel(new GridLayout(1, 0));
		show_edge_panel.setBorder(BorderFactory
				.createTitledBorder("Show edges"));
		show_edge_panel.add(e_show_u);
		show_edge_panel.add(e_show_d);

//		shape_panel.setAlignmentX(Component.LEFT_ALIGNMENT);
//		edge_panel.add(shape_panel);
		// gradient_panel.setAlignmentX(Component.LEFT_ALIGNMENT);
		// edge_panel.add(gradient_panel);
		// show_edge_panel.setAlignmentX(Component.LEFT_ALIGNMENT);
		// edge_panel.add(show_edge_panel);
		// arrow_panel.setAlignmentX(Component.LEFT_ALIGNMENT);
		// edge_panel.add(arrow_panel);

		e_color.setAlignmentX(Component.LEFT_ALIGNMENT);
		edge_panel.add(e_color);
		e_labels.setAlignmentX(Component.LEFT_ALIGNMENT);
		edge_panel.add(e_labels);

//		Class<?>[] combos = getLayoutOptions();
//		final JComboBox layoutCombo = new JComboBox(combos);
//		// use a renderer to shorten the layout name presentation
//		layoutCombo.setRenderer(new DefaultListCellRenderer() {
//			public Component getListCellRendererComponent(JList list,
//					Object value, int index, boolean isSelected,
//					boolean cellHasFocus) {
//				String valueString = value.toString();
//				valueString = valueString.substring(valueString
//						.lastIndexOf('.') + 1);
//				return super.getListCellRendererComponent(list, valueString,
//						index, isSelected, cellHasFocus);
//			}
//		});
//		layoutCombo.addActionListener(new LayoutChooser(layoutCombo, vv));
//// UNDO:		layoutCombo.setSelectedItem(FRLayout.class);
//		layout_panel.add(layoutCombo);

		// set up zoom controls
		zoom_at_mouse = new JCheckBox(
				"<html><center>zoom at mouse<p>(wheel only)</center></html>");
		zoom_at_mouse.addActionListener(this);

		final ScalingControl scaler = new CrossoverScalingControl();

		JButton plus = new JButton("+");
		plus.addActionListener(new ActionListener() {
                        @Override
			public void actionPerformed(ActionEvent e) {
				scaler.scale(vv, 1.1f, vv.getCenter());
			}
		});
		JButton minus = new JButton("-");
		minus.addActionListener(new ActionListener() {
                        @Override
			public void actionPerformed(ActionEvent e) {
				scaler.scale(vv, 1 / 1.1f, vv.getCenter());
			}
		});

		JPanel zoomPanel = new JPanel();
		zoomPanel.setAlignmentX(Component.RIGHT_ALIGNMENT);
		zoomPanel.setBorder(BorderFactory.createTitledBorder("Zoom"));
		zoomPanel.add(plus);
		zoomPanel.add(minus);

		gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
		gridBagConstraints.insets = new java.awt.Insets(5, 5, 0, 5);
		gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST;

		gridBagConstraints.gridx = 0;
		gridBagConstraints.gridy = 0;

		JComboBox modeBox = graphMouse.getModeComboBox();
		modeBox.setAlignmentX(Component.RIGHT_ALIGNMENT);

		JPanel modePanel = new JPanel(new BorderLayout()) {
			@Override
			public Dimension getMaximumSize() {
				return getPreferredSize();
			}
		};
		modePanel.setBorder(BorderFactory.createTitledBorder("Mouse Mode"));
		modePanel.add(modeBox);

		gridBagConstraints.gridx = 0;
		gridBagConstraints.gridy = 2;
		control_panel.add(modePanel, gridBagConstraints);

		gridBagConstraints.gridx = 0;
		gridBagConstraints.gridy = 3;
		control_panel.add(zoomPanel, gridBagConstraints);

		// add font control to center panel
		font = new JCheckBox("bold text");
		font.addActionListener(this);
		gridBagConstraints.gridx = 0;
		gridBagConstraints.gridy = 4;
		control_panel.add(font, gridBagConstraints);
	}

	private void setNetLayout(NeuralNetworkType type) {

                if (type == null){
                    this.netLayout = new RowLayersLayout();
                    return;
                }

		switch (type) {
		case ADALINE:
			this.netLayout = new RowLayersLayout();
			break;
		case PERCEPTRON:
			this.netLayout = new RowLayersLayout();
			break;
		case MULTI_LAYER_PERCEPTRON:
			this.netLayout = new RowLayersLayout();
			break;
		case HOPFIELD:
			this.netLayout = new SquareLayersLayout();
			break;
		case KOHONEN:
			this.netLayout = new KohonenLayout();
			break;
		case NEURO_FUZZY_REASONER:
			this.netLayout = new RowLayersLayout();
			break;

		default:
			this.netLayout = new RowLayersLayout();
			break;
		}
	}

	private static Class<?>[] getLayoutOptions() {
		List<Class<?>> layouts = new ArrayList<Class<?>>();
		layouts.add(DAGLayout.class);
                layouts.add(KKLayout.class);
		layouts.add(FRLayout.class);
		layouts.add(CircleLayout.class);
                layouts.add(TreeLayout.class);
		return layouts.toArray(new Class[0]);
	}

        private final static class VertexFontTransformer<V>
            implements Transformer<V,Font>
        {
            protected boolean bold = false;
            Font f = new Font("Helvetica", Font.PLAIN, 12);
            Font b = new Font("Helvetica", Font.BOLD, 12);

            public void setBold(boolean bold)
            {
                this.bold = bold;
            }

            @Override
            public Font transform(V v)
            {
                if (bold)
                    return b;
                else
                    return f;
            }
        }

        private final static class EdgeFontTransformer<E>
            implements Transformer<E,Font>
        {
        protected boolean bold = false;
        Font f = new Font("Helvetica", Font.PLAIN, 12);
        Font b = new Font("Helvetica", Font.BOLD, 12);

        public void setBold(boolean bold)
        {
            this.bold = bold;
        }

        @Override
        public Font transform(E e)
        {
            if (bold)
                return b;
            else
                return f;
        }
    }


        @Override
	public void actionPerformed(ActionEvent e) {
            AbstractButton source = (AbstractButton)e.getSource();

            if (source == e_line)
            {
                if(source.isSelected())
                {
                    vv.getRenderContext().setEdgeShapeTransformer(new EdgeShape.Line<Neuron,Connection>());
                }
            }
            else if (source == e_quad)
            {
                if(source.isSelected())
                {
                    vv.getRenderContext().setEdgeShapeTransformer(new EdgeShape.QuadCurve<Neuron,Connection>());
                }
            }
            else if (source == e_cubic)
            {
                if(source.isSelected())
                {
                    vv.getRenderContext().setEdgeShapeTransformer(new EdgeShape.CubicCurve<Neuron,Connection>());
                }
            }
            else if (source == v_labels)
            {
                if (source.isSelected())
                    vv.getRenderContext().setVertexLabelTransformer(vs);
                else
                    vv.getRenderContext().setVertexLabelTransformer(vs_none);
            }
            else if (source == e_labels)
            {
                if (source.isSelected())
                    vv.getRenderContext().setEdgeLabelTransformer(es);
                else
                    vv.getRenderContext().setEdgeLabelTransformer(es_none);
            }
            else if (source == font)
            {
                vff.setBold(source.isSelected());
                eff.setBold(source.isSelected());
            }
            else if (source == e_color)
            {
                ewcs.setWeighted(source.isSelected());
            }
            else if (source == v_size)
            {
                vssa.setScaling(source.isSelected());
            }
            else if (source == v_stroke)
            {
                vsh.setHighlight(source.isSelected());
            }
            vv.repaint();
        }

   /**
     * a GraphMousePlugin that offers popup
     * menu support
     */
    protected class PopupGraphMousePlugin extends AbstractPopupGraphMousePlugin
    	implements MouseListener {

        public PopupGraphMousePlugin() {
            this(MouseEvent.BUTTON3_MASK);
        }
        public PopupGraphMousePlugin(int modifiers) {
            super(modifiers);
        }

        /**
         * If this event is over a Vertex, pop up a menu to
         * allow the user to increase/decrease the voltage
         * attribute of this Vertex
         * @param e
         */
        @SuppressWarnings("unchecked")
        @Override
        protected void handlePopup(MouseEvent e) {
            final VisualizationViewer<Neuron,Connection> vv =
                (VisualizationViewer<Neuron,Connection>)e.getSource();
            Point2D p = e.getPoint();//vv.getRenderContext().getBasicTransformer().inverseViewTransform(e.getPoint());

            GraphElementAccessor<Neuron,Connection> pickSupport = vv.getPickSupport();
//            if(pickSupport != null) {
//                final Neuron v = pickSupport.getVertex(vv.getGraphLayout(), p.getX(), p.getY());
//                if(v != null) {
//                    JPopupMenu popup = new JPopupMenu();
//                    popup.add(new AbstractAction("Delete Neuron") {
//                        @Override
//                        public void actionPerformed(ActionEvent e) {
////                        	Double value = Math.min(1, transparency.get(v).doubleValue()+0.1);
////                        	transparency.put(v, value);
////                        	transparency.put(v, )transparency.get(v);
////                            MutableDouble value = (MutableDouble)transparency.getNumber(v);
////                            value.setDoubleValue(Math.min(1, value.doubleValue() + 0.1));
//                            vv.repaint();
//                        }
//                    });
//                    popup.add(new AbstractAction("Neuron Properties"){
//                        @Override
//                        public void actionPerformed(ActionEvent e) {
////                        	Double value = Math.max(0,
////                            		transparency.get(v).doubleValue()-0.1);
////                            	transparency.put(v, value);
////                            MutableDouble value = (MutableDouble)transparency.getNumber(v);
////                            value.setDoubleValue(Math.max(0, value.doubleValue() - 0.1));
//                            vv.repaint();
//                        }
//                    });
//                    popup.show(vv, e.getX(), e.getY());
//                } else {
//                    final Connection edge = pickSupport.getEdge(vv.getGraphLayout(), p.getX(), p.getY());
//                    if(edge != null) {
//                        JPopupMenu popup = new JPopupMenu();
//                        popup.add(new AbstractAction(edge.toString()) {
//                            @Override
//                            public void actionPerformed(ActionEvent e) {
//                                System.err.println("got "+edge);
//                            }
//                        });
//                        popup.show(vv, e.getX(), e.getY());
//
//                    }
//                }
//            }
        }
    }

    /**
     * Controls the shape, size, and aspect ratio for each vertex.
     *
     * @author Joshua O'Madadhain
     */
    private final static class VertexShapeSizeAspect<V,E>
    extends AbstractVertexShapeTransformer <V>
    implements Transformer<V,Shape>  {

        protected boolean stretch = false;
        protected boolean scale = false;
        protected boolean funny_shapes = false;
        protected Transformer<V,Double> voltages;
        protected Graph<V,E> graph;
//        protected AffineTransform scaleTransform = new AffineTransform();

        public VertexShapeSizeAspect(Graph<V,E> graphIn, Transformer<V,Double> voltagesIn)
        {
        	this.graph = graphIn;
            this.voltages = voltagesIn;
            setSizeTransformer(new Transformer<V,Integer>() {
                                @Override
				public Integer transform(V v) {
                                    if (scale)
                                        return (int)(voltages.transform(v) * 30) + 20;
                                    else
                                        return 20;
				}});

            setAspectRatioTransformer(new Transformer<V,Float>() {
                                @Override
				public Float transform(V v) {
                                    if (stretch) {
                                        return (float)(graph.inDegree(v) + 1) /
                                                (graph.outDegree(v) + 1);
                                    } else {
                                        return 1.0f;
                                    }
				}});
               }

		public void setStretching(boolean stretch)
                {
                    this.stretch = stretch;
                }

        public void setScaling(boolean scale)
        {
            this.scale = scale;
        }

        public void useFunnyShapes(boolean use)
        {
            this.funny_shapes = use;
        }

        @Override
        public Shape transform(V v)
        {
            if (funny_shapes)
            {
                if (graph.degree(v) < 5)
                {
                    int sides = Math.max(graph.degree(v), 3);
                    return factory.getRegularPolygon(v, sides);
                }
                else
                    return factory.getRegularStar(v, graph.degree(v));
            }
            else
                return factory.getEllipse(v);
        }
    }

    private final class SeedFillColor<V> implements Transformer<V,Paint>
    {
        protected PickedInfo<V> pi;
        protected final static float dark_value = 0.8f;
        protected final static float light_value = 0.2f;
        protected boolean seed_coloring;

        public SeedFillColor(PickedInfo<V> pi)
        {
            this.pi = pi;
            seed_coloring = false;
        }

        public void setSeedColoring(boolean b)
        {
            this.seed_coloring = b;
        }

//        public Paint getDrawPaint(V v)
//        {
//            return Color.BLACK;
//        }

        @Override
        public Paint transform(V v)
        {
            float alpha = (float)transparency.get(v).getWeight().getValue();
            if (pi.isPicked(v))
            {
                return new Color(1f, 1f, 0, alpha);
            }
            else
            {
                if (seed_coloring && seedVertices.contains(v))
                {
                    Color dark = new Color(0, 0, dark_value, alpha);
                    Color light = new Color(0, 0, light_value, alpha);
                    return new GradientPaint( 0, 0, dark, 10, 0, light, true);
                }
                else
                    return new Color(1f, 0, 0, alpha);
            }

        }
    }

    private final static class EdgeWeightStrokeFunction<E>
    implements Transformer<E,Stroke>
    {
        protected static final Stroke basic = new BasicStroke(1);
        protected static final Stroke heavy = new BasicStroke(2);
        protected static final Stroke dotted = RenderContext.DOTTED;

        protected boolean weighted = false;
        protected Map<E,Number> edge_weight;

        public EdgeWeightStrokeFunction(Map<E,Number> edge_weight)
        {
            this.edge_weight = edge_weight;
        }

        public void setWeighted(boolean weighted)
        {
            this.weighted = weighted;
        }

        @Override
        public Stroke transform(E e)
        {
            if (weighted)
            {
                if (drawHeavy(e))
                    return heavy;
                else
                    return dotted;
            }
            else
                return basic;
        }

        protected boolean drawHeavy(E e)
        {
            double value = edge_weight.get(e).doubleValue();
            if (value > 0.7)
                return true;
            else
                return false;
        }

    }

    private final static class VertexStrokeHighlight<V,E> implements
    Transformer<V,Stroke>
    {
        protected boolean highlight = false;
        protected Stroke heavy = new BasicStroke(5);
        protected Stroke medium = new BasicStroke(3);
        protected Stroke light = new BasicStroke(1);
        protected PickedInfo<V> pi;
        protected Graph<V,E> graph;

        public VertexStrokeHighlight(Graph<V,E> graph, PickedInfo<V> pi)
        {
        	this.graph = graph;
            this.pi = pi;
        }

        public void setHighlight(boolean highlight)
        {
            this.highlight = highlight;
        }

        @Override
        public Stroke transform(V v)
        {
            if (highlight)
            {
                if (pi.isPicked(v))
                    return heavy;
                else
                {
                	for(V w : graph.getNeighbors(v)) {
//                    for (Iterator iter = graph.getNeighbors(v)v.getNeighbors().iterator(); iter.hasNext(); )
//                    {
//                        Vertex w = (Vertex)iter.next();
                        if (pi.isPicked(w))
                            return medium;
                    }
                    return light;
                }
            }
            else
                return light;
        }

    }



    private final static class DirectionDisplayPredicate<V,E>
    	implements Predicate<Context<Graph<V,E>,E>>
    	//extends AbstractGraphPredicate<V,E>
    {
        protected boolean show_d;
        protected boolean show_u;

        public DirectionDisplayPredicate(boolean show_d, boolean show_u)
        {
            this.show_d = show_d;
            this.show_u = show_u;
        }

        public void showDirected(boolean b)
        {
            show_d = b;
        }

        public void showUndirected(boolean b)
        {
            show_u = b;
        }

        @Override
        public boolean evaluate(Context<Graph<V,E>,E> context)
        {
        	Graph<V,E> graph = context.graph;
        	E e = context.element;
            if (graph.getEdgeType(e) == EdgeType.DIRECTED && show_d) {
                return true;
            }
            if (graph.getEdgeType(e) == EdgeType.UNDIRECTED && show_u) {
                return true;
            }
            return false;
        }
    }

    private final static class VertexDisplayPredicate<V,E>
    	implements Predicate<Context<Graph<V,E>,V>>
//    	extends  AbstractGraphPredicate<V,E>
    {
        protected boolean filter_small;
        protected final static int MIN_DEGREE = 4;

        public VertexDisplayPredicate(boolean filter)
        {
            this.filter_small = filter;
        }

        public void filterSmall(boolean b)
        {
            filter_small = b;
        }

        @Override
        public boolean evaluate(Context<Graph<V,E>,V> context) {
        	Graph<V,E> graph = context.graph;
        	V v = context.element;
//            Vertex v = (Vertex)arg0;
            if (filter_small)
                return (graph.degree(v) >= MIN_DEGREE);
            else
                return true;
        }
    }

    private final class SeedDrawColor<V> implements Transformer<V,Paint>
    {
        protected PickedInfo<V> pi;
        protected final static float dark_value = 0.8f;
        protected final static float light_value = 0.2f;
        protected boolean seed_coloring;

        public SeedDrawColor(PickedInfo<V> pi)
        {
            this.pi = pi;
            seed_coloring = false;
        }

        public void setSeedColoring(boolean b)
        {
            this.seed_coloring = b;
        }

        @Override
        public Paint transform(V v)
        {
            return Color.BLACK;
        }

//        public Paint getFillPaint(V v)
//        {
//            float alpha = transparency.get(v).floatValue();
//            if (pi.isPicked(v))
//            {
//                return new Color(1f, 1f, 0, alpha);
//            }
//            else
//            {
//                if (seed_coloring && seedVertices.contains(v))
//                {
//                    Color dark = new Color(0, 0, dark_value, alpha);
//                    Color light = new Color(0, 0, light_value, alpha);
//                    return new GradientPaint( 0, 0, dark, 10, 0, light, true);
//                }
//                else
//                    return new Color(1f, 0, 0, alpha);
//            }
//
//        }
    }

    public class GradientPickedEdgePaintFunction<V,E> extends GradientEdgePaintTransformer<V,E>
    {
        private Transformer<E,Paint> defaultFunc;
        protected boolean fill_edge = false;
        Predicate<Context<Graph<V,E>,E>> selfLoop = new SelfLoopEdgePredicate<V,E>();

        public GradientPickedEdgePaintFunction(Transformer<E,Paint> defaultEdgePaintFunction,
                VisualizationViewer<V,E> vv)
        {
            super(Color.WHITE, Color.BLACK, vv);
            this.defaultFunc = defaultEdgePaintFunction;
        }

        public void useFill(boolean b)
        {
            fill_edge = b;
        }

        @Override
        public Paint transform(E e) {
            if (gradient_level == GRADIENT_NONE) {
                return defaultFunc.transform(e);
            } else {
            	return super.transform(e);
            }
        }

        @Override
        protected Color getColor2(E e)
        {
            return vv.getPickedEdgeState().isPicked(e)? Color.CYAN : c2;
        }

//        public Paint getFillPaint(E e)
//        {
//            if (selfLoop.evaluateEdge(vv.getGraphLayout().getGraph(), e) || !fill_edge)
//                return null;
//            else
//                return getDrawPaint(e);
//        }

    }

    public class VoltageTips<E>
    	implements Transformer<Neuron, String> {

        @Override
        public String transform(Neuron vertex) {
           return "Voltage:"+voltages.transform(vertex);
        }
    }

//    class VertexFactory implements Factory<Neuron> {
//
//		public Neuron create() {
//			return new Neuron();
//		}
//    }

//    class EdgeFactory implements Factory<Connection> {
//
//		public Connection create() {
//			return new Connection();
//		}
//    }


        /**
	 *
	 * @author danyelf
	 */

	private final class LayoutChooser implements ActionListener
        {
            private final JComboBox jcb;
            private final VisualizationViewer<Neuron,Connection> vv;

            private LayoutChooser(JComboBox jcb, VisualizationViewer<Neuron,Connection> vv)
            {
                super();
                this.jcb = jcb;
                this.vv = vv;
            }

            @Override
            public void actionPerformed(ActionEvent arg0)
            {
                Object[] constructorArgs = { graph };

                Class<? extends Layout<Neuron,Connection>> layoutC =
                    (Class<? extends Layout<Neuron,Connection>>) jcb.getSelectedItem();
    //            Class lay = layoutC;
                try
                {
                    Constructor<? extends Layout<Neuron, Connection>> constructor = layoutC
                            .getConstructor(new Class[] {Graph.class});
                    Object o = constructor.newInstance(constructorArgs);
                    Layout<Neuron, Connection> l = (Layout<Neuron, Connection>) o;
                    l.setInitializer(vv.getGraphLayout());
                    l.setSize(vv.getSize());

                                    LayoutTransition<Neuron, Connection> lt =
                                            new LayoutTransition<Neuron, Connection>(vv, vv.getGraphLayout(), l);
                                    Animator animator = new Animator(lt);
                                    animator.start();
                                    vv.getRenderContext().getMultiLayerTransformer().setToIdentity();
                                    vv.repaint();

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



}
