package trafficdefinition;

import enums.MotionRestriction;
import interfaces.HandleListener;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.geom.AffineTransform;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.io.Serializable;
import shapes.Point2DExt;

/**
 * Class representing a graphical UI handle that allows the user to drag it to change the shape or properties of a graphical element on the screen. The handle is drawn on screen as a small white rectangle.
 */
public class Handle implements Serializable, Cloneable {

	private static final long serialVersionUID = -8791712547811891550L;

	/**
	 * The handle's location
	 */
	private Point2DExt location;

	/**
	 * The handle's name
	 */
	private String name;

	/**
	 * The object that will receive notifications when the handle's location has
	 * changed
	 */
	private HandleListener listener;

	/**
	 * Restriction on the way that the handle can be moved. The handle's motion
	 * can be horizontal, vertical or unrestricted.
	 */
	private MotionRestriction restriction;

	/**
	 * Default constructor.
	 * 
	 * @param location
	 *            the handle's initial location
	 * @param name
	 *            the handle's name
	 * @param restriction
	 *            the handle's motion restriction
	 */
	public Handle(Point2D.Double location, String name, MotionRestriction restriction) {
		this.location = new Point2DExt(location);
		this.name = name;
		this.restriction = restriction;
	}

	/**
	 * Constructor that creates a new handle as a copy of the specified handle
	 * 
	 * @param source
	 */
	public Handle(Handle source) {
		this.location = new Point2DExt(source.location);
		this.name = source.name;
		this.restriction = source.restriction;
	}

	/**
	 * Registers the specified {@link HandleListener} as the handle's listener.
	 * This listener will be notified when the handle's location changes.
	 * 
	 * @param listener
	 */
	public void setHandleListener(HandleListener listener) {
		this.listener = listener;
	}

	/**
	 * Returns the handle's name
	 * @return
	 * @uml.property  name="name"
	 */
	public String getName() {
		return name;
	}

	/**
	 * Returns the handle's location
	 * @return
	 * @uml.property  name="location"
	 */

	public Point2D.Double getLocation() {
		return (Point2D.Double) location;
	}

	/**
	 * Changes the handle's location to the specified point notifies any
	 * registered listeners
	 * 
	 * @param location
	 */
	public void setLocationAndNotify(Point2D.Double location) {
		setLocation(location);

		if (listener != null) {
			listener.handleLocationChanged(this);
		}
	}

	/**
	 * Sets the handles location without taking in mind the handle's motion
	 * restriction and without notifying any registered listeners
	 * 
	 * @param location
	 */
	public void setUnrestrictedLocation(Point2D.Double location) {
		this.location.x = location.x;
		this.location.y = location.y;
	}

	/**
	 * Sets the handle's location taking in mind the handle's motion restriction
	 * but without notifying any registered listeners
	 * 
	 * @param location
	 */

	private void setLocation(Point2D.Double location) {
		switch (restriction) {
		case Horizontal:
			this.location.x = location.x;
			break;
		case Vertical:
			this.location.y = location.y;
			break;
		case None:
			this.location.x = location.x;
			this.location.y = location.y;
			break;
		}
	}

	/**
	 * Draws the handle on the specified graphics. The handle is drawn as a 2x2
	 * pixels square independent from the zooming.
	 * 
	 * @param g
	 */
	public void Draw(Graphics2D g) {

		// Get the previous transform
		AffineTransform currentTransform = g.getTransform();

		// Convert the handle's location using the transform
		Point2D t = currentTransform.transform(location, null);

		// Reset the transform on the graphics object
		g.setTransform(new AffineTransform());
		Rectangle2D.Double e = new Rectangle2D.Double();

		// Draw the handle
		e.setFrameFromCenter(t.getX(), t.getY(), t.getX() + 2, t.getY() + 2);

		g.setColor(Color.WHITE);
		g.fill(e);
		g.setColor(Color.BLACK);
		g.draw(e);

		// Restore the transform
		g.setTransform(currentTransform);
	}

	/**
	 * Checks to see if the specified point lies within the handle. This method
	 * is used to check if the mouse pointer is over the handle.
	 * 
	 * @param p
	 * @param tx
	 * @return
	 */
	public boolean hit(Point p, AffineTransform tx) {
		Point2D t = tx.transform(location, null);

		// Be a little flexible, check a bit around the handle as well
		Rectangle2D.Double e = new Rectangle2D.Double();
		e.setFrameFromCenter(t.getX(), t.getY(), t.getX() + 5, t.getY() + 5);

		if (e.contains(p)) {
			return true;
		} else {
			return false;
		}
	}

}
