package CookieCutter.g5;

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

public class Cookie extends Polygon2D {
	protected static final double BOUNDARY_OFFSET = 0.000000000000001; // MIN BOUNDARY
	protected static final int MAX_BACKOUT = 3000;
	protected Vector _corners = null;
	protected Vector _edges = null;
	protected double _rotation;
	protected Point2D.Double _translation;
	private int _id;

	public Cookie( int id ) throws Exception {
		super();
		_rotation = 0.0;
		_translation = new Point2D.Double( 0.0, 0.0 );
		_id = id;
	}

	public Cookie( int id, Vertex[] vertices ) throws Exception {
		super();
		_rotation = 0.0;
		_translation = new Point2D.Double( 0.0, 0.0 );
		_id = id;

		for( int i = 0; i < vertices.length; i++ ) {
			add( vertices[i] );
		}

		closePath();
		recenter();
		update();
	}

	public Cookie( int id, Point2D.Double[] vertices ) throws Exception {
		super();
		_rotation = 0.0;
		_translation = new Point2D.Double( 0.0, 0.0 );
		_id = id;

		for( int i = 0; i < vertices.length; i++ ) {
			add( vertices[i] );
		}

		closePath();
		recenter();
		update();
	}

	public int ID() {
		return( _id );
	}

	public void setRotation( double rotation ) {
		_rotation = rotation;
	}

	public double getRotation() {
		return( _rotation );
	}

	public void setTranslation( double x, double y ) {
		_translation.setLocation( x, y );
	}

	public Point2D.Double getTranslation() {
		return( _translation );
	}

	public void reset() {
		_translation = new Point2D.Double( 0.0, 0.0 );
		_rotation = 0.0;
	}

	/** Forces the shape to be at the origin
	 */
	public void recenter() throws Exception {
		Point2D.Double cent = centroid();

		// NOTE: we need the surrounding if() statement because, for some reason, 
		// the centroid actually becomes non-zero even if we subtract a 
		// centroid of 0.0 from each vertex
		if( cent.getX() != 0.0 && cent.getY() != 0.0 ) {
			recenter( cent );
		}
	}

	public void recenter( Point2D.Double p ) throws Exception {
		if( p.getX() != 0.0 && p.getY() != 0.0 ) {
			for( int i = 0; i < getVertexCount(); i++ ) {
				setX( i, getX( i ) - p.getX() );
				setY( i, getY( i ) - p.getY() );
			}
		}
	}

	/** Recalculates the corners for this polygon
	 */
	public void updateCorners() throws Exception {
		// no corners; create 'em.
		if( _corners == null && getVertexCount() > 2 ) {
			_corners = new Vector();

			for( int i = 0; i < getVertexCount(); i++ ) {
				Point2D.Double curr = new Point2D.Double( getX( i ), getY( i ) );
				Point2D.Double nextPoint = null, prevPoint = null;
				Corner next = null, prev = null;

				if( i == 0 ) {
					prevPoint = new Point2D.Double( getX( getVertexCount() - 1 ), getY( getVertexCount() - 1 ) );
					nextPoint = new Point2D.Double( getX( 1 ), getY( 1 ) );
				}
				else if( i == getVertexCount() - 1 ) {
					prevPoint = new Point2D.Double( getX( getVertexCount() - 2 ), getY( getVertexCount() - 2 ) );
					nextPoint = new Point2D.Double( getX( 0 ), getY( 0 ) );
				}
				else {
					prevPoint = new Point2D.Double( getX( i - 1 ), getY( i - 1 ) );
					nextPoint = new Point2D.Double( getX( i + 1 ), getY( i + 1 ) );
				}

				if( i != 0 ) {
					prev = ( Corner )_corners.elementAt( i - 1 );
				}

				if( i == getVertexCount() - 1 ) {
					next = ( Corner )_corners.elementAt( 0 );
				}

				// figure out what type of corner it is
				Polygon2D boundingTriangle = new Polygon2D();
				boundingTriangle.lineTo( prevPoint.getX(), prevPoint.getY() );
				boundingTriangle.lineTo( curr.getX(), curr.getY() );
				boundingTriangle.lineTo( nextPoint.getX(), nextPoint.getY() );
				boundingTriangle.closePath();

				Corner newCorner = null;

				if( ( boundingTriangle.area() > 0 && this.area() < 0 ) || 
					( boundingTriangle.area() < 0 && this.area() > 0 ) ) {
					newCorner = new ConcaveCorner( i, _id, prev, curr, next );
				}
				else {
					newCorner = new ConvexCorner( i, _id, prev, curr, next );
				}

				_corners.add( newCorner );

				// update all the other corners that should point to this new one
				if( i != 0 ) {
					Corner prevCorner = ( Corner )_corners.elementAt( i - 1 );
					prevCorner.setNext( newCorner );
				}

				if( i == getVertexCount() - 1 ) {
					Corner firstCorner = ( Corner )_corners.elementAt( 0 );
					firstCorner.setPrev( newCorner );
				}
			}

			return;
		}

		if( _corners != null ) {
			// corners exist; update them
			for( int i = 0; i < getVertexCount(); i++ ) {
				Corner corner = ( Corner )_corners.elementAt( i );
				corner.prev().setLocation( getX( i - 1 ), getY( i - 1 ) );
				corner.setLocation( getX( i ), getY( i ) );
				corner.next().setLocation( getX( i + 1 ), getY( i + 1 ) );
			}
		}
	}

	protected void add( Vertex vertex ) throws Exception {
		add( vertex.xpos(), vertex.ypos() );
	}

	protected void add( Point2D.Double vertex ) throws Exception {
		add( vertex.getX(), vertex.getY() );
	}

	protected void add( double x, double y ) throws Exception {
		lineTo( x, y );
	}

	public Rectangle getBounds() {
		return( getBounds2D().getBounds() );
	}

	public Rectangle2D getBounds2D() {
		Rectangle2D bounds = super.getBounds2D();
		bounds.setRect( bounds.getX() - BOUNDARY_OFFSET, 
			bounds.getY() - BOUNDARY_OFFSET, 
			bounds.getWidth() + 2 * BOUNDARY_OFFSET, 
			bounds.getHeight() + 2 * BOUNDARY_OFFSET 
		);

		return( bounds );
	}		

	/** Computes the centroid of all the vertices
	 */
	public Point2D.Double centroid() {
		double totalX = 0.0, totalY = 0.0;

		for( int i = 0; i < getVertexCount(); i++ ) {
			totalX += getX( i );
			totalY += getY( i );
		}

		return( new Point2D.Double( totalX / getVertexCount(), 
			totalY / getVertexCount() )
		);
	}

	/** Computes a Vector of each segment of the Polygon
	 */
	public void updateEdges() throws Exception {
		// no edges; create 'em.
		if( _edges == null && getVertexCount() > 1 ) {
			_edges = new Vector();

			for( int i = 0; i < getVertexCount(); i++ ) {
				if( i == getVertexCount() - 1 ) {
					_edges.add( new Edge( i, _id, getX( i ), getY( i ), getX( 0 ), getY( 0 ) ) );
					continue;
				}

				_edges.add( new Edge( i, _id, getX( i ), getY( i ), getX( i + 1 ), getY( i + 1 ) ) );
			}

			return;
		}

		if( _edges != null ) {
			// edges exist; update them
			for( int i = 0; i < getVertexCount(); i++ ) {
				Edge edge = ( Edge )_edges.elementAt( i );
				edge.setLine( getVertex( i ), getVertex( i + 1 ) );
			}
		}
	}

	public void update() throws Exception {
		updateEdges();
		updateCorners();
	}

	public Vector getEdges() {
		return( _edges );
	}

	public Vector getCorners() throws Exception {
		return( _corners );
	}

	public Corner[] getBestCornerComplement( Cookie cookie ) throws Exception {
		Vector otherCorners = cookie.getCorners();
		Corner[] pair = null;
		double minDiff = 2 * Math.PI;

		for( int i = 0; i < _corners.size(); i++ ) {
			Corner corner = ( Corner )_corners.elementAt( i );
			Corner compCorner = corner.complement( otherCorners );

			if( compCorner != null ) {
				if( pair == null ) {
					pair = new Corner[2];
				}

				double diff = corner.idealMeasure( compCorner );

				if( Math.abs( diff ) < Math.abs( minDiff ) ) {
					minDiff = diff;
					pair[0] = corner;
					pair[1] = compCorner;
				}
			}
		}

		return( pair );
	}

	public Edge[] getBestEdgeComplement( Cookie cookie ) {
		Vector edges = getEdges();
		Vector otherEdges = cookie.getEdges();
		Edge[] pair = new Edge[2];
		double minDiff = java.lang.Double.MAX_VALUE;

		for( int i = 0; i < edges.size(); i++ ) {
			Edge edge = ( Edge )edges.elementAt( i );
			Edge compEdge = edge.complement( otherEdges );

			if( compEdge != null ) {
				double diff = edge.idealMeasure( compEdge );

				if( Math.abs( diff ) < Math.abs( minDiff ) ) {
					minDiff = diff;
					pair[0] = edge;
					pair[1] = compEdge;
				}
			}
		}

		return( pair );
	}

	public boolean overlapsWith( Cookie cookie, int timesCalled ) {
		/*
		// check if given cookie's points aren't contained w/i this cookie
		for( int i = 0; i < cookie.getVertexCount(); i++ ) {
			if( contains( cookie.getVertex( i ) ) ) {
				if( timesCalled == MAX_BACKOUT ) {
					//System.out.println( "died since this contains cookie point " + i + " " + getX( i ) + " " + getY( i ) );
				}

				return( true );
			}
		}
		
		// check if this cookie's points aren't contained w/i the given cookie
		for( int i = 0; i < getVertexCount(); i++ ) {
			if( cookie.contains( getVertex( i ) ) ) {
				if( timesCalled == MAX_BACKOUT ) {
					//System.out.println( "died since cookie contains this point " + i + " " + getX( i ) + " " + getY( i ) );
				}

				return( true );
			}
		}
		*/

		// check if one of the line segments intersect
		Vector localSegs = getEdges();
		Vector otherSegs = cookie.getEdges();

		for( int i = 0; i < otherSegs.size(); i++ ) {
			Line2D.Double otherLine = ( Line2D.Double )( otherSegs.elementAt( i ) );

			for( int j = 0; j < localSegs.size(); j++ ) {
				Line2D.Double localLine = ( Line2D.Double )( localSegs.elementAt( j ) );

				if( otherLine.intersectsLine( localLine ) ) {
					if( timesCalled == MAX_BACKOUT ) {
						//System.out.println( "died since this line " + i + " intersects line " + j );
					}

					return( true );
				}
			}
		}

		return( false );
	}

	public boolean isConvexHull() {
		if( _corners != null ) {
			for( int i = 0; i < _corners.size(); i++ ) {
				Corner corner = ( Corner )_corners.elementAt( i );

				if( corner.getClass().toString().endsWith( "ConcaveCorner" ) ) {
					return( false );
				}
			}

			return( true );
		}

		return( false );
	}
}
