/*
 Copyright (c) 1998-2005 The Regents of the University of California
 All rights reserved.
 Permission is hereby granted, without written agreement and without
 license or royalty fees, to use, copy, modify, and distribute this
 software and its documentation for any purpose, provided that the above
 copyright notice and the following two paragraphs appear in all copies
 of this software.

 IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY
 FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
 ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF
 THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF
 SUCH DAMAGE.

 THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
 INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE
 PROVIDED HEREUNDER IS ON AN  BASIS, AND THE UNIVERSITY OF
 CALIFORNIA HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES,
 ENHANCEMENTS, OR MODIFICATIONS.

 PT_COPYRIGHT_VERSION_2
 COPYRIGHTENDKEY
 *
 */
package shapes;

import java.awt.Rectangle;
import java.awt.Shape;
import java.awt.geom.AffineTransform;
import java.awt.geom.Line2D;
import java.awt.geom.PathIterator;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.io.Serializable;

/*
 * Extended by Leontios Papaleontiou
 */

/**
 * A polygon shape. This class implements <b>java.awt.Shape</b>, and consists
 * of a series of straight-line segments. This class should be used instead of
 * GeneralPath for shapes that consist only of straight-line segments and are
 * always closed. It is more efficient than GeneralPath, and allows the
 * coordinates of vertices to be modified.
 * 
 * Following the convention set by the Java2D shape classes, the Polygon class
 * is an abstract class, which contains two concrete inner classes, one storing
 * floats and one storing doubles.
 * 
 * @version $Id: Polygon2D.java,v 1.14 2005/07/08 19:55:18 cxh Exp $
 * @author John Reekie
 */
public abstract class Polygon2D implements Shape, Serializable {
	/**
	 * The current number of coordinates
	 */
	protected int _coordCount = 0;

	/**
	 * The flag that says the the polygon has been closed
	 */
	protected boolean _closed = false;

	/**
	 * Close the polygon. No further segments can be added. If this method not
	 * called, then the path iterators will treat the polygon as thought it were
	 * closed, and implicitly join the most recently added vertex to the first
	 * one. However, this method should generally be called, as if the last
	 * vertex is the same as the first vertex, then it merges them.
	 */
	public void closePath() {
		if ((getX(getVertexCount() - 1) == getX(0)) && (getY(getVertexCount() - 1) == getY(0))) {
			_coordCount -= 2;
		}

		_closed = true;
	}

	/**
	 * Return true if the given point is inside the polygon. This method uses a
	 * straight-forward algorithm, where a point is taken to be inside the
	 * polygon if a horizontal line extended from the point to infinity
	 * intersects an odd number of segments.
	 */
	public boolean contains(double x, double y) {
		int crossings = 0;

		if (_coordCount == 0) {
			return false;
		}

		// Iterate over all vertices
		int i = 1;

		for (; i < getVertexCount();) {
			double x1 = getX(i - 1);
			double x2 = getX(i);
			double y1 = getY(i - 1);
			double y2 = getY(i);

			// Crossing if lines intersect
			if ((x < x1) || (x < x2)) {
				if (y == y2) {
					crossings++;
				} else if (y == y1) {
					// do nothing, so that two adjacent segments
					// don't both get counted
				} else if (Line2D.linesIntersect(x, y, Math.max(x1, x2), y, x1, y1, x2, y2)) {
					crossings++;
				}
			}

			i++;
		}

		// Final segment
		double x1 = getX(i - 1);
		double y1 = getY(i - 1);
		double x2 = getX(0);
		double y2 = getY(0);

		// Crossing if lines intersect
		if ((x < x1) || (x < x2)) {
			if (Line2D.linesIntersect(x, y, Math.max(x1, x2), y, x1, y1, x2, y2) && (y != y1)) {
				crossings++;
			}
		}

		// True if odd number of crossings
		return (crossings % 2) == 1;
	}

	/**
	 * Return true if the given point is inside the polygon.
	 */
	public boolean contains(Point2D p) {
		return contains(p.getX(), p.getY());
	}

	/**
	 * Return true if the given rectangle is entirely inside the polygon.
	 * (Currently, this algorithm can be fooled by supplying a rectangle that
	 * has all four corners inside the polygon, but which intersects some
	 * edges.)
	 */
	public boolean contains(Rectangle2D r) {
		return contains(r.getX(), r.getY(), r.getWidth(), r.getHeight());
	}

	/**
	 * Return true if the given rectangle is entirely inside the polygon.
	 * (Currently, this algorithm can be fooled by supplying a rectangle that
	 * has all four corners inside the polygon, but which intersects some
	 * edges.)
	 */
	public boolean contains(double x1, double y1, double w, double h) {
		double x2 = x1 + w;
		double y2 = y1 + h;
		return contains(x1, y1) && contains(x1, y2) && contains(x2, y1) && contains(x2, y2);
	}

	/**
	 * Get the integer bounds of the polygon.
	 */
	public Rectangle getBounds() {
		return getBounds2D().getBounds();
	}

	/**
	 * Get the floating-point bounds of the polygon.
	 */
	public abstract Rectangle2D getBounds2D();

	/**
	 * Get a path iterator over the object.
	 */
	public PathIterator getPathIterator(AffineTransform at, double flatness) {
		return getPathIterator(at);
	}

	/**
	 * Get a path iterator over the object.
	 */
	public PathIterator getPathIterator(AffineTransform at) {
		return new PolygonIterator(this, at);
	}

	/**
	 * Get the number of vertices
	 */
	public int getVertexCount() {
		return _coordCount / 2;
	}

	/**
	 * Get the given X-coordinate
	 * 
	 * @exception IndexOutOfBoundsException
	 *                The index is out of bounds.
	 */
	public abstract double getX(int index);

	/**
	 * Get the given Y-coordinate
	 * 
	 * @exception IndexOutOfBoundsException
	 *                The index is out of bounds.
	 */
	public abstract double getY(int index);

	/**
	 * Test if the polygon is intersected by the given rectangle. (Currently,
	 * this algorithm can be fooled by supplying a rectangle that has no corners
	 * inside the polygon, and does not contain any vertex of the polygon, but
	 * which intersects some edges.)
	 */
	public boolean intersects(Rectangle2D r) {
		return intersects(r.getX(), r.getY(), r.getWidth(), r.getHeight());
	}

	public boolean overlaps(Shape s) {
		if (_coordCount == 0) {
			return false;
		}

		if ((this.getBounds2D().contains(s.getBounds2D())) || (this.intersects(s.getBounds2D()))) {
			return true;
		} else {
			return false;
		}
	}

	/**
	 * Test if the polygon is intersected by the given rectangle.
	 */
	public boolean intersects(double x1, double y1, double w, double h) {
		double x2 = x1 + w;
		double y2 = y1 + h;

		if (_coordCount == 0) {
			return false;
		}

		// If the bounds don't intersect, then return false.
		Rectangle2D rect = new Rectangle2D.Double(x1, y1, w, h);

		if (!getBounds().intersects(rect)) {
			return false;
		}

		// return true if the polygon contains any vertex of the rectangle.
		if (contains(x1, y1) || contains(x1, y2) || contains(x2, y1) || contains(x2, y2)) {
			return true;
		}

		// return true if the rectangle contains any vertex of the polygon
		for (int i = 0; i < getVertexCount(); i++) {
			if (rect.contains(getX(i), getY(i))) {
				return true;
			}
		}

		// return true if any line segment of the polygon crosses a line
		// segment of the rectangle.
		// This is rather long, I wonder if it could be optimized.
		// Iterate over all vertices
		for (int i = 1; i < getVertexCount(); i++) {
			double vx1 = getX(i - 1);
			double vx2 = getX(i);
			double vy1 = getY(i - 1);
			double vy2 = getY(i);

			if (Line2D.linesIntersect(x1, y1, x1, y2, vx1, vy1, vx2, vy2)) {
				return true;
			}

			if (Line2D.linesIntersect(x1, y2, x2, y2, vx1, vy1, vx2, vy2)) {
				return true;
			}

			if (Line2D.linesIntersect(x2, y2, x2, y1, vx1, vy1, vx2, vy2)) {
				return true;
			}

			if (Line2D.linesIntersect(x2, y1, x1, y1, vx1, vy1, vx2, vy2)) {
				return true;
			}
		}

		return false;
	}

	/**
	 * Add a new vertex to the end of the polygon. Throw an exception of the
	 * polygon has already been closed.
	 */
	public abstract void lineTo(double x, double y);

	/**
	 * Move the start point of the vertex to the given position. Throw an
	 * exception if the line already contains any vertices.
	 */
	public abstract void moveTo(double x, double y);

	/**
	 * Reset the polygon back to empty.
	 */
	public void reset() {
		_coordCount = 0;
		_closed = false;
	}

	/**
	 * Set the given X-coordinate.
	 * 
	 * @exception IndexOutOfBoundsException
	 *                The index is out of bounds.
	 */
	public abstract void setX(int index, double x);

	/**
	 * Set the given Y-coordinate
	 * 
	 * @exception IndexOutOfBoundsException
	 *                The index is out of bounds.
	 */
	public abstract void setY(int index, double y);

	/**
	 * Transform the polygon with the given transform.
	 */
	public abstract void transform(AffineTransform at);

	/**
	 * Translate the polygon the given distance.
	 */
	public abstract void translate(double x, double y);

	/**
	 * Return a string representation of the polygon.
	 */
	public String toString() {
		String out = getClass().getName() + "[\n";

		for (int i = 0; i < getVertexCount(); i++) {
			out = out + "\t" + getX(i) + ", " + getY(i) + "\n";
		}

		out = out + "]";
		return out;
	}

	// /////////////////////////////////////////////////////////////////
	// // Float

	/**
	 * The concrete Polygon class that stores coordinates internally as floats.
	 */
	public static class Float extends Polygon2D {
		/**
		 * 
		 */
		private static final long serialVersionUID = 1L;

		/**
		 * The coordinates
		 */
		float[] _coords;

		/**
		 * Create a new polygon with space for the given number of vertices.
		 */
		public Float(int size) {
			_coords = new float[2 * size];
		}

		/**
		 * Create a new polygon with space for the given number of vertices.
		 */
		public Float(float[] coords) {
			_coords = coords;
			_coordCount = coords.length;
		}

		/**
		 * Create a new polygon with no vertices.
		 */
		public Float() {
			this(2);
		}

		/**
		 * Create a new polygon with a single start point
		 */
		public Float(float x, float y) {
			this(2);
			_coords[0] = x;
			_coords[1] = y;
			_coordCount = 2;
		}

		/**
		 * Get the floating-point bounds of the polygon.
		 */
		public Rectangle2D getBounds2D() {
			if (_coordCount <= 1) {
				return new Rectangle2D.Float();
			}

			float x1 = _coords[0];
			float y1 = _coords[1];
			float x2 = x1;
			float y2 = y1;

			for (int i = 2; i < _coordCount;) {
				if (_coords[i] < x1) {
					x1 = _coords[i];
				} else if (_coords[i] > x2) {
					x2 = _coords[i];
				}

				i++;

				if (_coords[i] < y1) {
					y1 = _coords[i];
				} else if (_coords[i] > y2) {
					y2 = _coords[i];
				}

				i++;
			}

			return new Rectangle2D.Float(x1, y1, x2 - x1, y2 - y1);
		}

		/**
		 * Get the given X-coordinate
		 * 
		 * @exception IndexOutOfBoundsException
		 *                The index is out of bounds.
		 */
		public double getX(int index) {
			return _coords[index * 2];
		}

		/**
		 * Get the given Y-coordinate
		 * 
		 * @exception IndexOutOfBoundsException
		 *                The index is out of bounds.
		 */
		public double getY(int index) {
			return _coords[(index * 2) + 1];
		}

		/**
		 * Add a new vertex to the end of the line.
		 */
		public void lineTo(double x, double y) {
			if (_closed) {
				throw new UnsupportedOperationException("This polygon has already been closed");
			}

			if (_coordCount == _coords.length) {
				float[] temp = new float[_coordCount * 2];
				System.arraycopy(_coords, 0, temp, 0, _coordCount);
				_coords = temp;
			}

			_coords[_coordCount++] = (float) x;
			_coords[_coordCount++] = (float) y;
		}

		/**
		 * Move the start point of the vertex to the given position.
		 * 
		 * @exception UnsupportedOperationException
		 *                The polygon already has vertices
		 */
		public void moveTo(double x, double y) {
			if (_coordCount > 0) {
				throw new UnsupportedOperationException("This polygon already has vertices");
			}

			_coords[0] = (float) x;
			_coords[1] = (float) y;
			_coordCount = 2;
		}

		/**
		 * Set the given X-coordinate.
		 * 
		 * @exception IndexOutOfBoundsException
		 *                The index is out of bounds.
		 */
		public void setX(int index, double x) {
			_coords[index * 2] = (float) x;
		}

		/**
		 * Set the given Y-coordinate
		 * 
		 * @exception IndexOutOfBoundsException
		 *                The index is out of bounds.
		 */
		public void setY(int index, double y) {
			_coords[(index * 2) + 1] = (float) y;
		}

		/**
		 * Transform the polygon with the given transform.
		 */
		public void transform(AffineTransform at) {
			at.transform(_coords, 0, _coords, 0, _coordCount / 2);
		}

		/**
		 * Translate the polygon the given distance.
		 */
		public void translate(double x, double y) {
			float fx = (float) x;
			float fy = (float) y;

			for (int i = 0; i < _coordCount;) {
				_coords[i++] += fx;
				_coords[i++] += fy;
			}
		}
	}

	// /////////////////////////////////////////////////////////////////
	// // Double

	/**
	 * The concrete Polygon class that stores coordinates internally as doubles.
	 */
	public static class Double extends Polygon2D {
		/**
		 * 
		 */
		private static final long serialVersionUID = 1L;

		/**
		 * The coordinates
		 */
		double[] _coords;

		/**
		 * Create a new polygon with space for the given number of vertices.
		 */
		public Double(int size) {
			_coords = new double[2 * size];
		}

		/**
		 * Create a new polygon with the given vertices, in the format [x0, y0,
		 * x1, y1, ... ].
		 */
		public Double(double[] coords) {
			_coords = coords;
			_coordCount = coords.length;
		}

		/**
		 * Create a new polygon with no coordinates
		 */
		public Double() {
			this(2);
		}

		/**
		 * Create a new polygon with a single start point
		 */
		public Double(double x, double y) {
			this(2);
			_coords[0] = x;
			_coords[1] = y;
			_coordCount = 2;
		}

		/**
		 * Get the floating-point bounds of the polygon.
		 */
		public Rectangle2D.Double getBounds2D() {
			if (_coordCount <= 0) {
				return new Rectangle2D.Double();
			}

			double x1 = _coords[0];
			double y1 = _coords[1];
			double x2 = x1;
			double y2 = y1;

			for (int i = 2; i < _coordCount;) {
				if (_coords[i] < x1) {
					x1 = _coords[i];
				} else if (_coords[i] > x2) {
					x2 = _coords[i];
				}

				i++;

				if (_coords[i] < y1) {
					y1 = _coords[i];
				} else if (_coords[i] > y2) {
					y2 = _coords[i];
				}

				i++;
			}

			return new Rectangle2D.Double(x1, y1, x2 - x1, y2 - y1);
		}

		/**
		 * Get the number of vertices
		 */
		public int getVertexCount() {
			return _coordCount / 2;
		}

		/**
		 * Get the given X-coordinate
		 * 
		 * @exception IndexOutOfBoundsException
		 *                The index is out of bounds.
		 */
		public double getX(int index) {
			return _coords[index * 2];
		}

		/**
		 * Get the given Y-coordinate
		 * 
		 * @exception IndexOutOfBoundsException
		 *                The index is out of bounds.
		 */
		public double getY(int index) {
			return _coords[(index * 2) + 1];
		}

		/**
		 * Add a new vertex to the end of the line.
		 */
		public void lineTo(double x, double y) {
			if (_coordCount == _coords.length) {
				double[] temp = new double[_coordCount * 2];
				System.arraycopy(_coords, 0, temp, 0, _coordCount);
				_coords = temp;
			}

			_coords[_coordCount++] = x;
			_coords[_coordCount++] = y;
		}

		/**
		 * Move the start point of the vertex to the given position.
		 * 
		 * @exception UnsupportedOperationException
		 *                The polygon already has vertices
		 */
		public void moveTo(double x, double y) {
			if (_coordCount > 0) {
				throw new UnsupportedOperationException("This polygon already has vertices");
			}

			_coords[0] = x;
			_coords[1] = y;
			_coordCount = 2;
		}

		/**
		 * Set the given X-coordinate.
		 * 
		 * @exception IndexOutOfBoundsException
		 *                The index is out of bounds.
		 */
		public void setX(int index, double x) {
			_coords[index * 2] = x;
		}

		/**
		 * Set the given Y-coordinate
		 * 
		 * @exception IndexOutOfBoundsException
		 *                The index is out of bounds.
		 */
		public void setY(int index, double y) {
			_coords[(index * 2) + 1] = y;
		}

		/**
		 * Transform the polygon with the given transform.
		 */
		public void transform(AffineTransform at) {
			at.transform(_coords, 0, _coords, 0, _coordCount / 2);
		}

		/**
		 * Translate the polygon the given distance.
		 */
		public void translate(double x, double y) {
			for (int i = 0; i < _coordCount;) {
				_coords[i++] += x;
				_coords[i++] += y;
			}
		}
	}
}
