package CookieCutter.g5;

import java.util.Vector;
import java.awt.*;
import java.awt.geom.*;

/** 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.
 *
 * ADAPTED FROM Grace (http://www.doclsf.de/grace/)
 * License: GNU GPL
 *
 * @author 	John Reekie
 */
public class Polygon2D implements Shape {
	/** 
	 * The current number of coordinates
	 */
	//protected int _coordCount = 0;

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

	/** The coordinates
	 */
	//protected double _coords[] = new double[4];
	protected Vector _coords;

	/** Create a new polygon with no coordinates
	 */
	public Polygon2D() {
		_coords = new Vector();
	}

	/** 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) {
		if ( _coords.size() == 0 ) {
			return false;
		}

		for( int i = 0; i < getVertexCount(); i++ ) {
			Line2D.Double ray = new Line2D.Double( x, y, 
				java.lang.Double.MAX_VALUE, 
				java.lang.Double.MAX_VALUE );

			if( ray.intersectsLine( getX( i - 1 ), getY( i - 1 ), 
				getX( i ), getY( i ) ) ) {
				crossings++;
			}
		}

		/*
		// 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 (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)) {
				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 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);
	}

	/** 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, but which intersects some edges.)
	 */
	public boolean intersects (Rectangle2D r) {
		return intersects(r.getX(), r.getY(), r.getWidth(), r.getHeight());
	}

	/** 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, but which intersects some edges.)
	 */
	public boolean intersects (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);
	}

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

	/** Get the floating-point bounds of the polygon.
	 */
	public Rectangle2D getBounds2D () {
		//if (_coordCount <= 0) {
		if ( _coords.size() == 0 ) {
			return new Rectangle2D.Double();
		}
		/*
		double x1 = _coords[0];
		double y1 = _coords[1];
		*/
		double x1 = getX( 0 );
		double y1 = getY( 0 );
		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++;
		}
		*/

		for (int i = 0; i < _coords.size(); i++ ) {
			Point2D.Double vertex = ( Point2D.Double )_coords.elementAt( i );

			if (vertex.getX() < x1) {
				x1 = vertex.getX();
			} else if (vertex.getX() > x2) {
				x2 = vertex.getX();
			}
			if (vertex.getY() < y1) {
				y1 = vertex.getY();
			} else if (vertex.getY() > y2) {
				y2 = vertex.getY();
			}
		}

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

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

	/** Get the given X-coordinate
	 *
	 * @throws IndexOutOfBoundsException The index is out of bounds.
	 */
	public double getX (int index) {
		/*
		if( index < 0 ) {
			index = getVertexCount() + index; // wrap around
		}
		
		return _coords[index*2];
		*/
		if( index < 0 ) {
			index = getVertexCount() + index; // wrap around
		}
		else if( index > getVertexCount() - 1 ) {
			index = index - getVertexCount();
		}
		
		Point2D.Double vertex = getVertex( index );
		return vertex.getX();
	}

	/** Get the given Y-coordinate
	 *
	 * @throws IndexOutOfBoundsException The index is out of bounds.
	 */
	public double getY (int index) {
		if( index < 0 ) {
			index = getVertexCount() + index; // wrap around
		}
		else if( index > getVertexCount() - 1 ) {
			index = index - getVertexCount();
		}
		
		//return _coords[index*2+1];
		Point2D.Double vertex = getVertex( index );
		return( vertex.getY() );
	}

	public Point2D.Double getVertex( int index ) {
		if( index < 0 ) {
			index = getVertexCount() + index; // wrap around
		}
		else if( index > getVertexCount() - 1 ) {
			index = index - getVertexCount();
		}

		return( ( Point2D.Double )( _coords.elementAt( index ) ) );
	}

	/** 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;
		*/
		_coords.add( new Point2D.Double( x, y ) );
	}

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

		/*
		_coords[0] = x;
		_coords[1] = y;
		_coordCount = 2;
		*/
		_coords = new Vector();
		_coords.add( new Point2D.Double( x, y ) );
	}

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

		if( index == 0 ) {
			_coords[_coords.length - 2] = x;
		}
		*/
		Point2D.Double curr = getVertex( index );
		curr.setLocation( x, curr.getY() );
	}

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

		if( index == 0 ) {
			_coords[_coords.length - 1] = y;
		}
		*/
		Point2D.Double curr = getVertex( index );
		curr.setLocation( curr.getX(), y );
	}

	/** Transform the polygon with the given transform.
	 */
	public void transform (AffineTransform at) {
		//at.transform(_coords, 0, _coords, 0, _coordCount/2);
		Point2D.Double[] dst = new Point2D.Double[getVertexCount()];
		Point2D.Double[] src= new Point2D.Double[getVertexCount()];

		for( int i = 0; i < src.length; i++ ) {
			Point2D.Double vertex = getVertex( i );
			src[i] = new Point2D.Double( vertex.getX(), vertex.getY() ); 
		}
		
		at.transform( src, 0, dst, 0, getVertexCount() );

		for( int i = 0; i < dst.length; i++ ) {
			Point2D.Double vertex = getVertex( i );
			vertex.setLocation( dst[i].getX(), dst[i].getY() );
		}
	}

	/** 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;
		}
		*/
		for( int i = 0; i < getVertexCount(); i++ ) {
			Point2D.Double vertex = getVertex( i );
			vertex.setLocation( vertex.getX() + x, vertex.getY() + y );
		}
	}

	/** Rotate the polygon about the origin given an angle
	 */
	/*
	public void rotate( double angle ) {
		AffineTransformation rotationMatrix = new AffineTransform();
		rotationMatrix.rotate( angle );
		transform( rotationMatrix );
	}
	*/

	/** Integrates over a polygon
	  * Taken from http://www.math.niu.edu/~rusin/known-math/95/concave.corner
	  */
	public double area() {
		double area = 0.0;

		for( int i = 0; i < getVertexCount(); i++ ) {
			area += ( getY( i ) * getX( i - 1 ) - getX( i ) * getY( i - 1 ) ) / 2.0;
		}

		return( area );
	}
}
