/*******************************************************************************
 * Copyright 2005, CHISEL Group, University of Victoria, Victoria, BC, Canada.
 * All rights reserved. This program and the accompanying materials are made
 * available under the terms of the Eclipse Public License v1.0 which
 * accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 * 
 * Contributors: The Chisel Group, University of Victoria
 *******************************************************************************/
package org.eclipse.zest.layouts.algorithms;

import java.util.Arrays;

import org.eclipse.zest.layouts.LayoutStyles;
import org.eclipse.zest.layouts.dataStructures.InternalNode;
import org.eclipse.zest.layouts.dataStructures.InternalRelationship;

/**
 * @version 2.0
 * @author Ian Bull
 * @author Casey Best and Rob Lintern
 */
public class GridLayoutAlgorithm extends AbstractLayoutAlgorithm {

	private static final double PADDING_PERCENTAGE = 0.95;

	protected int rowPadding = 0;

	public void setLayoutArea(double x, double y, double width, double height) {
		throw new RuntimeException("Operation not implemented");
	}

	int rows, cols, numChildren;
	double colWidth, rowHeight, offsetX, offsetY;
	int totalProgress;
	double h, w;

	/**
	 * Initializes the grid layout.
	 * @param styles
	 * @see LayoutStyles
	 */
	public GridLayoutAlgorithm(int styles) {
		super(styles);
	}

	/**
	 * Inititalizes the grid layout with no style.
	 */
	public GridLayoutAlgorithm() {
		this(LayoutStyles.NONE);
	}

	protected int getCurrentLayoutStep() {
		// TODO: This isn't right
		return 0;
	}

	protected int getTotalNumberOfLayoutSteps() {
		return totalProgress;
	}

	/**
	 * 
	 */
	protected void preLayoutAlgorithm(InternalNode[] entitiesToLayout, InternalRelationship[] relationshipsToConsider, double x, double y, double width, double height) {

		// TODO: Filter unwanted entities and relationships
		//super.applyLayout (entitiesToLayout, relationshipsToConsider, boundsX, boundsY, boundsWidth, boundsHeight);
		// now begin
		numChildren = entitiesToLayout.length;
		if (numChildren < 1)
			return;

		int[] colsAndRows = calculateNumberOfRowsAndCols(numChildren, x, y, width, height);
		cols = colsAndRows[0];
		rows = colsAndRows[1];

		totalProgress = rows + 2;
		fireProgressEvent(1, totalProgress);

		// sort the entities
		if (comparator != null) {
			Arrays.sort(entitiesToLayout, comparator);
		} else {
			Arrays.sort(entitiesToLayout);
		}
		fireProgressEvent(2, totalProgress);

		// Calculate row height and column width
		colWidth = width / cols;
		rowHeight = height / rows;

		// Calculate amount to scale children
		double[] nodeSize = calculateNodeSize(colWidth, rowHeight);
		w = nodeSize[0];
		h = nodeSize[1];
		offsetX = (colWidth - w) / 2.0; // half of the space between columns
		offsetY = (rowHeight - h) / 2.0; // half of the space between rows
	}

	/**
	 * Use this algorithm to layout the given entities, using the given relationships and bounds.
	 * The entities will be placed in the same order as they are passed in, unless a comparator
	 * is supplied.  
	 * 
	 * @param entitiesToLayout Apply the algorithm to these entities
	 * @param relationshipsToConsider Only consider these relationships when applying the algorithm.
	 * @param boundsX The left side of the bounds in which the layout can place the entities.
	 * @param boundsY The top side of the bounds in which the layout can place the entities.
	 * @param boundsWidth The width of the bounds in which the layout can place the entities.
	 * @param boundsHeight The height of the bounds in which the layout can place the entities.
	 * @throws RuntimeException Thrown if entitiesToLayout doesn't contain all of the endpoints for each relationship in relationshipsToConsider
	 */
	protected synchronized void applyLayoutInternal(InternalNode[] entitiesToLayout, InternalRelationship[] relationshipsToConsider, double boundsX, double boundsY, double boundsWidth, double boundsHeight) {

		int index = 0;
		for (int i = 0; i < rows; i++) {
			for (int j = 0; j < cols; j++) {
				if ((i * cols + j) < numChildren) {
					// find new position for child
					double xmove = boundsX + j * colWidth + offsetX;
					double ymove = boundsY + i * rowHeight + offsetY;
					InternalNode sn = entitiesToLayout[index++];
					sn.setInternalLocation(xmove, ymove);
					sn.setInternalSize(Math.max(w, MIN_ENTITY_SIZE), Math.max(h, MIN_ENTITY_SIZE));
				}
			}
			fireProgressEvent(2 + i, totalProgress);
		}
		updateLayoutLocations(entitiesToLayout);
		fireProgressEvent(totalProgress, totalProgress);
	}

	protected void postLayoutAlgorithm(InternalNode[] entitiesToLayout, InternalRelationship[] relationshipsToConsider) {

	}

	/**
	 * Calculates and returns an array containing the number of columns, followed by the number of rows
	 */
	protected int[] calculateNumberOfRowsAndCols(int numChildren, double boundX, double boundY, double boundWidth, double boundHeight) {
		if (getEntityAspectRatio() == 1.0) {
			return calculateNumberOfRowsAndCols_square(numChildren, boundX, boundY, boundWidth, boundHeight);
		} else {
			return calculateNumberOfRowsAndCols_rectangular(numChildren);
		}
	}

	protected int[] calculateNumberOfRowsAndCols_square(int numChildren, double boundX, double boundY, double boundWidth, double boundHeight) {
		int rows = Math.max(1, (int) Math.sqrt(numChildren * boundHeight / boundWidth));
		int cols = Math.max(1, (int) Math.sqrt(numChildren * boundWidth / boundHeight));

		// if space is taller than wide, adjust rows first
		if (boundWidth <= boundHeight) {
			//decrease number of rows and columns until just enough or not enough
			while (rows * cols > numChildren) {
				if (rows > 1)
					rows--;
				if (rows * cols > numChildren)
					if (cols > 1)
						cols--;
			}
			//increase number of rows and columns until just enough
			while (rows * cols < numChildren) {
				rows++;
				if (rows * cols < numChildren)
					cols++;
			}
		} else {
			//decrease number of rows and columns until just enough or not enough
			while (rows * cols > numChildren) {
				if (cols > 1)
					cols--;
				if (rows * cols > numChildren)
					if (rows > 1)
						rows--;
			}
			//increase number of rows and columns until just enough
			while (rows * cols < numChildren) {
				cols++;
				if (rows * cols < numChildren)
					rows++;
			}
		}
		int[] result = { cols, rows };
		return result;
	}

	protected int[] calculateNumberOfRowsAndCols_rectangular(int numChildren) {
		int rows = Math.max(1, (int) Math.ceil(Math.sqrt(numChildren)));
		int cols = Math.max(1, (int) Math.ceil(Math.sqrt(numChildren)));
		int[] result = { cols, rows };
		return result;
	}

	protected double[] calculateNodeSize(double colWidth, double rowHeight) {
		double childW = Math.max(MIN_ENTITY_SIZE, PADDING_PERCENTAGE * colWidth);
		double childH = Math.max(MIN_ENTITY_SIZE, PADDING_PERCENTAGE * (rowHeight - rowPadding));
		double whRatio = colWidth / rowHeight;
		if (whRatio < getEntityAspectRatio()) {
			childH = childW / getEntityAspectRatio();
		} else {
			childW = childH * getEntityAspectRatio();
		}
		double[] result = { childW, childH };
		return result;
	}

	/**
	 * Increases the padding between rows in the grid
	 * @param rowPadding Value will not be set if less than 0.
	 */
	public void setRowPadding(int rowPadding) {
		if (rowPadding < 0) {
			return;
		}
		this.rowPadding = rowPadding;
	}

	protected boolean isValidConfiguration(boolean asynchronous, boolean continueous) {
		if (asynchronous && continueous)
			return false;
		else if (asynchronous && !continueous)
			return true;
		else if (!asynchronous && continueous)
			return false;
		else if (!asynchronous && !continueous)
			return true;

		return false;
	}

}
