/*******************************************************************************
 * 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.Iterator;
import java.util.List;

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

/**
 * This layout will take the given entities, apply a tree layout to them, and then display the 
 * tree in a circular fashion with the roots in the center.
 * 
 * @author Casey Best
 * @auhtor Rob Lintern
 */
public class RadialLayoutAlgorithm extends TreeLayoutAlgorithm {
	private static final double MAX_DEGREES = Math.PI * 2;
	private double startDegree;
	private double endDegree;
	private TreeLayoutAlgorithm treeLayout;
	private List roots;

	/**
	 * Creates a radial layout with no style.
	 */
	public RadialLayoutAlgorithm() {
		this(LayoutStyles.NONE);
	}

	//TODO: This is a really strange pattern.  It extends tree layout and it contains a tree layout ? 
	public RadialLayoutAlgorithm(int styles) {
		super(styles);
		treeLayout = new TreeLayoutAlgorithm(styles);
		startDegree = 0;
		endDegree = MAX_DEGREES;
	}

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

	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;
	}

	DisplayIndependentRectangle layoutBounds = null;

	protected void preLayoutAlgorithm(InternalNode[] entitiesToLayout, InternalRelationship[] relationshipsToConsider, double x, double y, double width, double height) {
		// TODO Auto-generated method stub
		layoutBounds = new DisplayIndependentRectangle(x, y, width, height);
		super.preLayoutAlgorithm(entitiesToLayout, relationshipsToConsider, x, y, width, height);
	}

	protected void postLayoutAlgorithm(InternalNode[] entitiesToLayout, InternalRelationship[] relationshipsToConsider) {
		roots = treeLayout.getRoots();
		computeRadialPositions(entitiesToLayout, layoutBounds);

		defaultFitWithinBounds(entitiesToLayout, layoutBounds);

		super.postLayoutAlgorithm(entitiesToLayout, relationshipsToConsider);

	}

	/**
	 * Set the range the radial layout will use when applyLayout is called.
	 * Both values must be in radians.
	 */
	public void setRangeToLayout(double startDegree, double endDegree) {
		this.startDegree = startDegree;
		this.endDegree = endDegree;
	}

	/**
	 * Take the tree and make it round.  This is done by determining the location of each entity in terms
	 * of its percentage in the tree layout.  Then apply that percentage to the radius and distance from
	 * the center.
	 */
	protected void computeRadialPositions(InternalNode[] entities, DisplayIndependentRectangle bounds2) { //TODO TODO TODO
		DisplayIndependentRectangle bounds = new DisplayIndependentRectangle(getLayoutBounds(entities, true));
		bounds.height = bounds2.height;
		bounds.y = bounds2.y;
		for (int i = 0; i < entities.length; i++) {
			InternalNode entity = entities[i];
			double percentTheta = (entity.getInternalX() - bounds.x) / bounds.width;
			double distance = (entity.getInternalY() - bounds.y) / bounds.height;
			double theta = startDegree + Math.abs(endDegree - startDegree) * percentTheta;
			double newX = distance * Math.cos(theta);
			double newY = distance * Math.sin(theta);

			entity.setInternalLocation(newX, newY);
		}
	}

	/**
	 * Find the bounds in which the nodes are located.  Using the bounds against the real bounds
	 * of the screen, the nodes can proportionally be placed within the real bounds.
	 * The bounds can be determined either including the size of the nodes or not.  If the size
	 * is not included, the bounds will only be guaranteed to include the center of each node.
	 */
	protected DisplayIndependentRectangle getLayoutBounds(InternalNode[] entitiesToLayout, boolean includeNodeSize) {
		DisplayIndependentRectangle layoutBounds = super.getLayoutBounds(entitiesToLayout, includeNodeSize);
		DisplayIndependentPoint centerPoint = (roots != null) ? determineCenterPoint(roots) : new DisplayIndependentPoint(layoutBounds.x + layoutBounds.width / 2, layoutBounds.y + layoutBounds.height / 2);
		//	The center entity is determined in applyLayout
		double maxDistanceX = Math.max(Math.abs(layoutBounds.x + layoutBounds.width - centerPoint.x), Math.abs(centerPoint.x - layoutBounds.x));
		double maxDistanceY = Math.max(Math.abs(layoutBounds.y + layoutBounds.height - centerPoint.y), Math.abs(centerPoint.y - layoutBounds.y));
		layoutBounds = new DisplayIndependentRectangle(centerPoint.x - maxDistanceX, centerPoint.y - maxDistanceY, maxDistanceX * 2, maxDistanceY * 2);
		return layoutBounds;
	}

	/**
	 * Find the center point between the roots
	 */
	private DisplayIndependentPoint determineCenterPoint(List roots) {
		double totalX = 0, totalY = 0;
		for (Iterator iterator = roots.iterator(); iterator.hasNext();) {
			InternalNode entity = (InternalNode) iterator.next();
			totalX += entity.getInternalX();
			totalY += entity.getInternalY();
		}
		return new DisplayIndependentPoint(totalX / roots.size(), totalY / roots.size());
	}
}
