// PixelFitter.java -- given a cookie, figure out how to repeat it
// intelligently

/**
 * Pillsbury Doughboy
 * 
 * @author Mark Ayzenshtat
 * @author Vlad Shchogolev
 * @author David Vespe
 */

package CookieCutter.g1;
import CookieCutter.*;
import java.util.*;

public class PixelFitter {
	int numSquares;
	boolean isUsed[][]; // pixellization of this shape, in 1's and 0's
	double squareSize; // how big each pixel is, in world-coordinates
	double maxDimension; // max of width, height
	boolean verbose;

	public PixelFitter(Cookie shape) {
		try {
			verbose = false; // whether or not to print output

			Vertex points[] = shape.getVertices();

			numSquares = 250; // how finely to grid this shape
			isUsed = new boolean[numSquares][numSquares];

			// --------------------------------------------------------------------
			// find maximum dimension (don't care if it's x or y)
			// of this shape -- so that we can determine size of a 'square'

			// !!! should have min x,y and max x,y

			maxDimension = -1d;
			double width = -1d;
			double height = -1d;

			double maxX, maxY;
			double minX, minY;

			maxX = points[0].xpos();
			minX = points[0].xpos();
			maxY = points[0].ypos();
			minY = points[0].ypos();

			for (int j = 1; j < points.length; j++) {
				// figure out max and min coordinates (bounding rectangle)
				if (points[j].xpos() > maxX)
					maxX = points[j].xpos();
				if (points[j].xpos() < minX)
					maxX = points[j].xpos();

				if (points[j].ypos() > maxY)
					maxY = points[j].ypos();
				if (points[j].ypos() < minY)
					maxY = points[j].ypos();
			}

			width = maxX - minX;
			height = maxY - minY;

			maxDimension = width;
			if (height > width)
				maxDimension = height;

			// --------------------------------------------------------------------
			// determine how high/wide a gridding square is on a side
			squareSize = maxDimension / numSquares;

			// --------------------------------------------------------------------
			// initialize isUsed matrix
			for (int i = 0; i < numSquares; i++) {
				for (int j = 0; j < numSquares; j++) {
					isUsed[i][j] = false;
				}
			}

			// --------------------------------------------------------------------
			// walk through isUsed matrix; set squares touched by a side to true
			for (int i = 0; i < numSquares; i++) {
				for (int j = 0; j < numSquares; j++) {
					// we are in the grid box defined by:
					// x = i*squareSize to (i+1)*squareSize
					// y = j*squareSize to (j+1)*squareSize

					for (int k = 0; k < points.length; k++) {
						// examining vertex points[k]

						// find vertex in polygon that follows k
						Vertex nextVertex;
						if (k + 1 == points.length) {
							// we're at end; need to loop back to beginning
							nextVertex = points[0];
						} else {
							// we're not at end; take k+1th point
							nextVertex = points[k + 1];
						}

						// figure out what verticies comprise this square
						Vertex topLeft =
							new Vertex(
								i * squareSize + minX,
								j * squareSize + minY);
						Vertex topRight =
							new Vertex(
								(i + 1) * squareSize + minX,
								j * squareSize + minY);
						Vertex bottomLeft =
							new Vertex(
								i * squareSize + minX,
								(j + 1) * squareSize + minY);
						Vertex bottomRight =
							new Vertex(
								(i + 1) * squareSize + minX,
								(j + 1) * squareSize + minY);

						if (CookieCutter
							.intersects(
								points[k],
								nextVertex,
								topLeft,
								topRight)
							|| CookieCutter.intersects(
								points[k],
								nextVertex,
								topLeft,
								bottomLeft)
							|| CookieCutter.intersects(
								points[k],
								nextVertex,
								bottomRight,
								topRight)
							|| CookieCutter.intersects(
								points[k],
								nextVertex,
								bottomLeft,
								bottomRight)) {
							isUsed[i][j] = true;
						}
					}
				}
			} // done examining line

			if (verbose)
				print();
		} catch (Exception e) {
			// should probably do something smart on exception
		}
	}

	public static CookieGroup fitTwo(Cookie first) {
		CookieGroup cookiegroup; // the CookieGroup itself
		List cookies = new ArrayList(); // to build CookieGruop

		// figure out solution
		PixelFitter pix = new PixelFitter(first);
		PixelSolution solution = pix.solve();
		solution.print();

		Vertex shape[] = first.getVertices();

		double top, bottom, right, left;
		top = PixelCookiePlacer.getMaxY(shape);
		bottom = PixelCookiePlacer.getMinY(shape);
		right = PixelCookiePlacer.getMaxX(shape);
		left = PixelCookiePlacer.getMinX(shape);

		double myWidth = right - left;
		double myHeight = top - bottom;
		double maxDimension = myWidth;

		if (myHeight > myWidth)
			maxDimension = myHeight;

		try {
			// place first cookie at default location			
			cookies.add(first.transform(CookieCutter.centroid(shape), 0));
			PixelCookiePlacer.print(shape);

			// now place up second cookie
			if (solution.xOffset == 0 && solution.yOffset == 0) {
				// no solution found

				// this part should be fixed up, but if we get here,
				// this solution is no good anyway				
				Vertex placement[] = PixelCookiePlacer.translate(shape, .4, 0);

				cookies.add(
					first.transform(CookieCutter.centroid(placement), 0));
				PixelCookiePlacer.print(placement);
			} else {
				// place second cookie according to solution we found
				Vertex placement[] = shape;
				double angle = 0;
				
				PixelCookiePlacer.print(placement);
				// rotate first
				if (solution.angle == 3 || solution.angle == 4) {
					placement = PixelCookiePlacer.rotate180(placement);
					angle = Math.PI;
				}

				PixelCookiePlacer.print(placement);

				// then translate
				if (solution.angle == 0 || solution.angle == 3) {
					placement =
						PixelCookiePlacer.translate(
							placement,
							maxDimension - solution.xOffset,
							-solution.yOffset);
				} else if (solution.angle == 1 || solution.angle == 4) {
					placement =
						PixelCookiePlacer.translate(
							placement,
							-solution.xOffset,
							maxDimension - solution.yOffset);
				}
				
				PixelCookiePlacer.print(placement);

				Cookie c = first.transform(CookieCutter.centroid(placement), angle);
				Cookie c1 = c.pack(true, cookies);
				Cookie c2 = c.pack(false, cookies);
				cookies.add((c1.getLargestX() < c2.getLargestX()) ? c1 : c2);
			}
		} catch (Exception e) {
		}

		cookiegroup = new CookieGroup(cookies);
		return cookiegroup;
	}

	public PixelSolution solve() {
		PixelSolution firstSolution = lookForFits(0);
		PixelSolution secondSolution = lookForFits(1);

		if (firstSolution.goodness > secondSolution.goodness) {			
			return firstSolution;
		}		
		return secondSolution;
	}

	public PixelSolution lookForFits(int direction) {
		int valueOfBestSolution = -1;
		int directionOfBestSolution = -1;
		int firstIndexOfBestSolution = -1;
		int secondIndexOfBestSolution = -1;

		// try to stack rows

		// algorithm:
		// - for each edge row:
		//   - find rows that fit with it; for each fitting row found,
		//     - work in both directions to see if adjacent rows match

		int edgeIndex = 0; // use top edge
		int edgeDirection = direction; // do rows or columns according 
		// argument

		// look at each row/column
		for (int i = 0; i < numSquares; i++) {
			// examine all rows for overlaps, from the top down
			if (fitTogether(edgeIndex, edgeDirection, i, edgeDirection)) {
				boolean failed = false;
				// this edge matches row/column 'i'

				// now test rows above
				for (int j = 1; j < numSquares; j++) {
					if (edgeIndex + j == numSquares
						|| edgeIndex + j < 0
						|| i + j == numSquares
						|| i + j < 0) {
						// we've done all the matching we can do in this direction and
						// things seem to have worked; stop
						break;
					}

					if (!fitTogether(edgeIndex + j,
						edgeDirection,
						i + j,
						edgeDirection)) {
						// these rows don't fit -- not a match
						failed = true;
						break;
					}
				}
				if (!failed) {
					int value = numSquares - i; // number of rows overlapping

					if (valueOfBestSolution < value) {
						valueOfBestSolution = value;
						directionOfBestSolution = edgeDirection;
						firstIndexOfBestSolution = edgeIndex;
						secondIndexOfBestSolution = i;
					}
				}
			}

			//	    System.out.println("comparing row "+edgeIndex+" and inverse row "
			//			       +i+".");
			//			       +(numSquares-i-1)+".");

			// compare with a 180-degree rotation
			if (fitTogether(edgeIndex, edgeDirection, i, edgeDirection + 2))
				//	    if (fitTogether(edgeIndex, edgeDirection, numSquares-i-1, edgeDirection+2))
				{
				// this edge matches row/column 'i'
				boolean failed = false;

				//		System.out.println("got a fit");

				// now test inverted rows from bottom up
				for (int j = 1; j < numSquares; j++) {
					//		    System.out.println("downwards iterator="+(edgeIndex+j));
					//		    System.out.println("upwards iterator="+(numSquares-i-1-j));
					//		    System.out.println("(threshold=0 or "+numSquares+")");

					if (edgeIndex + j == numSquares
						|| edgeIndex + j < 0
						|| i - j == numSquares
						|| i - j < 0)
						//			(numSquares-i-1-j)==numSquares || (numSquares-i-1)-j<0)
						{
						// we've done all the matching we can do and
						// things seem to have worked; stop
						break;
					}

					if (!fitTogether(edgeIndex + j,
						edgeDirection,
						i - j,
						edgeDirection + 2))
						//				     numSquares-i-1-j, edgeDirection+2))
						{
						//			System.out.println("row "+(edgeIndex+j)
						//					   +" does not fit into inverse row "
						//					   +(i-j)
						//					   +".");
						// these rows don't fit -- not a match
						failed = true;
						break;
					}

					//		    System.out.println("row "+(edgeIndex+j)
					//				       +" fits into inverse row "
					//				       +(i-j)
					//				       +"...  the search continues.");
				}
				// if they fit exactly, we'll get here without having a 'break'

				if (!failed) {
					//		    System.out.println("row "+edgeIndex+" fits into inverse row "
					//				       +i+".");

					int value = i; // number of rows overlapping

					if (valueOfBestSolution < value) {
						valueOfBestSolution = value;
						directionOfBestSolution = edgeDirection + 2;
						firstIndexOfBestSolution = edgeIndex;
						secondIndexOfBestSolution = i;
					}
				}
			}

			// keep looking for places this edge row might fit
		} // done testing if edge fits into any rows

		if (verbose)
			System.out.println(
				"best solution: first index="
					+ firstIndexOfBestSolution
					+ ", second index="
					+ secondIndexOfBestSolution
					+ ", direction="
					+ directionOfBestSolution
					+ ", overlap="
					+ valueOfBestSolution
					+ " rows");

		PixelSolution solution = new PixelSolution();

		if (valueOfBestSolution != -1) {
			// solutions found
			if (verbose)
				System.out.println(
					"each row of overlap= " + (maxDimension / numSquares));
			if (directionOfBestSolution == 2) {
				solution.angle = 3;
			} else if (directionOfBestSolution == 1) {
				solution.angle = 1;
			} else if (directionOfBestSolution == 3) {
				solution.angle = 4;
			} else {
				solution.angle = 0;
			}

			if (directionOfBestSolution == 0 || directionOfBestSolution == 2) {
				solution.xOffset =
					valueOfBestSolution * maxDimension / numSquares;
				solution.yOffset = 0;
				solution.goodness = solution.xOffset;
			} else {
				solution.yOffset =
					valueOfBestSolution * maxDimension / numSquares;
				solution.xOffset = 0;
				solution.goodness = solution.yOffset;
			}
		}
		return solution;
	}

	public boolean fitTogether(
		int firstIndex,
		int firstDirection,
		int secondIndex,
		int secondDirection)
	// determine if the supplied rows/columns fit into one another
	// - 'direction' = 0 for row, 1 for column, 2 for inverted row, 3
	// - for inverted column
	// - 'index' = the number of the row/column to examine

	// !!! are there bugs in this function I haven't found?
	{
		// work through each value in given row(s)/column(s)
		for (int j = 0; j < numSquares; j++) {
			boolean firstValue, secondValue;

			if (firstDirection == 0) {
				// first is a row
				firstValue = isUsed[firstIndex][j];
			} else if (firstDirection == 1) {
				// first is a column
				firstValue = isUsed[j][firstIndex];
			} else if (firstDirection == 2) {
				// first is an inverted row
				firstValue = isUsed[firstIndex][numSquares - j - 1];
			} else // if (firstDirection==3)
				{
				// first is an inverted column
				firstValue = isUsed[numSquares - j - 1][firstIndex];
			}

			if (secondDirection == 0) {
				// second is a row
				secondValue = isUsed[secondIndex][j];
			} else if (secondDirection == 1) {
				// second is a column
				secondValue = isUsed[j][secondIndex];
			} else if (secondDirection == 2) {
				// second is an inverted row
				secondValue = isUsed[secondIndex][numSquares - j - 1];
			} else // if (secondDirection==3)
				{
				// second is an inverted column
				secondValue = isUsed[numSquares - j - 1][secondIndex];
			}

			if (firstValue == true && secondValue == true) {
				// both rows/columns require this square; no-go
				return false;
			}
		}

		// didn't find any conflicts
		return true;
	}

	public void print() {
		System.out.println("-----");
		for (int i = 0; i < numSquares; i++) {
			for (int j = 0; j < numSquares; j++) {
				if (isUsed[i][j] == true) {
					System.out.print("1 ");
				} else {
					System.out.print("0 ");
				}
			}
			System.out.println("");
		}
	}
}

/*
	public Move[] moves() throws Exception
	{
		_random = new Random();
		Move[] RET = new Move[_cookies.length];
		for (int i = 0; i < _cookies.length; i++)
		{
//		    System.out.println("placing cookie shape #"+i);

		    // in here we're working with only one shape
		    // (perhaps several cookies)

		    RET[i] = new Move(_copies);

		    // ------------------------------------------------
		    // figure out some universal stuff
		    double angle = 0; // _random.nextDouble();
//		    Vertex center = CookieCutter.centroid(_cookies[i]);
		    Vertex center = _cookiecutter.centroid(_cookies[i]);
//		    System.out.println("centroid = "+center.xpos()+","+center.ypos());

		    // this will hold the total width of all the cookies we've placed so far
		    double maxX = 0d;

		    // this holds the previous value of maxX
		    double lastMaxX = 0d;

		    // column-related variables
		    double maxYThisColumn = 0d;
		    int maxDepth = 0; // how many we can stack on top of one another

		    // ------------------------------------------------
		    // grid-ify this shape

		    Grid grid = new Grid(_cookies[i]);

		    // find useful solutions
		    GridSolution rightSolution = grid.lookForFits(0);
		    GridSolution bottomSolution = grid.lookForFits(1);

		    System.out.println("done looking for fits");

		    // figure out how many cookies we can place up-and-down
		    int columnSize = 1;
		    if (bottomSolution.direction==1 &&
			bottomSolution.value!=-1)
		    {
			// inversion not required
			columnSize = (int)((1-grid.height)/(grid.height-bottomSolution.value))+1;
		    }
		    else if (bottomSolution.direction==3 &&
			     bottomSolution.value!=-1)
		    {
			// inversion required for packing

			// iterate through possible column sizes until no more objects will fit
			while ( (columnSize+2)*grid.height/2 +
				(columnSize+1)*(grid.height-bottomSolution.value)/2 < 1)
			{
			    columnSize++;
			    System.out.print(".");

			    if (columnSize>20) // error???
				break;
			}
		    }
		    else
		    {
			// no valid packing solutions; stack
			
			columnSize = (int)(1d/grid.height);
		    }

		    System.out.println("column size = "+columnSize);

//		    GridSolution solution = rightSolution;

		    // place cookies
		    for (int j = 0; j < _copies; j++)
		    {
			System.out.println("placing cookie #"+j+", start x = "+maxX);
			
			if (j%columnSize==0)
			{
			    // beginning of a new column
			    lastMaxX=maxX;
			    maxX+=grid.width;
			}
			
			// where we'll place this cookie
			Vertex newLocation[] = new Vertex[_cookies[i].length];
			
			if (bottomSolution.direction==1)
			{
			    angle=0; 			// figure out angle (easy)

			    // figure out placement
			    for (int k=0; k<newLocation.length; k++)
			    {
				newLocation[k]=new Vertex(_cookies[i][k].xpos()-grid.minX
							  + lastMaxX,
							  _cookies[i][k].ypos()-grid.minY
							  + j%columnSize*grid.height
							  - bottomSolution.value);
			    }
			}
			else // bottomSolution==3
			{
			    angle=3.14159;		// figure out angle (easy)

			    // figure out placement
			    for (int k=0; k<newLocation.length; k++)
			    {
				newLocation[k]=new Vertex(_cookies[i][k].xpos()-grid.minX
							  + lastMaxX,
							  _cookies[i][k].ypos()-grid.minY
							  +maxYThisColumn);
			    }
			}

			Vertex hash = _cookiecutter.centroid(newLocation);
			RET[i].setCookiePosition(j,
						 new Vertex(hash.xpos(),
							    hash.ypos()),
						 angle);

			// first, see if we can place this cookie below the previous
			if (maxYThisColumn>0 && 
			    maxYThisColumn+grid.height<=1)
			{
			    System.out.println("placing cookie underneath. maxY="+maxYThisColumn
					       +", grid.height="+grid.height);

			    // this shape will fit under
			    Vertex newLocation[] = new Vertex[_cookies[i].length];
			    angle=0;
			    for (int k=0; k<newLocation.length; k++)
			    {
				newLocation[k]=new Vertex(_cookies[i][k].xpos()-grid.minX
							  + lastMaxX,
							  _cookies[i][k].ypos()-grid.minY
							  +maxYThisColumn);
			    }
			    
			    Vertex revisedCenter = _cookiecutter.centroid(newLocation);
			    RET[i].setCookiePosition(j,
						     new Vertex(revisedCenter.xpos(),
								revisedCenter.ypos()),
						     angle);


			    // don't increase maxX since we didn't move right
			    maxYThisColumn+=grid.height+.0001; // but increase maxY

			    // keep track of how many rows we're making
			    if (lastMaxX==0)
				maxDepth+=1;
			}
			else if (j%2==0)
			{
			    // place even (0,2,...) cookie on edge of workspace

			    double x = center.xpos()+maxX-grid.minX;
			    double y = center.ypos()-grid.minY;

			    System.out.println("location: x="+x
					       +" y="+y);

			    RET[i].setCookiePosition(j,
						     new Vertex(x,y),
						     0);
			    maxYThisColumn=grid.height+.0001;
			    lastMaxX = maxX;
			    maxX+=grid.width+.0001;
//			    maxX+=grid.maxDimension+.0001;

//			     new Vertex(j + 0.5, 0.5), angle);
			}
			else
			{
			    // place odd cookie so it fits into
			    // previous, if we found a way to do that
			    if (solution.value==-1)
			    {
				// no way to fit
				double x = center.xpos()+maxX-grid.minX;
				double y = center.ypos()-grid.minY;

				RET[i].setCookiePosition(j,
							 new Vertex(x,y),
							 0);
				maxYThisColumn=grid.height+.0001;
				lastMaxX = maxX;
				maxX+=grid.width+.0001;
			    }
			    else
			    {
				// there was a packing solution

				System.out.println("using solution:");
				solution.print();
				
 				Vertex newLocation[] = new Vertex[_cookies[i].length];

				// figure out if we need to rotate 
				if (solution.direction==0)
				{
				    angle=0;
				    for (int k=0; k<newLocation.length; k++)
				    {
					newLocation[k]=new Vertex(_cookies[i][k].xpos()-grid.minX
								  + maxX - solution.value
								  + (grid.maxDimension-grid.width),
								  _cookies[i][k].ypos()-grid.minY);
				    }
				}
				else if (solution.direction==2)
				{
				    // roll backwards
				    angle=3.14159;

				    // !!! need to evaluate correctness of this function
				    for (int k=0; k<newLocation.length; k++)
				    {
// 					double x = grid.width-_cookies[i][k].xpos()-grid.minX;
// 					double y = grid.height-_cookies[i][k].ypos()-grid.minY;
					double x = grid.maxDimension-(_cookies[i][k].xpos()-grid.minX)
					    + maxX - solution.value;
					double y = grid.maxDimension-(_cookies[i][k].ypos()-grid.minY);
					
					System.out.println("point ("+x+","
							   +y+")");
					
					newLocation[k]=new Vertex(x, y);
				    }
				}

				Vertex revisedCenter = _cookiecutter.centroid(newLocation);
				RET[i].setCookiePosition(j,
							 new Vertex(revisedCenter.xpos(),
								    revisedCenter.ypos()),
								    angle);

//				Vertex revisedCenter = CookieCutter.centroid(newLocation);

// 				System.out.println("center's xpos="+revisedCenter.xpos()
// 						   +", maxX="+maxX
// 						   +", solution.value="+solution.value
// 						   +", width="+grid.width
// 						   +", maxDimension="+grid.maxDimension);

// 				System.out.println("location: x="+
// 						   (maxX + revisedCenter.xpos()
// 						    - solution.value)
// 						   +" y="+revisedCenter.ypos()
// 						   +" angle="+angle);

// 				Vertex revisedCenter = _cookiecutter.centroid(newLocation);
// 				RET[i].setCookiePosition(j,
// 							 new Vertex(revisedCenter.xpos(),
// 								    revisedCenter.ypos()),

// 							     maxX
// 								    + revisedCenter.xpos()
// 								    - solution.value,
//   //							     width - revisedCenter.xpos()
//   //							     + maxX - solution.value,
// 								    revisedCenter.ypos()),
//								    angle);
							 
				// only add as much space as solution requires
				maxYThisColumn=grid.height+.0001;
				lastMaxX = maxX;
				maxX+=(grid.maxDimension-solution.value)+.0001;
			    }
			}
		    }
		    System.out.println("placing right boundary at: "+maxX);
		    RET[i].setRightBoundary(maxX);
		}
		return RET;
	}
}

class Grid
{
    int gridSize;
    int numSquares;
    double squareSize;
    boolean isUsed[][];
    public double maxDimension;
    public double width;
    public double height;
    public double minX, minY;

    public Grid(Vertex points[]) throws Exception
    {
	numSquares = 20; // how finely to grid this shape
	isUsed = new boolean[numSquares][numSquares];

	// --------------------------------------------------------------------
	// find maximum dimension (don't care if it's x or y)
	// of this shape -- so that we can determine size of a 'square'

	// !!! should have min x,y and max x,y
	maxDimension=-1d;
	width=-1d;
	height=-1d;

	double maxX, maxY;

	maxX = points[0].xpos();
	minX = points[0].xpos();
	maxY = points[0].ypos();
	minY = points[0].ypos();

	for (int j=1; j<points.length; j++)
	{
	    // figure out max and min coordinates (bounding rectangle)
	    if (points[j].xpos()>maxX)
		maxX=points[j].xpos();
	    if (points[j].xpos()<minX)
		maxX=points[j].xpos();

	    if (points[j].ypos()>maxY)
		maxY=points[j].ypos();
	    if (points[j].ypos()<minY)
		maxY=points[j].ypos();
	}

	width = maxX-minX;
	height= maxY-minY;

	maxDimension = width;
	if (height>width)
	    maxDimension=height;

	// --------------------------------------------------------------------
	// determine how high/wide a gridding square is on a side
	squareSize = maxDimension/numSquares;
	
	// --------------------------------------------------------------------
	// initialize isUsed matrix
	for (int i = 0; i < numSquares; i++)
	{
	    for (int j = 0; j < numSquares; j++)
	    {
		isUsed[i][j]=false;
	    }
	}

	// --------------------------------------------------------------------
	// walk through isUsed matrix; set squares touched by a side to true
	for (int i = 0; i < numSquares; i++)
	{
	    for (int j = 0; j < numSquares; j++)
	    {
		// we are in the grid box defined by:
		// x = i*squareSize to (i+1)*squareSize
		// y = j*squareSize to (j+1)*squareSize

		for (int k = 0; k < points.length; k++)
		{
		    // examining vertex points[k]

		    // find vertex in polygon that follows k
		    Vertex nextVertex;
		    if (k+1==points.length)
		    {
			// we're at end; need to loop back to beginning
			nextVertex = points[0];
		    }
		    else
		    {
			// we're not at end; take k+1th point
			nextVertex = points[k+1];
		    }

		    // figure out what verticies comprise this square
		    Vertex topLeft = new Vertex(i*squareSize+minX,
						j*squareSize+minY);
		    Vertex topRight = new Vertex((i+1)*squareSize+minX,
						 j*squareSize+minY);
		    Vertex bottomLeft = new Vertex(i*squareSize+minX,
						   (j+1)*squareSize+minY);
		    Vertex bottomRight = new Vertex((i+1)*squareSize+minX,
						    (j+1)*squareSize+minY);

		    if (CookieCutter.intersects(points[k], nextVertex,
						topLeft, topRight) ||
			CookieCutter.intersects(points[k], nextVertex,
						topLeft, bottomLeft) ||
			CookieCutter.intersects(points[k], nextVertex,
						bottomRight, topRight) ||
			CookieCutter.intersects(points[k], nextVertex,
						bottomLeft, bottomRight))
		    {
			isUsed[i][j]=true;
		    }
		}
	    }
	} // done examining line

	// walk through isUsed matrix; set squares interior to a side
	// to true
	for (int i = 0; i < numSquares; i++)
	{
	    boolean isInside = false;
	    for (int j = 0; j < numSquares; j++)
	    {
		if (isUsed[i][j]==true)
		{
		    if (j==0 || isUsed[i][j-1]==false)
		    {
			// we're on an edge; invert sense of insideness
			inside = !inside;
		    }
		}
		else
		{
		    // we're not on an edge; this square was false
		    // 
		    // need to set it to whether or not we're inside
		    isUsed[i][j]=inside;
		}
	    }
	    }

	print();
    } // end of grid constructor
    
    public void print()
    {
	System.out.println("-----");
	for (int i = 0; i < numSquares; i++)
	{
	    for (int j = 0; j < numSquares; j++)
	    {
		if (isUsed[i][j]==true)
		{
		    System.out.print("1 ");
		}
		else
		{
		    System.out.print("0 ");
		}
	    }
	    System.out.println("");
	}
    }

    public GridSolution lookForFits()
    {
	int valueOfBestSolution = -1;
	int directionOfBestSolution = -1;
	int firstIndexOfBestSolution = -1;
	int secondIndexOfBestSolution = -1;

	// try to stack rows

	// algorithm:
	// - for each edge row:
	//   - find rows that fit with it; for each fitting row found,
	//     - work in both directions to see if adjacent rows match

	int edgeIndex = 0;         // use top edge
	int edgeDirection = 0;     // do rows, not columns

	// look at each row/column
	for (int i = 0; i < numSquares; i++)
	{
	    // examine all rows for overlaps, from the top down
	    if (fitTogether(edgeIndex, edgeDirection, i, edgeDirection))
	    {
		boolean failed = false;
		// this edge matches row/column 'i'

		// now test rows above
		for (int j = 1; j < numSquares; j++)
		{
		    if (edgeIndex+j==numSquares || edgeIndex+j<0 ||
			i+j==numSquares || i+j<0)
		    {
			// we've done all the matching we can do in this direction and
			// things seem to have worked; stop
			break;
		    }

		    if (!fitTogether(edgeIndex+j, edgeDirection, i+j, edgeDirection))
		    {
			// these rows don't fit -- not a match
			failed=true;
			break;
		    }
		}
		if (!failed)
		{
		    System.out.println("row "+edgeIndex+" fits into row "+i+".");
		    int value = numSquares-i; // number of rows overlapping

		    if (valueOfBestSolution<value)
		    {
			valueOfBestSolution=value;
			directionOfBestSolution = edgeDirection;
			firstIndexOfBestSolution = edgeIndex;
			secondIndexOfBestSolution = i;
		    }
		}
	    }

//	    System.out.println("comparing row "+edgeIndex+" and inverse row "
//			       +i+".");
//			       +(numSquares-i-1)+".");

	    // compare with a 180-degree rotation
	    if (fitTogether(edgeIndex, edgeDirection, i, edgeDirection+2))
//	    if (fitTogether(edgeIndex, edgeDirection, numSquares-i-1, edgeDirection+2))
	    {
		// this edge matches row/column 'i'
		boolean failed = false;

//		System.out.println("got a fit");

		// now test inverted rows from bottom up
		for (int j = 1; j < numSquares; j++)
		{
  //		    System.out.println("downwards iterator="+(edgeIndex+j));
  //		    System.out.println("upwards iterator="+(numSquares-i-1-j));
  //		    System.out.println("(threshold=0 or "+numSquares+")");

		    if (edgeIndex+j==numSquares || edgeIndex+j<0 ||
			i-j==numSquares || i-j<0)
//			(numSquares-i-1-j)==numSquares || (numSquares-i-1)-j<0)
		    {
			// we've done all the matching we can do and
			// things seem to have worked; stop
			break;
		    }

		    if (!fitTogether(edgeIndex+j, edgeDirection,
				     i-j, edgeDirection+2))
//				     numSquares-i-1-j, edgeDirection+2))
		    {
//			System.out.println("row "+(edgeIndex+j)
//					   +" does not fit into inverse row "
//					   +(i-j)
//					   +".");
			// these rows don't fit -- not a match
			failed=true;
			break;
		    }

//		    System.out.println("row "+(edgeIndex+j)
//				       +" fits into inverse row "
//				       +(i-j)
//				       +"...  the search continues.");
		}
		// if they fit exactly, we'll get here without having a 'break'

		if (!failed)
		{
//		    System.out.println("row "+edgeIndex+" fits into inverse row "
//				       +i+".");

		    int value = i; // number of rows overlapping

		    if (valueOfBestSolution<value)
		    {
			valueOfBestSolution=value;
			directionOfBestSolution = edgeDirection+2;
			firstIndexOfBestSolution = edgeIndex;
			secondIndexOfBestSolution = i;
		    }
		}
	    }

	    // keep looking for places this edge row might fit
	} // done testing if edge fits into any rows

	System.out.println("best solution: first index="+firstIndexOfBestSolution
			   +", second index="+secondIndexOfBestSolution
			   +", direction="+directionOfBestSolution
			   +", overlap="+valueOfBestSolution+" rows");

	if (valueOfBestSolution==-1)
	{
	    // no solutions found
	    return new GridSolution(-1d, -1, -1, -1);
	}

	System.out.println("each row of overlap= "+(maxDimension/numSquares));
	return new GridSolution(valueOfBestSolution*maxDimension/numSquares,
				directionOfBestSolution,
				firstIndexOfBestSolution,
				secondIndexOfBestSolution);

	    // look at each row above this row
		if (fitTogether(i, 0, j, 0))
		{
		    // rows i and j fit

		    boolean misfit = false;

		    // see if i+k and j+k fit
		    for (int k=1; k<numSquares-j; k++)
		    {
			if (!fitTogether(i+k, 0, j+k, 0))
			{
			    misfit = true;
			    break;
			}
		    }
		    if (!misfit)
		    {
			System.out.println("row "+i+" fits into row "+j+".");
			done=true;
			break;
		    }
		}
	    }
	    if (done)
		break;
	}


	// try to stack columns

	// look at each column
	for (int i = 0; i < numSquares; i++)
	{
	    // look at each column to this column's right
	    for (int j = i+1; j < numSquares; j++)
	    {
		if (fitTogether(i, 1, j, 1))
		{
		    // columns i and j fit

		    boolean misfit = false;

		    // see if i+k and j+k fit
		    for (int k=1; k<numSquares-j; k++)
		    {
			if (!fitTogether(i+k, 1, j+k, 1))
			{
			    misfit = true;
			    break;
			}
		    }
		    if (!misfit)
		    {
			System.out.println("column "+i+" fits into column "+j+".");
			done=true;
			break;
		    }
		}
	    }
	    if (done)
		break;
	}

	// try to fit rows into columns

	// look at each row
	for (int i = 0; i < numSquares; i++)
	{
	    // look at each column
	    for (int j = 0; j < numSquares; j++)
	    {
		if (fitTogether(i, 0, j, 1))
		{
		    // row i and column j fit

		    boolean misfit = false;

		    // see if i+k and j+k fit
		    for (int k=1; k<numSquares-j; k++)
		    {
			if (!fitTogether(i+k, 0, j+k, 1))
			{
			    misfit = true;
			    break;
			}
		    }
		    if (!misfit)
		    {
			System.out.println("row "+i+" fits into column "+j+".");
			done=true;
			break;
		    }
		}
	    }
	    if (done)
		break;
	}


	for (int i = 0; i < numSquares; i++)
	{
	    for (int j = 0; j < numSquares; j++)
	    {
		// examine row i and row j
		if (fitTogether(i, 0, j, 0))
		{
		    System.out.println("row "+i+" and row "+j+" fit");
		}

		// examine row i and column j
		if (fitTogether(i, 0, j, 1))
		{
		    System.out.println("row "+i+" and column "+j+" fit");
		}

		// examine column i and row j
		if (fitTogether(i, 1, j, 0))
		{
		    System.out.println("column "+i+" and row "+j+" fit");
		}

		// examine column i and column j
		if (fitTogether(i, 1, j, 1))
		{
		    System.out.println("column "+i+" and column "+j+" fit");
		}
	    }
	}
    }

    public GridSolution lookForFits(int direction)
    {
	int valueOfBestSolution = -1;
	int directionOfBestSolution = -1;
	int firstIndexOfBestSolution = -1;
	int secondIndexOfBestSolution = -1;

	// try to stack rows

	// algorithm:
	// - for each edge row:
	//   - find rows that fit with it; for each fitting row found,
	//     - work in both directions to see if adjacent rows match

	int edgeIndex = 0;                 // use top edge
	int edgeDirection = direction;     // do rows or columns according to variable

	// look at each row/column
	for (int i = 0; i < numSquares; i++)
	{
	    // examine all rows for overlaps, from the top down
	    if (fitTogether(edgeIndex, edgeDirection, i, edgeDirection))
	    {
		boolean failed = false;
		// this edge matches row/column 'i'

		// now test rows above
		for (int j = 1; j < numSquares; j++)
		{
		    if (edgeIndex+j==numSquares || edgeIndex+j<0 ||
			i+j==numSquares || i+j<0)
		    {
			// we've done all the matching we can do in this direction and
			// things seem to have worked; stop
			break;
		    }

		    if (!fitTogether(edgeIndex+j, edgeDirection, i+j, edgeDirection))
		    {
			// these rows don't fit -- not a match
			failed=true;
			break;
		    }
		}
		if (!failed)
		{
		    System.out.println("row "+edgeIndex+" fits into row "+i+".");
		    int value = numSquares-i; // number of rows overlapping

		    if (valueOfBestSolution<value)
		    {
			valueOfBestSolution=value;
			directionOfBestSolution = edgeDirection;
			firstIndexOfBestSolution = edgeIndex;
			secondIndexOfBestSolution = i;
		    }
		}
	    }

//	    System.out.println("comparing row "+edgeIndex+" and inverse row "
//			       +i+".");
//			       +(numSquares-i-1)+".");

	    // compare with a 180-degree rotation
	    if (fitTogether(edgeIndex, edgeDirection, i, edgeDirection+2))
//	    if (fitTogether(edgeIndex, edgeDirection, numSquares-i-1, edgeDirection+2))
	    {
		// this edge matches row/column 'i'
		boolean failed = false;

//		System.out.println("got a fit");

		// now test inverted rows from bottom up
		for (int j = 1; j < numSquares; j++)
		{
  //		    System.out.println("downwards iterator="+(edgeIndex+j));
  //		    System.out.println("upwards iterator="+(numSquares-i-1-j));
  //		    System.out.println("(threshold=0 or "+numSquares+")");

		    if (edgeIndex+j==numSquares || edgeIndex+j<0 ||
			i-j==numSquares || i-j<0)
//			(numSquares-i-1-j)==numSquares || (numSquares-i-1)-j<0)
		    {
			// we've done all the matching we can do and
			// things seem to have worked; stop
			break;
		    }

		    if (!fitTogether(edgeIndex+j, edgeDirection,
				     i-j, edgeDirection+2))
//				     numSquares-i-1-j, edgeDirection+2))
		    {
//			System.out.println("row "+(edgeIndex+j)
//					   +" does not fit into inverse row "
//					   +(i-j)
//					   +".");
			// these rows don't fit -- not a match
			failed=true;
			break;
		    }

//		    System.out.println("row "+(edgeIndex+j)
//				       +" fits into inverse row "
//				       +(i-j)
//				       +"...  the search continues.");
		}
		// if they fit exactly, we'll get here without having a 'break'

		if (!failed)
		{
//		    System.out.println("row "+edgeIndex+" fits into inverse row "
//				       +i+".");

		    int value = i; // number of rows overlapping

		    if (valueOfBestSolution<value)
		    {
			valueOfBestSolution=value;
			directionOfBestSolution = edgeDirection+2;
			firstIndexOfBestSolution = edgeIndex;
			secondIndexOfBestSolution = i;
		    }
		}
	    }

	    // keep looking for places this edge row might fit
	} // done testing if edge fits into any rows

	System.out.println("best solution: first index="+firstIndexOfBestSolution
			   +", second index="+secondIndexOfBestSolution
			   +", direction="+directionOfBestSolution
			   +", overlap="+valueOfBestSolution+" rows");

	if (valueOfBestSolution==-1)
	{
	    // no solutions found
	    return new GridSolution(-1d, -1, -1, -1);
	}

	System.out.println("each row of overlap= "+(maxDimension/numSquares));
	return new GridSolution(valueOfBestSolution*maxDimension/numSquares,
				directionOfBestSolution,
				firstIndexOfBestSolution,
				secondIndexOfBestSolution);
    }


    public boolean fitTogether(int firstIndex, int firstDirection,
			       int secondIndex, int secondDirection)
    // determine if the supplied rows/columns fit into one another
    // - 'direction' = 0 for row, 1 for column, 2 for inverted row, 3 for inverted column
    //    !!! 2 and 3 not implemented !!!
    // - 'index' = the number of the row/column to examine
    {
	// work through each value in given row(s)/column(s)
	for (int j = 0; j < numSquares; j++)
	{
	    boolean firstValue, secondValue;
	    
	    if (firstDirection==0)
	    {
		// first is a row
		firstValue = isUsed[firstIndex][j];
	    }
	    else if (firstDirection==1)
	    {
		// first is a column
		firstValue = isUsed[j][firstIndex];
	    }
 	    else if (firstDirection==2)
 	    {
 		// first is an inverted row
		firstValue = isUsed[firstIndex][numSquares-j-1];
 	    }
 	    else // if (firstDirection==3)
 	    {
 		// first is an inverted column
		firstValue = isUsed[numSquares-j-1][firstIndex];
 	    }

	    if (secondDirection==0)
	    {
		// second is a row
		secondValue = isUsed[secondIndex][j];
	    }
	    else if (secondDirection==1)
	    {
		// second is a column
		secondValue = isUsed[j][secondIndex];
	    }
	    else if (secondDirection==2)
	    {
		// second is an inverted row
		secondValue = isUsed[secondIndex][numSquares-j-1];
	    }
	    else // if (secondDirection==3)
	    {
		// second is an inverted column
		secondValue = isUsed[numSquares-j-1][secondIndex];
	    }

	    if (firstValue==true && secondValue==true)
	    {
		// both rows/columns require this square; no-go
		return false;
	    }
	}

	// didn't find any conflicts
	return true;
	}

    // test function to find rows that fit into one another
    // simple and not exhaustive
    public void fitTwo()
    {
	// --------------------------------------------------------------------

	// walk through isUsed matrix; compare each row to each other
	// row to see if it fits in
	for (int firstRow = 0; firstRow < numSquares; firstRow++)
	{
	    for (int secondRow = 0; secondRow < numSquares; secondRow++)
	    {
		boolean fit = true;

		// for these two rows, see if they can safely coexist
		for (int j = 0; j < numSquares; j++)
		{
		    if (isUsed[firstRow][j]==true &&
			isUsed[secondRow][j]==true)
		    {
			// both rows need this box; not a match
			fit = false;
			break;
		    }
		}
		
		if (fit)
		    System.out.println("rows "+firstRow+" and "+secondRow
				       +" fit together");

	    }
	}
    }
}


class GridSolution
{
    double value;
    int direction, firstIndex, secondIndex;
    
    public GridSolution(double val, int dir, int index1, int index2)
    {
	value=val;
	direction = dir;
	firstIndex = index1;
	secondIndex = index2;
    }

    public void print()
    {
	System.out.println("solution: first index="+firstIndex
			   +", second index="+secondIndex
			   +", direction="+direction
			   +", overlap="+value);

    }
}
*/
