/*
 * JasperReports - Free Java Reporting Library.
 * Copyright (C) 2001 - 2009 Jaspersoft Corporation. All rights reserved.
 * http://www.jaspersoft.com
 *
 * Unless you have purchased a commercial license agreement from Jaspersoft,
 * the following license terms apply:
 *
 * This program is part of JasperReports.
 *
 * JasperReports is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * JasperReports is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public License
 * along with JasperReports. If not, see <http://www.gnu.org/licenses/>.
 */

/*
 * Contributors:
 * Greg Hilton
 */

package net.sf.jasperreports.engine.export;

import java.awt.Color;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import net.sf.jasperreports.engine.JRBoxContainer;
import net.sf.jasperreports.engine.JRLineBox;
import net.sf.jasperreports.engine.JRPrintElement;
import net.sf.jasperreports.engine.JRPrintFrame;
import net.sf.jasperreports.engine.JRPrintPage;
import net.sf.jasperreports.engine.base.JRBasePrintFrame;
import net.sf.jasperreports.engine.type.ModeEnum;
import net.sf.jasperreports.engine.util.JRBoxUtil;

/**
 * Utility class used by grid exporters to create a grid for page layout.
 *
 * @author Lucian Chirita (lucianc@users.sourceforge.net)
 * @version $Id: JRGridLayout.java 3659 2010-03-31 10:20:49Z shertage $
 */
public class JRGridLayout
{
	private final ExporterNature nature;

	private int width;
	private int height;
	private int offsetX;
	private int offsetY;
	private final String address;

	private CutsInfo xCuts;
	private CutsInfo yCuts;
	private JRExporterGridCell[][] grid;

	private Map boxesCache;

	private int virtualFrameIndex = 0;

	private boolean hasTopMargin = true;
	private boolean hasBottomMargin = true;
	private boolean hasLeftMargin = true;
	private boolean hasRightMargin = true;

	private boolean isNested;

	/**
	 * Constructor.
	 *
	 * @param elements the elements that should arranged in a grid
	 * @param width the width available for the grid
	 * @param height the height available for the grid
	 * @param offsetX horizontal element position offset
	 * @param offsetY vertical element position offset
	 */
	public JRGridLayout(
		ExporterNature nature,
		List elements,
		int width,
		int height,
		int offsetX,
		int offsetY
		)
	{
		this(
			nature,
			elements,
			width,
			height,
			offsetX,
			offsetY,
			null //xCuts
			);
	}

	/**
	 * Constructor.
	 *
	 * @param elements the elements that should arranged in a grid
	 * @param width the width available for the grid
	 * @param height the height available for the grid
	 * @param offsetX horizontal element position offset
	 * @param offsetY vertical element position offset
	 * @param xCuts An optional list of pre-calculated X cuts.
	 */
	public JRGridLayout(
		ExporterNature nature,
		List elements,
		int width,
		int height,
		int offsetX,
		int offsetY,
		CutsInfo xCuts
		)
	{
		this.nature = nature;
		this.height = height;
		this.width = width;
		this.offsetX = offsetX;
		this.offsetY = offsetY;
		this.address = null;
		this.xCuts = xCuts;

		boxesCache = new HashMap();

		virtualFrameIndex = elements.size();

		layoutGrid(createWrappers(elements, address));

		if (nature.isSplitSharedRowSpan())
		{
			splitSharedRowSpanIntoNestedGrids();
		}
	}

	/**
	 * Constructor.
	 *
	 * @param wrappers the element wrappers that should arranged in a grid
	 * @param width the width available for the grid
	 * @param height the height available for the grid
	 * @param offsetX horizontal element position offset
	 * @param offsetY vertical element position offset
	 * @param address element address
	 */
	protected JRGridLayout(
		ExporterNature nature,
		ElementWrapper[] wrappers,
		int width,
		int height,
		int offsetX,
		int offsetY,
		String address
		)
	{
		this.nature = nature;
		this.height = height;
		this.width = width;
		this.offsetX = offsetX;
		this.offsetY = offsetY;
		this.address = address;

		//this constructor is called only in nested grids:
		this.isNested = true;

		boxesCache = new HashMap();

		layoutGrid(wrappers);

		if (nature.isSplitSharedRowSpan())
		{
			splitSharedRowSpanIntoNestedGrids();
		}
	}


	/**
	 *
	 */
	private void createNestedGrid(
		int row1,
		int col1,
		int row2,
		int col2
		)
	{
		JRBasePrintFrame frame = new JRBasePrintFrame(null);
		List wrappers = new ArrayList();

		frame.setWidth(xCuts.getCut(col2) - xCuts.getCut(col1));
		frame.setHeight(yCuts.getCut(row2) - yCuts.getCut(row1));

		String virtualAddress = (address == null ? "" : address + "_") + getNextVirtualFrameIndex();

		JRExporterGridCell gridCell =
			new ElementGridCell(
				new ElementWrapper(frame, virtualAddress, null),
				frame.getWidth(),
				frame.getHeight(),
				col2 - col1,
				row2 - row1
				);

		gridCell.setLayout(
			new JRGridLayout(
				nature,
				(ElementWrapper[]) wrappers.toArray(new ElementWrapper[wrappers.size()]),
				frame.getWidth(),
				frame.getHeight(),
				offsetX -xCuts.getCut(col1),
				offsetY -yCuts.getCut(row1),
				virtualAddress
				)
			);

		OccupiedGridCell occupiedGridCell = new OccupiedGridCell(gridCell);
		for(int row = row1; row < row2; row++)
		{
			for(int col = col1; col < col2; col++)
			{
				JRExporterGridCell gCell = grid[row][col];
				grid[row][col] = occupiedGridCell;
				ElementWrapper wrapper = gCell.getWrapper();
				if (gCell.getType() == JRExporterGridCell.TYPE_ELEMENT_CELL)
				{
					wrappers.add(wrapper);
					frame.addElement(wrapper.getElement());//FIXMEODT do we need this?
				}
			}
		}

		grid[row1][col1] = gridCell;
	}


	/**
	 * Constructs the element grid.
	 */
	protected void layoutGrid(ElementWrapper[] wrappers)
	{

		boolean createXCuts = (xCuts == null);

		xCuts = createXCuts ? new CutsInfo() : xCuts;
		yCuts = nature.isIgnoreLastRow() ? new CutsInfo(0) : new CutsInfo(height);

		if(!isNested && nature.isIgnorePageMargins())
		{
			setMargins(wrappers);

			if(createXCuts)
			{
				List xCutsList = xCuts.getCuts();

				if(hasLeftMargin)
				{
					xCutsList.remove(Integer.valueOf(0));
				}
			}

			List yCutsList = yCuts.getCuts();

			if(hasTopMargin)
			{
				yCutsList.remove(Integer.valueOf(0));
			}
			if(hasBottomMargin)
			{
				yCutsList.remove(Integer.valueOf(height));
			}
		}

		createCuts(wrappers, offsetX, offsetY, createXCuts);

		// add a cut at the width if it's a nested grid, or if the right margin
		// is not to be removed and no element goes beyond the width
		if (createXCuts && (isNested
				|| (!(nature.isIgnorePageMargins() && hasRightMargin)
				&& !(xCuts.hasCuts() && xCuts.getLastCut() >= width))))
		{
			xCuts.addCut(width);
		}
		
		xCuts.use();
		yCuts.use();

		int colCount = Math.max(xCuts.size() - 1, 0);
		int rowCount = Math.max(yCuts.size() - 1, 0);

		grid = new JRExporterGridCell[rowCount][colCount];

		for(int row = 0; row < rowCount; row++)
		{
			for(int col = 0; col < colCount; col++)
			{
				grid[row][col] =
					new EmptyGridCell(
						xCuts.getCut(col + 1) - xCuts.getCut(col),
						yCuts.getCut(row + 1) - yCuts.getCut(row),
						1,
						1
						);
			}
		}

		setGridElements(wrappers,
				offsetX, offsetY,
				0, 0, rowCount, colCount);

		width = xCuts.getTotalLength();
		height = yCuts.getTotalLength();
	}

	protected void createCuts(ElementWrapper[] wrappers, int elementOffsetX, int elementOffsetY, boolean createXCuts)
	{
		for(int elementIndex = 0; elementIndex < wrappers.length; elementIndex++)
		{
			ElementWrapper wrapper = wrappers[elementIndex];
			JRPrintElement element = wrapper.getElement();

			if (nature.isToExport(element))
			{
				if (createXCuts)
				{
					xCuts.addXCuts(element, elementOffsetX);
				}

				yCuts.addYCuts(element, elementOffsetY);
				
				if (nature.isDeep() && element instanceof JRPrintFrame)
				{
					JRPrintFrame frame = (JRPrintFrame) element;
					createCuts(
						wrapper.getWrappers(),
						element.getX() + elementOffsetX + frame.getLineBox().getLeftPadding().intValue(),
						element.getY() + elementOffsetY + frame.getLineBox().getTopPadding().intValue(),
						createXCuts
						);
				}
			}
		}
	}

	protected void setMargins(ElementWrapper[] wrappers)
	{
		for(int elementIndex = 0; elementIndex < wrappers.length; elementIndex++)
		{
			ElementWrapper wrapper = wrappers[elementIndex];
			JRPrintElement element = wrapper.getElement();

			if (nature.isToExport(element))
			{
				if(hasLeftMargin && element.getX() <= 0)
				{
					hasLeftMargin = false;
				}

				if(hasRightMargin && element.getX() >= width - element.getWidth())
				{
					hasRightMargin = false;
				}

				if(hasTopMargin && element.getY() <= 0)
				{
					hasTopMargin = false;
				}

				if(hasBottomMargin && element.getY() >= height - element.getHeight())
				{
					hasBottomMargin = false;
				}

			}
		}
	}


	protected void setGridElements(ElementWrapper[] wrappers,
			int elementOffsetX, int elementOffsetY,
			int startRow, int startCol, int endRow, int endCol)
	{
		for(int elementIndex = wrappers.length - 1; elementIndex >= 0; elementIndex--)
		{
			ElementWrapper wrapper = wrappers[elementIndex];
			JRPrintElement element = wrapper.getElement();

			if (nature.isToExport(element))
			{
				int x = element.getX() + elementOffsetX;
				int y = element.getY() + elementOffsetY;

				int col1 = xCuts.indexOfCut(x);
				int row1 = yCuts.indexOfCut(y);
				int col2 = xCuts.indexOfCut(x + element.getWidth());
				int row2 = yCuts.indexOfCut(y + element.getHeight());

				if (!isOverlap(row1, col1, row2, col2))
				{
					if (nature.isDeep() && element instanceof JRPrintFrame)
					{
						JRPrintFrame frame = (JRPrintFrame) element;
						
						setGridElements(
							wrapper.getWrappers(),
							x + frame.getLineBox().getLeftPadding().intValue(),
							y + frame.getLineBox().getTopPadding().intValue(),
							row1, col1, row2, col2
							);
						
						setFrameCellsStyle(frame, row1, col1, row2, col2);
					}
					else
					{
						setGridElement(wrapper, row1, col1, row2, col2);
					}
				}
			}
		}

		if (nature.isHorizontallyMergeEmptyCells())
		{
			horizontallyMergeEmptyCells(startRow, startCol, endRow, endCol);
		}
	}

	protected void horizontallyMergeEmptyCells(int startRow, int startCol, int endRow, int endCol)
	{
		for (int row = startRow; row < endRow; ++row)
		{
			int startSpan = -1;
			int spanWidth = 0;
			int col = startCol;
			for (; col < endCol; ++col)
			{
				JRExporterGridCell cell = grid[row][col];
				if (cell.isEmpty())
				{
					if (startSpan == -1)
					{
						startSpan = col;
					}
					spanWidth += cell.getWidth();
				}
				else
				{
					if (startSpan != -1 && col - startSpan > 1)
					{
						JRExporterGridCell spanCell = grid[row][startSpan];
						spanCell.setColSpan(col - startSpan);
						spanCell.setWidth(spanWidth);
						//TODO set OCCUPIED_CELL?
					}
					startSpan = -1;
					spanWidth = 0;
				}
			}
			if (startSpan != -1 && col - startSpan > 1)
			{
				JRExporterGridCell spanCell = grid[row][startSpan];
				spanCell.setColSpan(col - startSpan);
				spanCell.setWidth(spanWidth);
			}
		}
	}

	protected boolean isOverlap(int row1, int col1, int row2, int col2)
	{
		boolean isOverlap = false;
		if (nature.isSpanCells())
		{
			is_overlap_out:
			for (int row = row1; row < row2; row++)
			{
				for (int col = col1; col < col2; col++)
				{
					if (!grid[row][col].isEmpty())
					{
						isOverlap = true;
						break is_overlap_out;
					}
				}
			}
		}
		else
		{
			isOverlap = grid[row1][col1].getWrapper() != null;
		}
		return isOverlap;
	}


	protected void setGridElement(ElementWrapper wrapper, int row1, int col1, int row2, int col2)
	{
		yCuts.addUsage(row1, CutsInfo.USAGE_NOT_EMPTY);
		xCuts.addUsage(col1, CutsInfo.USAGE_NOT_EMPTY);

		if (col2 - col1 != 0 && row2 - row1 != 0)
		{
			JRPrintElement element = wrapper.getElement();
			JRPrintFrame frame = element instanceof JRPrintFrame ? (JRPrintFrame) element : null;

			int rowSpan = nature.isSpanCells() ? row2 - row1 : 1;
			int colSpan = nature.isSpanCells() ? col2 - col1 : 1;
			JRExporterGridCell gridCell =
				new ElementGridCell(
					wrapper,
					element.getWidth(),
					element.getHeight(),
					colSpan,
					rowSpan
					);

			if (frame != null)//FIXMEODT if deep, does this make sense?
			{
				gridCell.setLayout(
					new JRGridLayout(
						nature,
						wrapper.getWrappers(),
						frame.getWidth(),
						frame.getHeight(),
						0, //offsetX
						0, //offsetY
						wrapper.getAddress()
						)
					);
			}

			gridCell.setBox((element instanceof JRBoxContainer)?((JRBoxContainer)element).getLineBox():null);

			if (nature.isBreakBeforeRow(element))
			{
				yCuts.addUsage(row1,  CutsInfo.USAGE_BREAK);
			}
			if (nature.isBreakAfterRow(element))
			{
				yCuts.addUsage(row1 + rowSpan,  CutsInfo.USAGE_BREAK);
			}

			if (nature.isSpanCells())
			{
				OccupiedGridCell occupiedGridCell = new OccupiedGridCell(gridCell);
				for (int row = row1; row < row2; row++)
				{
					for (int col = col1; col < col2; col++)
					{
						grid[row][col] = occupiedGridCell;
					}
					yCuts.addUsage(row, CutsInfo.USAGE_SPANNED);
				}

				for (int col = col1; col < col2; col++)
				{
					xCuts.addUsage(col, CutsInfo.USAGE_SPANNED);
				}
			}

			grid[row1][col1] = gridCell;
		}
	}


	protected void setFrameCellsStyle(JRPrintFrame frame, int row1, int col1, int row2, int col2)
	{
		Color backcolor = frame.getModeValue() == ModeEnum.OPAQUE ? frame.getBackcolor() : null;

		for (int row = row1; row < row2; row++)
		{
			for (int col = col1; col < col2; col++)
			{
				JRExporterGridCell cell = grid[row][col];

				if (cell.getBackcolor() == null)
				{
					if (frame.getModeValue() == ModeEnum.OPAQUE)
					{
						cell.setBackcolor(backcolor);
					}
				}

				if (cell.getForecolor() == null)
				{
					cell.setForecolor(frame.getForecolor());
				}

				boolean keepLeft = col == col1;
				boolean keepRight = col == col2 - cell.getColSpan();
				boolean keepTop = row == row1;
				boolean keepBottom = row == row2 - cell.getRowSpan();

				if (keepLeft || keepRight || keepTop || keepBottom)
				{
					JRLineBox cellBox = cell.getBox();
					Object key = new BoxKey(frame.getLineBox(), cellBox, keepLeft, keepRight, keepTop, keepBottom);
					JRLineBox modBox = (JRLineBox) boxesCache.get(key);
					if (modBox == null)
					{
						modBox = JRBoxUtil.clone(frame.getLineBox(), keepLeft, keepRight, keepTop, keepBottom, cellBox);
						boxesCache.put(key, modBox);
					}

					cell.setBox(modBox);
				}
			}
		}
	}

	private void splitSharedRowSpanIntoNestedGrids()
	{
		for(int row = 0; row < grid.length;)
		{
			int rowSpan = getSharedRowSpan(row);
			//negative row span means it is not shared row span
			if (rowSpan > 0)
			{
				splitSharedColSpanIntoNestedGrids(row, row + rowSpan);
			}
			row += Math.abs(rowSpan);
		}
	}

	private void splitSharedColSpanIntoNestedGrids(int row1, int row2)
	{
		for(int col = 0; col < grid[row1].length;)
		{
			int colSpan = getSharedColSpan(row1, row2, col);
			//negative col span means it is not shared col span
			if (colSpan > 0)
			{
				if (
					!(row1 == 0
					&& row2 == grid.length
					&& col == 0
					&& colSpan == grid[0].length)
					)
				{
					this.createNestedGrid(
						row1,
						col,
						row2,
						col + colSpan
						);
				}
			}
			col += Math.abs(colSpan);
		}
	}

	private int getSharedRowSpan(int row1)
	{
		int rowSpan = 1;
		int sharedSpanCount = 0;

		for(int row = 0; row < rowSpan; row++)
		{
			for(int col = 0; col < grid[0].length; col++)
			{
				JRExporterGridCell gridCell = grid[row1 + row][col];
				if (row + gridCell.getRowSpan() > rowSpan)
				{
					sharedSpanCount++;
					rowSpan = row + gridCell.getRowSpan();
				}
			}
		}

		// we have "shared row span" only if at least two merged cells share at least one row;
		// negative row span is used to skip row span that is not shared
		return sharedSpanCount > 1 ? rowSpan : -rowSpan;
	}

	private int getSharedColSpan(int row1, int row2, int col1)
	{
		int colSpan = 1;
		boolean isSharedSpan = false;

		for(int col = 0; col < colSpan; col++)
		{
			for(int row = row1; row < row2; row++)
			{
				JRExporterGridCell gridCell = grid[row][col1 + col];
				if (col + gridCell.getColSpan() > colSpan)
				{
					isSharedSpan = true;
					colSpan = col + gridCell.getColSpan();
				}
				else if (gridCell.getRowSpan() > 1)
				{
					isSharedSpan = true;
				}
			}
		}

		// we have "shared col span" only if at least two merged cells share at least one col;
		// negative col span is used to skip col span that is not shared
		return isSharedSpan ? colSpan : -colSpan;
	}

	/**
	 * Returns the constructed element grid.
	 *
	 * @return the constructed element grid
	 */
	public JRExporterGridCell[][] getGrid()
	{
		return grid;
	}


	/**
	 * Returns the list of cut points on the X axis for the grid.
	 *
	 * @return the list of cut points on the X axis for the grid
	 */
	public CutsInfo getXCuts()
	{
		return xCuts;
	}


	/**
	 * Returns the list of cut points on the Y axis for the grid.
	 *
	 * @return the list of cut points on the Y axis for the grid
	 */
	public CutsInfo getYCuts()
	{
		return yCuts;
	}


	/**
	 * Returns the width available for the grid.
	 *
	 * @return the width available for the grid
	 */
	public int getWidth()
	{
		return width;
	}


	public int getRowHeight(int row)
	{
		return yCuts.getCut(row + 1) - yCuts.getCut(row);
	}


	public static int getMaxRowHeight(JRExporterGridCell[] row)
	{
		int maxRowHeight = row[0].getHeight();
		for (int col = 0; col < row.length; col++)
		{
			JRExporterGridCell cell = row[col];

			if (cell.getType() != JRExporterGridCell.TYPE_OCCUPIED_CELL)
			{
				if (maxRowHeight < cell.getHeight())
				{
					maxRowHeight = cell.getHeight();
				}
			}
		}
		return maxRowHeight;
	}


	public static int getRowHeight(JRExporterGridCell[] row)//FIXMEODT are we still using this?
	{
		if (row[0].getRowSpan() == 1 && row[0].getType() != JRExporterGridCell.TYPE_OCCUPIED_CELL) //quick exit
		{
			return row[0].getHeight();
		}

		int rowHeight = 0;
		int minSpanIdx = 0;

		int colCount = row.length;

		int col;
		for (col = 0; col < colCount; col++)
		{
			JRExporterGridCell cell = row[col];

			if (cell.getType() != JRExporterGridCell.TYPE_OCCUPIED_CELL)
			{
				if (cell.getRowSpan() == 1)
				{
					rowHeight = cell.getHeight();
					break;
				}

				if (cell.getRowSpan() < row[minSpanIdx].getRowSpan())
				{
					minSpanIdx = col;
				}
			}
		}

		if (col >= colCount) //no cell with rowSpan = 1 was found, getting the height of the cell with min rowSpan
		{
			rowHeight = row[minSpanIdx].getHeight();
		}

		return rowHeight;
	}


	/**
	 * This static method calculates all the X cuts for a list of pages.
	 *
	 * @param pages
	 *            The list of pages.
	 * @param startPageIndex
	 *            The first page to consider.
	 * @param endPageIndex
	 *            The last page to consider.
	 * @param width
	 *            The page width
	 * @param offsetX
	 *            horizontal element position offset
	 */
	public static CutsInfo calculateXCuts(ExporterNature nature, List pages, int startPageIndex, int endPageIndex, int width, int offsetX)
	{
		CutsInfo xCuts = new CutsInfo();

		for (int pageIndex = startPageIndex; pageIndex <= endPageIndex; pageIndex++)
		{
			JRPrintPage page = (JRPrintPage) pages.get(pageIndex);
			addXCuts(nature, page.getElements(), offsetX, xCuts);
		}
		
		// add a cut at the page width if no element goes beyond the width
		int lastCut = xCuts.getLastCut();
		if (lastCut < width)
		{
			xCuts.addCut(width);
		}

		return xCuts;
	}

	/**
	 * This static method calculates the X cuts for a list of print elements and
	 * stores them in the list indicated by the xCuts parameter.
	 *
	 * @param elementsList
	 *            The list of elements to be used to determine the X cuts.
	 * @param elementOffsetX
	 *            horizontal element position offset
	 * @param xCuts
	 *            The list to which the X cuts are to be added.
	 */
	protected static void addXCuts(ExporterNature nature, List elementsList, int elementOffsetX, CutsInfo xCuts)
	{
		for (Iterator it = elementsList.iterator(); it.hasNext();)
		{
			JRPrintElement element = ((JRPrintElement) it.next());

			if (nature.isToExport(element))
			{
				xCuts.addXCuts(element, elementOffsetX);
				
				if (element instanceof JRPrintFrame)
				{
					JRPrintFrame frame = (JRPrintFrame) element;
					addXCuts(
						nature,
						frame.getElements(),
						element.getX() + elementOffsetX + frame.getLineBox().getLeftPadding().intValue(),
						xCuts
						);
				}
			}
		}
	}

	/**
	 *
	 */
	protected int getNextVirtualFrameIndex()
	{
		return virtualFrameIndex++;
	}


	/**
	 *
	 */
	private static ElementWrapper[] createWrappers(List elementsList, String parentAddress)
	{
		ElementWrapper[] wrappers = new ElementWrapper[elementsList.size()];

		for (int elementIndex = 0; elementIndex < elementsList.size(); elementIndex++)
		{
			JRPrintElement element = ((JRPrintElement) elementsList.get(elementIndex));

			String address = (parentAddress == null ? "" : parentAddress + "_") + elementIndex;

			wrappers[elementIndex] =
				new ElementWrapper(
					element,
					address,
					element instanceof JRPrintFrame
						? createWrappers(((JRPrintFrame)element).getElements(), address)
						: null
					);
		}

		return wrappers;
	}

	/**
	 *
	 */
	protected static class BoxKey
	{
		final JRLineBox box;
		final JRLineBox cellBox;
		final boolean left;
		final boolean right;
		final boolean top;
		final boolean bottom;
		final int hashCode;

		BoxKey(JRLineBox box, JRLineBox cellBox, boolean left, boolean right, boolean top, boolean bottom)
		{
			this.box = box;
			this.cellBox = cellBox;
			this.left = left;
			this.right = right;
			this.top = top;
			this.bottom = bottom;

			int hash = box.hashCode();
			if (cellBox != null)
			{
				hash = 31*hash + cellBox.hashCode();
			}
			hash = 31*hash + (left ? 1231 : 1237);
			hash = 31*hash + (right ? 1231 : 1237);
			hash = 31*hash + (top ? 1231 : 1237);
			hash = 31*hash + (bottom ? 1231 : 1237);
			hashCode = hash;
		}

		public boolean equals(Object obj)
		{
			if (obj == this)
			{
				return true;
			}

			BoxKey b = (BoxKey) obj;

			return b.box.equals(box) &&
				(b.cellBox == null ? cellBox == null : (cellBox != null && b.cellBox.equals(cellBox))) &&
				b.left == left && b.right == right && b.top == top && b.bottom == bottom;
		}

		public int hashCode()
		{
			return hashCode;
		}
	}


}