/* Copyright (c) 1997-2004
   Ewgenij Gawrilow, Michael Joswig (Technische Universitaet Berlin, Germany)
   http://www.math.tu-berlin.de/polymake,  mailto:polymake@math.tu-berlin.de

   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 2, or (at your option) any
   later version: http://www.gnu.org/licenses/gpl.txt.

   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.

   $Project: polymake $$Id: SpringEmbedderFrame.java,v 1.8 2006/02/17 08:37:40 thilosch Exp $
*/

package de.tuberlin.polymake.graph;

import java.awt.BorderLayout;
import java.awt.Button;
import java.awt.Checkbox;
import java.awt.CheckboxGroup;
import java.awt.Component;
import java.awt.Frame;
import java.awt.GridLayout;
import java.awt.Label;
import java.awt.TextField;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.IOException;
import java.util.Iterator;
import java.util.Properties;
import java.util.Vector;

import javax.swing.JPanel;
import javax.swing.JSplitPane;

import jv.number.PuDouble;
import jv.object.PsDebug;
import jv.object.PsObject;
import jv.object.PsPanel;
import jv.project.PgGeometryIf;
import jv.project.PvPickEvent;
import jv.project.PvPickListenerIf;
import jv.vecmath.PdVector;
import de.tuberlin.polymake.common.EmbeddedGeometries;
import de.tuberlin.polymake.common.JavaviewUtils;
import de.tuberlin.polymake.common.JavaviewControl;
import de.tuberlin.polymake.common.PointSet;
import de.tuberlin.polymake.common.PolymakeFrame;
import de.tuberlin.polymake.common.SimpleGeometryParser;

/**
 * Frame used to display a Schlegel Diagram which can be interactively
 * modified by dragging vertices, changing the zoomFactor and selecting
 * new projection facets. The data which is sent will be filtered using the 
 * MsgQueue.
 * 
 * @author  Thilo Schr&ouml;der
 *			
 */
public class SpringEmbedderFrame extends PolymakeFrame implements PvPickListenerIf {

    /** Help frame belonging to the Schlegelframe */
    protected static SpringEmbedderHelpFrame helpFrame = null;

    protected CheckboxGroup stepCheckboxGroup = new CheckboxGroup();
    protected TextField stepField = new TextField();
    protected Button helpButton = new Button("Help");
    protected Button sendButton = new Button("Recompute");
    protected Button stopButton = new Button("Stop");
    protected Button restartButton = new Button("Reset");

    protected boolean autoRecompute = false;

    protected boolean startup = true;
    /**
     * Create a frame displaying the given geometry with given zoom parameter.
     * The output of any event is written into a pipe.
     *
     * @param i_geom   		geometry to be displayed
     * @param params    	non Javaview jvx parameters
     * @param sink			
     */
    public SpringEmbedderFrame(EmbeddedGeometries geoms, 
			       Properties params, Properties iparams,
			       JavaviewControl parent) {

	super(geoms,params,iparams,parent);
	setUpGUI();
	
	for(Iterator it = iparameters.keySet().iterator(); it.hasNext();) {
	    String prop = (String)it.next();
	    if (!params.getProperty(prop).equals("null")) {
		parameters.setProperty(prop,params.getProperty(prop));
		PuDouble slider = (PuDouble)sliderMap.get(prop);
		slider.setValue(Double.parseDouble(parameters.getProperty(prop)));
	    }
	}

	parameters.setProperty("continue",params.getProperty("continue"));

	disp.addPickListener(this);

	disp.fit();
	pack();
    }

    /** update the frame with new values for the vertices and the zoom Factor */
    public void update(PointSet ps, Properties params) {
	if(!getTitle().equals(ps.getName())) {
	    title = ps.getName();
	    setTitle(title);
	}
	stopButton.setEnabled(autoRecompute);
	for(Iterator it = iparameters.keySet().iterator(); it.hasNext();) {
	    String param = (String)(it.next());
	    if (!params.getProperty(param).equals("null")) {
		parameters.setProperty(param,params.getProperty(param));
		PuDouble slider = (PuDouble)(sliderMap.get(param));
		slider.setValue(Double.parseDouble(parameters.getProperty(param)));
	    }
	}

	if (!params.getProperty("continue").equals("null")) {
	    parameters.setProperty("continue",params.getProperty("continue"));
	    stopButton.setEnabled(!(parameters.getProperty("continue").equals("0")));
	}
	geom.update(ps);
	if(startup) { startup = false; disp.fit(); }
    }

    public boolean autoRecompute() {
	return autoRecompute;
    }

    /* ---------------------GUI------------------------*/

    public JPanel getParamPanel() {
	JPanel paramPanel = new JPanel(new GridLayout(iparameters.size()+1,1));
	paramPanel.add(getAnimationPanel());
	for(Iterator it = iparameters.keySet().iterator(); it.hasNext();) {
	    String param = (String)it.next();
	    String value = (String)(iparameters.get(param));
	    PuDouble slider;
	    if(value.equals("1")) {
		slider = new PuDouble(param,new directSlider(param));
	    } else {
		slider = new PuDouble(param,null);
	    }
	    slider.setDefBounds(0.5, 100.0, 0.01, 1);
	    slider.init();
	    paramPanel.add(slider.assureInspector(PsPanel.INFO, PsPanel.INFO_EXT));
	    sliderMap.put(param,slider);
	}
	return paramPanel;
    }

    public JPanel getAnimationPanel() {
	JPanel stepPanel = new JPanel(new GridLayout(1,6));
	stepPanel.add(new Label("AnimationSteps"));
	stepPanel.add(new Checkbox("none",stepCheckboxGroup,true));
	stepPanel.add(new Checkbox("5",stepCheckboxGroup,false));
	stepPanel.add(new Checkbox("10",stepCheckboxGroup,false));
	stepPanel.add(new Checkbox("50",stepCheckboxGroup,false));
	stepPanel.add(new Checkbox("100",stepCheckboxGroup,false));
	stopButton.addActionListener(new ActionListener() {
		public void actionPerformed(ActionEvent e) {
		    if(getStepValue().equals("0")) {
			statusBar.setText("No animation to stop.");
		    } else {
			autoRecompute = false;
		    }
		}
	    });
	stepPanel.add(stopButton);
	return stepPanel;
    }

    public JPanel getComputationPanel() {
	JPanel computationPanel = new JPanel(new BorderLayout());
	JPanel buttonPanel = new JPanel(new GridLayout(1,2));
	restartButton.addActionListener(new ActionListener() {
		public void actionPerformed(ActionEvent e) {
		    try {
			for(Iterator it = sliderMap.keySet().iterator(); it.hasNext();) {
			    String param = (String)it.next();
			    PuDouble slider = (PuDouble)sliderMap.get(param);
			    parameters.setProperty(param,Double.toString(slider.getValue()));
			}
			parameters.setProperty("step",getStepValue());
			parameters.setProperty("continue",(!getStepValue().equals("0"))?"1":"0");
			parameters.setProperty("restart","1");
			parent.putMessage(SimpleGeometryParser.write(geom.getName(),parameters),true);
			parameters.remove("restart");
			statusBar.setText("Press <Recompute> to start embedding.");
		    } catch (IOException ex) {
			System.err.println("SchlegelFrame: error writing to client");
			ex.printStackTrace(System.err);
		    }
		}
	    });
	
	sendButton.addActionListener(new ActionListener() {
		public void actionPerformed(ActionEvent e) {
		    try {
			for(Iterator it = sliderMap.keySet().iterator(); it.hasNext();) {
			    String param = (String)it.next();
			    PuDouble slider = (PuDouble)sliderMap.get(param);
			    parameters.setProperty(param,Double.toString(slider.getValue()));
			}
			parameters.setProperty("step",getStepValue());
			parameters.setProperty("continue",(!getStepValue().equals("0"))?"1":"0");			
			Vector markedVertices = geom.getMarkedVertices();
 			parent.putMessage(SimpleGeometryParser.write(JavaviewUtils.toPointSet(geom.getEmbedding()),markedVertices,parameters),true);
			statusBar.setText("");
		    } catch (IOException ex) {
			System.err.println("SchlegelFrame: error writing to client");
			ex.printStackTrace(System.err);
		    }
		}
	    });
	buttonPanel.add(restartButton);
	buttonPanel.add(sendButton);
	computationPanel.add(buttonPanel,BorderLayout.CENTER);
	computationPanel.add(statusBar,BorderLayout.SOUTH);
	return computationPanel;
    }

    public JPanel getSouthPanel() {
	JPanel southPanel = new JPanel(new BorderLayout());
	JPanel computationPanel = getComputationPanel();
	southPanel.add(computationPanel, BorderLayout.SOUTH);
	return southPanel;
    }
    
    protected String getStepValue() {
	Checkbox selectedCheckbox = stepCheckboxGroup.getSelectedCheckbox();
	if(selectedCheckbox.getLabel().equals("none")) {
	    autoRecompute = false;
	    return "0";
	} else {
	    autoRecompute = true;
	    return selectedCheckbox.getLabel();
	}
    }

    protected void setUpGUI() {
	setLayout(new BorderLayout());
	
	helpButton.addActionListener(new ActionListener() {
		public void actionPerformed(ActionEvent e) {
		    if (helpFrame == null)
			helpFrame = new SpringEmbedderHelpFrame();
		    if (!helpFrame.isVisible()
			|| helpFrame.getState() == Frame.ICONIFIED)
			helpFrame.setLocation(getX() + 20, getY() + 20);
		    helpFrame.setState(Frame.NORMAL);
		    helpFrame.setVisible(true);
		}
	    });


	JPanel southPanel = getComputationPanel();
	JPanel topPanel = new JPanel();
	topPanel.setLayout(new BorderLayout());
	topPanel.add(helpButton, BorderLayout.EAST);
	
	JSplitPane splitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT,
					      (Component)disp, getParamPanel());
	splitPane.setContinuousLayout(true);
	splitPane.setOneTouchExpandable(true);
	splitPane.setResizeWeight(1.);

	add(topPanel, BorderLayout.NORTH);
	add(splitPane, BorderLayout.CENTER);
	add(southPanel, BorderLayout.SOUTH);
    }

    /** 
     * @author Thilo Schr&ouml;der
     *
     * This class implements the update behaviour of the zoom factor slider.
     */
    protected class directSlider extends PsObject {
	
	protected String paramName;

	public directSlider(String paramName) {
	    this.paramName = paramName;
	}

	public boolean update(java.lang.Object event) {
	    if (Double.parseDouble((String)(parameters.get(paramName))) == ((PuDouble) event).getValue())
		return true;
	    parameters.setProperty(paramName, Double.toString(((PuDouble) event).getValue()));
	    try {
		for(Iterator it = sliderMap.keySet().iterator(); it.hasNext();) {
		    String param = (String)it.next();
		    PuDouble slider = (PuDouble)(sliderMap.get(param));
		    parameters.setProperty(param,Double.toString(slider.getValue()));
		}
		parameters.setProperty("step",getStepValue());
		parameters.setProperty("continue",(!getStepValue().equals("0"))?"1":"0");
		parent.putMessage(SimpleGeometryParser.write(geom.getName(),parameters),true);
		statusBar.setText("");
	    } catch (IOException ex) {
		System.err.println("SpringEmbedderFrame: communication error");
		ex.printStackTrace();
		return false;
	    }
	    return true;
	}
    }

    // ---------------- Begin of PvPickListenerIf ------------------------

    /** The name of a listeners allows the display to issue verbal debug messages. */
    public String getName() {
	return "SpringEmbedderFramePicker";
    }

    /** Currently not supported by display yet. */
    public void selectGeometry(PgGeometryIf geom) {
    }

    /**
     * Get a location in the display with 2d display and 3d world coordinates.
     * Method is called when display is in mode PvDisplayIf.MODE_DISPLAY_PICK.
     * @see		jv.project.PvPickListenerIf
     * @param	pos		Pick event issued by the display
     */
    public void pickDisplay(PvPickEvent pos) {
	PsDebug.message("pickDisplay entered.");
    }

    /**
     * Drag a location in the display with 2d display and 3d world coordinates.
     * Method is called when display is in mode PvDisplayIf.MODE_DISPLAY_PICK.
     * @see		jv.project.PvPickListenerIf
     * @param	pos		Pick event issued by the display
     */
    public void dragDisplay(PvPickEvent pos) {
	PsDebug.message("pickDisplay entered.");
    }

    /**
     * Pick an arbitrary point on a geometry, point may lie inside an element.
     * Method is called when display is in mode PvDisplayIf.MODE_INITIAL_PICK
     * or if temporarily the i-key is pressed, and any pixel in the display is picked.
     * @see		jv.project.PvPickListenerIf
     * @param	pos		Pick event issued by the display
     */
    public void pickInitial(PvPickEvent pos) {
	// PsDebug.message("pickInitial entered.");
    }

    /**
     * Drag an arbitrary point along a geometry, point may lie inside an element.
     * Method is called when display is in mode PvDisplayIf.MODE_INITIAL_PICK
     * or if temporarily the i-key is pressed, and any pixel in the display is dragged.
     * @see		jv.project.PvPickListenerIf
     * @param	pos		Pick event issued by the display
     */
    public void dragInitial(PvPickEvent pos) {
	// PsDebug.message("dragInitial entered = "+pos.getLocation());
    }

    /**
     * Get a picked vertex of a geometry.
     * Method is called when display is in mode PvDisplayIf.MODE_PICK
     * or if temporarily the p-key is pressed, and a vertex picked.
     * @see		jv.project.PvPickListenerIf
     * @param	geom		Picked geometry on which vertex lies
     * @param	index		Index of vertex in vertex array of geometry
     * @param	vertex	3d coordinates of vertex position
     */
    public void pickVertex(PgGeometryIf geom, int index, PdVector vertex) {
    }

    /**
     * <p>Method is called when display is in mode PvDisplayIf.MODE_PICK
     * or if temporarily the p-key is pressed, and a vertex dragged.</p>
     * <p>Drag a picked vertex of a geometry and put its coordinates into
     * the MsgQueue. If the queue is empty sent PolymakeFrame.ANSWER
     * to sink.</p>
     * 
     * @param	geom		Picked geometry on which vertex lies
     * @param	index		Index of vertex in vertex array of geometry
     * @param	vertex	3d coordinates of vertex position
     */
    public void dragVertex(PgGeometryIf a_geom, int index, PdVector vertex) {
	geom.moveVertex(a_geom.getName(),index,vertex);
    }

    /**
     * Mark a set of vertices of a geometry within a given bounding box.
     * Method is called when display is in mode PvDisplayIf.MODE_MARK
     * or if temporarily the m-key is pressed, and a rectangle is drawn.
     * @param	markBox		contains four coplanar points on the bounding prism, 
     *                          and direction of prism. 
     */
    public void markVertices(PvPickEvent markBox) {
    }

    /**
     * Unmark a set of vertices of a geometry within a given bounding box.
     * Method is called when display is in mode PvDisplayIf.MODE_UNMARK
     * or if temporarily the u-key is pressed, and a rectangle is drawn.
     * @param	markBox		contains four coplanar points on the bounding prism, and direction of prism. 
     */
    public void unmarkVertices(PvPickEvent markBox) {
	//	PsDebug.message("unmarkVertices entered. Unmarked Vertex: " + pos.getVertexInd());
    }
    // ---------------- End of PvPickListenerIf ------------------------

}
