package de.tu_darmstadt.informatik.tk.scopviz.ui.handlers;

import java.awt.event.MouseEvent;

import org.graphstream.ui.geom.Point3;
import org.graphstream.ui.graphicGraph.GraphicElement;
import org.graphstream.ui.view.Camera;
import org.graphstream.ui.view.util.DefaultMouseManager;

import de.tu_darmstadt.informatik.tk.scopviz.debug.Debug;
import de.tu_darmstadt.informatik.tk.scopviz.graphs.GraphManager;
import de.tu_darmstadt.informatik.tk.scopviz.graphs.MyEdge;
import de.tu_darmstadt.informatik.tk.scopviz.graphs.MyGraph;
import de.tu_darmstadt.informatik.tk.scopviz.graphs.MyNode;
import de.tu_darmstadt.informatik.tk.scopviz.main.CreationMode;
import de.tu_darmstadt.informatik.tk.scopviz.main.EdgeSelectionHelper;
import de.tu_darmstadt.informatik.tk.scopviz.main.Main;
import de.tu_darmstadt.informatik.tk.scopviz.ui.PropertiesManager;
import de.tu_darmstadt.informatik.tk.scopviz.ui.ToolboxManager;

/**
 * Mouse Manager to handle all Mouse based Interaction on the Graph Display
 * 
 * @author Jan Enders
 * @version 1.0
 */
public class MyMouseManager extends DefaultMouseManager {

	/**
	 * A Reference to the GraphManager this MouseManager is attached to for
	 * easier Access.
	 */
	private GraphManager graphManager;

	/**
	 * The Timestamp of when the Camera's View Center was last changed.
	 */
	private long lastCamUpdate = 0;

	/**
	 * The recorded Position of the Mouse Cursor (in Graph Units) when the Mouse
	 * was last clicked.
	 */
	private Point3 oldMousePos;

	/**
	 * The recorded View Center of the Camera when the Mouse was last clicked.
	 */
	private Point3 oldViewCenter;

	/**
	 * Constructor, sets Reference to the GraphManager this MouseManager is
	 * attached to.
	 * 
	 * @param manager
	 *            the GraphManager for Reference
	 */
	public MyMouseManager(GraphManager manager) {
		this.graphManager = manager;
	}

	/**
	 * Gets called whenever a Mouse Button is pressed while the Mouse is not
	 * over an Element.
	 * 
	 * @param event
	 *            the corresponding MouseEvent
	 */
	@Override
	protected void mouseButtonPress(MouseEvent event) {
		view.requestFocus();

		Point3 cursorPos = graphManager.getView().getCamera().transformPxToGu(event.getX(), event.getY());
		MyNode n;
		MyGraph nodeProducer = new MyGraph("temp");
		MyEdge selectedEdge = EdgeSelectionHelper.getClosestEdge(cursorPos);

		switch (Main.getInstance().getCreationMode()) {

		// If not trying to create any Nodes or Edges, select the Edge that
		// was clicked on
		case CREATE_NONE:
			if (selectedEdge != null) {
				graphManager.selectEdge(selectedEdge.getId());
			}
			break;

		// Otherwise, create node based on creation Mode
		case CREATE_STANDARD_NODE:
			n = nodeProducer.addNode(Main.getInstance().getUnusedID());
			n.setAttribute("xyz", cursorPos);
			n.setAttribute("ui.class", "standard");
			n.setAttribute("typeofNode", "standard");
			graphManager.addNode(n);
			graphManager.selectNode(n.getId());
			Debug.out("INFORMATION: Added Node with ID " + n.getId() + " at Position (" + cursorPos.x + "/"
					+ cursorPos.y + ")", 1);

			break;

		case CREATE_SOURCE_NODE:
			n = nodeProducer.addNode(Main.getInstance().getUnusedID());
			n.setAttribute("xyz", cursorPos);
			n.setAttribute("ui.class", "source");
			n.setAttribute("typeofNode", "source");
			graphManager.addNode(n);
			graphManager.selectNode(n.getId());
			Debug.out("INFORMATION: Added Source Node with ID " + n.getId() + " at Position (" + cursorPos.x + "/"
					+ cursorPos.y + ")", 1);

			break;

		case CREATE_SINK_NODE:
			n = nodeProducer.addNode(Main.getInstance().getUnusedID());
			n.setAttribute("xyz", cursorPos);
			n.setAttribute("ui.class", "sink");
			n.setAttribute("typeofNode", "sink");
			graphManager.addNode(n);
			graphManager.selectNode(n.getId());
			Debug.out("INFORMATION: Added Sink Node with ID " + n.getId() + " at Position (" + cursorPos.x + "/"
					+ cursorPos.y + ")", 1);

			break;

		case CREATE_PROC_NODE:
			n = nodeProducer.addNode(Main.getInstance().getUnusedID());
			n.setAttribute("xyz", cursorPos);
			n.setAttribute("ui.class", "procEn");
			n.setAttribute("typeofNode", "procEn");
			ToolboxManager.createProcMaxDialog(n);
			graphManager.addNode(n);
			graphManager.selectNode(n.getId());
			Debug.out("INFORMATION: Added ProcEn Node with ID " + n.getId() + " at Position (" + cursorPos.x + "/"
					+ cursorPos.y + ")", 1);

			break;

		case CREATE_OPERATOR_NODE:
			n = nodeProducer.addNode(Main.getInstance().getUnusedID());
			n.setAttribute("xyz", cursorPos);
			n.setAttribute("ui.class", "operator");
			n.setAttribute("typeofNode", "operator");
			ToolboxManager.createProcNeedDialog(n);
			graphManager.addNode(n);
			graphManager.selectNode(n.getId());
			Debug.out("INFORMATION: Added Operator Node with ID " + n.getId() + " at Position (" + cursorPos.x + "/"
					+ cursorPos.y + ")", 1);

			break;

		default:
			break;
		}
	}

	/**
	 * Gets Called whenever a Mouse Button is released while the Mouse is not
	 * over an Element.
	 * 
	 * @param event
	 *            the corresponding MouseEvent
	 */
	protected void mouseButtonRelease(MouseEvent event) {
		PropertiesManager.setItemsProperties();
	}

	/**
	 * Gets called whenever a Mouse Button is pressed down while the Mouse is
	 * over an Element.
	 * 
	 * @param element
	 *            the element that was clicked on
	 * @param event
	 *            the corresponding MouseEvent
	 */
	@Override
	protected void mouseButtonPressOnElement(GraphicElement element, MouseEvent event) {
		view.freezeElement(element, true);
		String id = element.getId();

		// if in Creation Mode, try to create an Edge with the Node that was
		// clicked on
		if (Main.getInstance().getCreationMode() != CreationMode.CREATE_NONE) {
			graphManager.createEdges(id);
			return;
		}

		// otherwise select the Node that was clicked
		graphManager.selectNode(id);

	}

	/**
	 * Gets called when a Mouse Button is released after having pressed it down
	 * on an Element.
	 * 
	 * @param element
	 *            the Element that the Mouse Button was pressed on
	 * @param event
	 *            the corresponding MouseEvent
	 */
	@Override
	protected void mouseButtonReleaseOffElement(GraphicElement element, MouseEvent event) {
		view.freezeElement(element, false);
		// update the Attributes of a Node after moving it
		PropertiesManager.setItemsProperties();
	}

	// Mouse Listener

	/**
	 * Gets called whenever a Mouse Button is pressed down.
	 * 
	 * @param event
	 *            the corresponding MouseEvent
	 */
	@Override
	public void mousePressed(MouseEvent event) {
		// Left Click -> Find out whether the User clicked on an Element
		if (event.getButton() == MouseEvent.BUTTON1) {
			curElement = view.findNodeOrSpriteAt(event.getX(), event.getY());

			if (curElement != null) {
				mouseButtonPressOnElement(curElement, event);
			} else {
				mouseButtonPress(event);
			}

			// Middle Click -> The User wants to Pan the Camera, record the
			// current Position and View Center
		} else if (event.getButton() == MouseEvent.BUTTON3) {
			Camera cam = Main.getInstance().getGraphManager().getView().getCamera();
			oldMousePos = cam.transformPxToGu(event.getX(), event.getY());
			oldViewCenter = cam.getViewCenter();
		}
	}

	/**
	 * Gets Called whenever the Mouse is dragged while a Mouse Button is being
	 * held down.
	 * 
	 * @param event
	 *            the corresponding MouseEvent
	 */
	@Override
	public void mouseDragged(MouseEvent event) {
		// If the Mouse is dragging an Element, move it
		if (curElement != null) {
			elementMoving(curElement, event);

			// If the Middle Mouse Button is pressed, move the Camera following
			// the dragging gesture
		} else if (event.getButton() == MouseEvent.BUTTON3) {

			Camera cam = Main.getInstance().getGraphManager().getView().getCamera();
			Point3 newMousePos = cam.transformPxToGu(event.getX(), event.getY());
			double offsetX = oldMousePos.x - newMousePos.x;
			double offsetY = oldMousePos.y - newMousePos.y;
			double newX = oldViewCenter.x + offsetX;
			double newY = oldViewCenter.y + offsetY;

			// Only change Camera Position every 50 milliseconds, otherwise the
			// camera does weird things
			long currTime = System.currentTimeMillis();
			if (currTime - lastCamUpdate >= 50) {
				cam.setViewCenter(newX, newY, 0);
				lastCamUpdate = currTime;
			}

		}
	}

	/**
	 * Gets called when a Mouse Button is released.
	 * 
	 * @param event
	 *            the corresponding MouseEvent
	 */
	@Override
	public void mouseReleased(MouseEvent event) {
		if (curElement != null) {
			mouseButtonReleaseOffElement(curElement, event);
			curElement = null;
		} else {
			mouseButtonRelease(event);
		}
	}

}
