/* 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: SchlegelFrame.java,v 1.14 2005/11/01 09:18:16 thilosch Exp $
 */

package de.tuberlin.polymake.polytope;

import java.awt.BorderLayout;
import java.awt.Button;
import java.awt.Color;
import java.awt.Component;
import java.awt.Frame;
import java.awt.GridLayout;
import java.awt.Panel;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.IOException;
import java.util.Properties;
import java.util.StringTokenizer;
import java.util.Vector;

import jv.geom.PgPolygonSet;
import jv.number.PuDouble;
import jv.object.PsDebug;
import jv.object.PsObject;
import jv.object.PsPanel;
import jv.project.PgGeometry;
import jv.project.PgGeometryIf;
import jv.project.PvDisplayIf;
import jv.project.PvGeometryIf;
import jv.project.PvPickEvent;
import jv.project.PvPickListenerIf;
import jv.vecmath.PdVector;
import jv.vecmath.PiVector;
import de.tuberlin.polymake.common.EmbeddedGeometries;
import de.tuberlin.polymake.common.JavaviewControl;
import de.tuberlin.polymake.common.PointSet;
import de.tuberlin.polymake.common.PolymakeFrame;
import de.tuberlin.polymake.common.PolymakePoint;
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 SchlegelFrame extends PolymakeFrame implements PvPickListenerIf {
	
	/** Help frame belonging to the Schlegelframe */
	protected static SchlegelHelpFrame helpFrame = null;
	
	protected Panel topPanel = new Panel();
	protected Button storeButton;
	protected Button sendMarkedButton;
	protected Button helpButton;
	protected boolean vertexDragged = false;
	
	static protected Color projColor = new Color(0,0,0);
	static protected Color facetColor = new Color(0,0,0);
	
	protected Vector facet = null;
	
	/**
	 * 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 SchlegelFrame(EmbeddedGeometries geoms, Properties params, Properties iparams, JavaviewControl parent) {
		
		super(geoms,params,iparams,parent);
		setUpGUI();
		
		if(System.getProperty("polymake.user.colors.proj") != null) {
			StringTokenizer st = new StringTokenizer(System.getProperty("polymake.user.colors.proj"));
			projColor = new Color(Integer.parseInt(st.nextToken()),
					Integer.parseInt(st.nextToken()),
					Integer.parseInt(st.nextToken()));
		}
		if(System.getProperty("polymake.user.colors.facets") != null) {
			StringTokenizer st = new StringTokenizer(System.getProperty("polymake.user.colors.facets"));
			facetColor = new Color(Integer.parseInt(st.nextToken()),
					Integer.parseInt(st.nextToken()),
					Integer.parseInt(st.nextToken()));
		}
		
		facet = SimpleGeometryParser.parseFacet(parameters.getProperty("facet"));
		
		setPolygonColors();
		
		disp.addPickListener(this);
		
		// Set display in initial-pick mode, that is, the methods
		// pickInitial() and dragInitial() of PvPickListenerIf 
		// are called on mouse events.
		disp.setMajorMode(PvDisplayIf.MODE_PICK);
		
		disp.fit();
	}
	
	/** update the frame with new values for the vertices and the zoom Factor */
	public void update(PointSet ps, Properties params, boolean clearTags) {
		if(!getTitle().equals(ps.getName())) {
			title = ps.getName();
			setTitle(title);
		}
		if (!params.getProperty("zoom").equals("null"))	{
			parameters.setProperty("zoom",params.getProperty("zoom"));
			if(vertexDragged) {
				((PuDouble)sliderMap.get("zoom")).setValue(Double.parseDouble(parameters.getProperty("zoom")));
				vertexDragged = false;
			}
		}
		geom.update(ps, clearTags);
		
	}
	
	public void update(PointSet ps, Properties params) {
		update(ps, params, false);
	}
	// ---------------- Begin of PvPickListenerIf ------------------------
	
	/** The name of a listeners allows the display to issue verbal debug messages. */
	public String getName() {
		return "SchlegelFramePicker";
	}
	
	/** 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 m_geom, int index, PdVector vertex) {
		
		try {
			geom.moveVertex(m_geom.getName(),index,vertex);
			
			PointSet outputVertices = new PointSet(geom.getName(),1);
			PdVector tmpVertex = geom.getEmbeddedVertex(m_geom.getName(),index);
			outputVertices.setPoint(0, new PolymakePoint(tmpVertex.getEntries(),tmpVertex.getName()));
			parent.putMessage(SimpleGeometryParser.write(outputVertices,null),true);
			statusBar.setText("");
			vertexDragged = true;
		} catch (Exception ex) {
			System.err.println("SchlegelFrame: error writing to client");
			ex.printStackTrace(System.err);
		}
	}
	
	/**
	 * 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 ------------------------
	
	private void setPolygonColors() {
		PgGeometry[] geoms = geom.getGeometries();
		for(int j = 0; j < geoms.length; ++j) {
			if(geoms[j].getType() == PvGeometryIf.GEOM_POLYGON_SET) {
				PiVector [] polygon = ((PgPolygonSet)geoms[j]).getPolygons();
				for (int i=0; i < polygon.length; i++) {
					if(facet.contains(new Integer(polygon[i].getEntry(0))) &&
							facet.contains(new Integer(polygon[i].getEntry(1))) ) {
						((PgPolygonSet)geoms[j]).setPolygonColor(i,projColor);
					} else {
						((PgPolygonSet)geoms[j]).setPolygonColor(i,facetColor);
					}
				}
			}
			geoms[j].update(geoms[j]);
		}
	}
	
	protected void setUpGUI() {
		// Standard Java technique to add a component to an applet.
		setLayout(new BorderLayout());
		
		// Create a ZOOM Slider which writes its value to the sink on change
		PuDouble zoomFactor = new PuDouble("Zoom Factor", new zoomSlider());
		zoomFactor.setDefBounds(0.01, 1.0, 0.01, 0.1);
		zoomFactor.init();
		//	zoomFactor.setDefValue(0.5);
		vertexDragged = true;
		
		helpButton = new Button("Help");
		helpButton.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				if (helpFrame == null)
					helpFrame = new SchlegelHelpFrame();
				if (!helpFrame.isVisible()
						|| helpFrame.getState() == Frame.ICONIFIED)
					helpFrame.setLocation(getX() + 20, getY() + 20);
				helpFrame.setState(Frame.NORMAL);
				helpFrame.setVisible(true);
			}
		});
		
		topPanel.setLayout(new BorderLayout());
		topPanel.add(zoomFactor.assureInspector(PsPanel.INFO, PsPanel.INFO_EXT), BorderLayout.CENTER);
		topPanel.add(helpButton, BorderLayout.EAST);
		
		sliderMap.put("zoom",zoomFactor);
		
		add((Component) disp, BorderLayout.CENTER);
		add(topPanel, BorderLayout.NORTH);
		
		storeButton = new Button("My Favorite View!");
		storeButton.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				try {
					parent.putMessage(SimpleGeometryParser.write(geom.getName(),"store",1.),false);
					statusBar.setText("");
				} catch (IOException ex) {
					System.err.println("SchlegelFrame: error writing to client");
					ex.printStackTrace(System.err);
				}
			}
		});
		
		sendMarkedButton = new Button("New Projection Facet");
		sendMarkedButton.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				try {
					Vector markedVertices = geom.getMarkedVertices();
					if(markedVertices.isEmpty()) {
						statusBar.setText("no points selected");
					} else {
						parent.putMessage(SimpleGeometryParser.writeFacet(geom.getName(),markedVertices),true);
						statusBar.setText("");
					}
				} catch (IOException ex) {
					System.err.println("SchlegelFrame: error writing to client");
					ex.printStackTrace(System.err);
				}
			}
		});
		
		Panel panel = new Panel(new GridLayout(2,1));
		Panel bPanel = new Panel(new GridLayout(1,2));
		//	bPanel.add(storeButton);
		bPanel.add(sendMarkedButton);
		panel.add(bPanel);
		panel.add(statusBar);
		add(panel, BorderLayout.SOUTH);
	}
	
	/** 
	 * @author Thilo Schr&ouml;der
	 *
	 * This class implements the update behaviour of the zoom factor slider.
	 */
	protected class zoomSlider extends PsObject {
		
		public boolean update(java.lang.Object event) {
			double actualValue = java.lang.Math.round(((PuDouble) event).getValue()*100)/100.0;
			
			if (Double.parseDouble(parameters.getProperty("zoom")) == actualValue) {
				return true;
			}
			try {
				parent.putMessage(SimpleGeometryParser.write(geom.getName(),"zoom",actualValue),true);
				statusBar.setText("");
			} catch (IOException ex) {
				System.err.println("SchlegelFrame: communication error");
				ex.printStackTrace();
				return false;
			}
			//	    parameters.setProperty("zoom", Double.toString(actualValue));
			return true;
		}
	}
}
