package CookieCutter.g2;

import java.awt.geom.*;
import java.util.*;
import CookieCutter.*;

public final class Cookie {
	public static final double kEpsilon = 1e-10;
	public static final double kCollideEpsilon = 1e-3;

	private double[] mX;
	private double[] mY;
	private Point mCentroid;
	private double mRotation;
	private CookieCutter mCC;
	private double mArea;
	private Box mBounds;

	public Cookie(Vertex[] iVertices, CookieCutter iCC) {
		try {
			mX = new double[iVertices.length];
			mY = new double[iVertices.length];
			for (int i = 0; i < iVertices.length; i++) {
				mX[i] = iVertices[i].xpos();
				mY[i] = iVertices[i].ypos();
			}

			mCC = iCC;
			mCentroid = new Point(mCC.centroid(iVertices));
			mArea = mCC.area(iVertices);
			mRotation = 0;
			mBounds = null;
		} catch (Exception ex) {
			System.err.println("Exception occurred: " + ex);
			System.exit(1);
		}
	}

	private Cookie(double[] iX, double[] iY, Point iCentroid,
		double iRot, double iArea) {
		mX = iX;
		mY = iY;
		mCentroid = iCentroid;
		mArea = iArea;
		mRotation = iRot;
		mBounds = null;
	}

	/**
	 * Returns the largest X value of any point in this polygon.  This value is
	 * used to determine the right boundary of a particular configuration of
	 * cookies.
	 */
	public double getLargestX() {
	    return getBounds().getRight();
	}

	/**
	 * Translates and rotates this polygon.  This method makes a copy of the
	 * polygon and then translates and rotates the copy.  Therefore, the original
	 * polygon is unaffected.  The supplied rotation angle should be relative
	 * to the original orientation of the shape.
	 */
	public Cookie transform(Point iCentroid, double iRotationAngle) {
		Cookie p = this.copy();

		double offsetX = iCentroid.xpos() - mCentroid.xpos();
		double offsetY = iCentroid.ypos() - mCentroid.ypos();
		double dx;
		double dy;
		double newDX;
		double newDY;

		double sinOfAngle = Math.sin(iRotationAngle);
		double cosOfAngle = Math.cos(iRotationAngle);

		for (int i = 0; i < p.mX.length; i++) {
			// translate...
			p.mX[i] += offsetX;
			p.mY[i] += offsetY;

			// ...and rotate
			if (iRotationAngle != 0) {
				dx = p.mX[i] - iCentroid.xpos();
				dy = p.mY[i] - iCentroid.ypos();
				newDX = dx * cosOfAngle - dy * sinOfAngle;
				newDY = dx * sinOfAngle + dy * cosOfAngle;

				p.mX[i] = iCentroid.xpos() + newDX;
				p.mY[i] = iCentroid.ypos() + newDY;
			}
		}

		// update centroid and rotation
		p.mCentroid.setXpos(iCentroid.xpos());
		p.mCentroid.setYpos(iCentroid.ypos());
		p.mRotation = mRotation + iRotationAngle;

		return p;

	}

	public Cookie shift(double x, double y) {
		Point center = new Point(mCentroid.xpos()+x, mCentroid.ypos()+y);
		return this.transform(center, 0);
	}

	/**
	 * Returns a deep copy of this polygon.
	 */
	public Cookie copy() {
		double[] x = new double[mX.length];
		double[] y = new double[mY.length];

		System.arraycopy(mX, 0, x, 0, mX.length);
		System.arraycopy(mY, 0, y, 0, mY.length);

		return new Cookie(x, y, new Point(mCentroid), mRotation, mArea);
	}

	public int getNumVertices() {
		return mX.length;
	}

        public double[] getmX(){
          return mX;
        }
	public double[] getmY(){
          return mY;
        }
	/**
	 * @return
	 */
	public double getArea() {
		return mArea;
	}

	/**
	 * @return
	 */
	public Point getCentroid() {
		return mCentroid.copy();
	}

	/**
	 * @return
	 */
	public double getRotation() {
		return mRotation;
	}

	public boolean isInBound() {
	    try {
		for (int i = 0; i < mX.length; i++) {
			if (mX[i] < 0 || mY[i] < 0 || mY[i] > 1) {
				return false;
			}
		}
	    }
	    catch (NullPointerException npe) {
		System.out.println("Null Pointer caught in isInBound: " + npe);
	    }
		return true;
	}

	public Box getBounds() {
	    //	    System.out.println("[getBounds] checking for existing bounds");
		if (mBounds == null) {
		    //		    System.out.println("[getBounds] no existing bounds found, calculating new...");
			double left = Double.MAX_VALUE;
			double right = Double.MIN_VALUE;
			double top = Double.MIN_VALUE;
			double bottom = Double.MAX_VALUE;

			for (int i = 0; i < mX.length; i++) {
				if (mX[i] < left) left = mX[i];
				if (mX[i] > right) right = mX[i];
				if (mY[i] < bottom) bottom = mY[i];
				if (mY[i] > top) top = mY[i];
			}

			mBounds = new Box(left, top, right, bottom);
		}
		//		System.out.println("[getBounds] returning a Box representing the bounds");
		return mBounds;
	}

	public Cookie flushToLeftObstacle(List iPlacedCookies) {
		if (intersects(iPlacedCookies)) {
			return this;
		}

		double d = getBounds().getLeft() - kEpsilon;
		Cookie current = this;

		while (d > kCollideEpsilon &&
				current.getBounds().getLeft() > kCollideEpsilon) {

			Cookie moved = current.shift(-d, 0);
			if (!moved.intersects(iPlacedCookies)) {
				current = moved;
			}

			d /= 2;
		}

		return current;
	}



	public Cookie flushToTopObstacle(List iPlacedCookies) {
	    Cookie current =this;
	    try {
		if (intersects(iPlacedCookies)) {
			return this;
		}

		double d = 1 - getBounds().getTop() - kEpsilon;
		//Cookie current = this;

		while (d > kCollideEpsilon &&
				(1 - current.getBounds().getTop()) > kCollideEpsilon) {

			Cookie moved = current.shift(0, d);
			if (!moved.intersects(iPlacedCookies)) {
				current = moved;
			}

			d /= 2;
		}
	    } 
	    catch (NullPointerException e) {
		System.out.println("NPE! " + e);
	    }

	    return current;
	}



	public Cookie flushToBottomObstacle(List iPlacedCookies) {
		if (intersects(iPlacedCookies)) {
			return this;
		}

		double d = getBounds().getBottom() - kEpsilon;
		Cookie current = this;

		while (d > kCollideEpsilon &&
				current.getBounds().getBottom() > kCollideEpsilon) {

			Cookie moved = current.shift(0, -d);
			if (!moved.intersects(iPlacedCookies)) {
				current = moved;
			}

			d /= 2;
		}

		return current;
	}

        //put somewhere random.. //rand is a random number between 0 and 1
        public Cookie flushToRandomY(List iPlacedCookies, double rand) {
          if (intersects(iPlacedCookies)) {
            return this;
          }

          double d = getBounds().getBottom() - kEpsilon;
          Cookie current = this;
          Cookie moved = current.shift(0, -d*(double)rand);
          if (!moved.intersects(iPlacedCookies)) {
            current = moved;
          }
          return current;
        }


	/**
	 * Returns a copy of this cookie, the left side of which is aligned with the
	 * given x-coordinate.
	 */
	public Cookie flushLeftToX(double iX) {
		Box bounds = getBounds();
		double leftmost = bounds.getLeft();
		double dx = iX - leftmost + kEpsilon;

		return transform(new Point(mCentroid.xpos() + dx, mCentroid.ypos()), 0);
	}

	/**
	 * Returns a copy of this cookie, the bottom of which is aligned with the
	 * given y-coordinate.
	 */
	public Cookie flushBottomToY(double iY) {
		Box bounds = getBounds();
		double bottommost = bounds.getBottom();
		double dy = iY - bottommost + kEpsilon;

		return transform(new Point(mCentroid.xpos(), mCentroid.ypos() + dy), 0);
	}

	public boolean intersects(Cookie iC) {
		if (this.equals(iC)) {
			return false;
		}

		// if the cookies' bounding boxes don't intersect, the cookies themselves
		// can't possibly intersect
		if (!getBounds().intersects(iC.getBounds())) {
			return false;
		}

		for (int i = 0; i < mX.length; i++) {
			for (int j = 0; j < iC.mX.length; j++) {
				double ax1 = mX[i];
				double ay1 = mY[i];
				double ax2 = mX[(i + 1) % mX.length];
				double ay2 = mY[(i + 1) % mY.length];

				double bx1 = iC.mX[j];
				double by1 = iC.mY[j];
				double bx2 = iC.mX[(j + 1) % mX.length];
				double by2 = iC.mY[(j + 1) % mY.length];

				if (Line2D.linesIntersect(ax1, ay1, ax2, ay2, bx1, by1, bx2, by2)) {
					return true;
				}
			}
		}

		return false;
	}

	public boolean intersects(List iCookies) {
	    if(getBounds().getLeft()<0||getBounds().getBottom()<0||getBounds().getTop()>=1){
            return true;
	    }
		for (Iterator i = iCookies.iterator(); i.hasNext(); ) {
			Cookie c = (Cookie) i.next();
			if (this.intersects(c)) {
				return true;
			}
		}

		return false;
	}

	public boolean intersects(Cookie[] iCookies) {
		return intersects(iCookies, 0, iCookies.length - 1);
	}

	public boolean intersects(Cookie[] iC, int first, int last) {
		for (int k = first; k <= last; k++) {
			if (this.intersects(iC[k]))
				return true;
		}
		return false;
	}

	public static double computeRightBoundary(Cookie[] iCookies) {

	    double rightmost = 0;
	    //	    System.out.println("[computeRightBoundary] iCookies Array Count: "+iCookies.length);
	    //	    System.out.println("[computeRightBoundary] Cookie 0 dump: "+iCookies[0]);
	    try {
		//		System.out.println("[computeRightBoundary] getting initial rightmost point");
		rightmost = iCookies[0].getLargestX();
		
		for (int i = 0; i < iCookies.length; i++) {
		    //		    System.out.println("[computeRightBoundary] getting rightmost point of Cookie "+i);
		    double x = iCookies[i].getLargestX();
		    //		    System.out.println("[computeRightBoundary] checking value against current rightmost");
		    if (x > rightmost) {
			//			System.out.println("[computeRightBoundary] " + x + " > " + rightmost);
			//			System.out.println("[computeRightBoundary] Cookie " + i + " is now rightmost");
			rightmost = x;
		    }
 		}
	    }
	    catch (NullPointerException npe ) {
		System.out.println("Null pointer caught in computeRightBoundary" + npe);
		npe.printStackTrace();
	    }
	    return rightmost + Cookie.kEpsilon;
	}

	public String toString() {
		String s = "[";

		for (int k = 0; k < mY.length; k++) {
			s += "(" + Point.nf.format(mX[k]) + "," +
				Point.nf.format(mY[k]) + ") ";
		}

		return s + "]";
	}
}
