/*******************************************************************************
 * 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.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

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

/**
 * The TreeLayoutAlgorithm class implements a simple algorithm to
 * arrange graph nodes in a layered vertical tree-like layout. 
 * 
 * This is by no means an efficiently coded algorithm.
 *
 * @version  2.0
 * @author   Casey Best and Rob Lintern (version 1.0 by Jingwei Wu)
 */
public class TreeLayoutAlgorithm extends AbstractLayoutAlgorithm {

	private final static double DEFAULT_WEIGHT = 0;
	private final static boolean DEFAULT_MARKED = false;

	private final static boolean AS_DESTINATION = false;
	private final static boolean AS_SOURCE = true;

	private final static int NUM_DESCENDENTS_INDEX = 0;
	private final static int NUM_LEVELS_INDEX = 1;

	private ArrayList treeRoots;

	private double boundsX;
	private double boundsY;
	private double boundsWidth;
	private double boundsHeight;
	private DisplayIndependentRectangle layoutBounds = null;

	private List[] parentLists;
	private List[] childrenLists;
	private double[] weights;
	private boolean[] markedArr;

	/////////////////////////////////////////////////////////////////////////
	/////                        Constructors                           /////
	/////////////////////////////////////////////////////////////////////////

	/**
	 * Constructs a new TreeLayoutAlgorithm object.
	 */
	public TreeLayoutAlgorithm(int styles) {
		super(styles);
	}

	/**
	 * Tree layout algorithm Constructor with NO Style
	 *
	 */
	public TreeLayoutAlgorithm() {
		this(LayoutStyles.NONE);
	}

	/////////////////////////////////////////////////////////////////////////
	/////                        Public Methods                         /////
	/////////////////////////////////////////////////////////////////////////

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

	protected int getCurrentLayoutStep() {
		// TODO Auto-generated method stub
		return 0;
	}

	protected int getTotalNumberOfLayoutSteps() {
		return 4;
	}

	/**
	 * Executes this TreeLayoutAlgorithm layout algorithm by referencing the
	 * data stored in the repository system. Once done, the result
	 * will be saved to the data repository.
	 * 
	 * @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 void preLayoutAlgorithm(InternalNode[] entitiesToLayout, InternalRelationship[] relationshipsToConsider, double x, double y, double width, double height) {
		// Filter unwanted entities and relationships
		//super.applyLayout (entitiesToLayout, relationshipsToConsider, boundsX, boundsY, boundsWidth, boundsHeight);

		parentLists = new List[entitiesToLayout.length];
		childrenLists = new List[entitiesToLayout.length];
		weights = new double[entitiesToLayout.length];
		markedArr = new boolean[entitiesToLayout.length];
		for (int i = 0; i < entitiesToLayout.length; i++) {
			parentLists[i] = new ArrayList();
			childrenLists[i] = new ArrayList();
			weights[i] = DEFAULT_WEIGHT;
			markedArr[i] = DEFAULT_MARKED;
		}

		this.boundsHeight = height;
		this.boundsWidth = width;
		this.boundsX = x;
		this.boundsY = y;
		layoutBounds = new DisplayIndependentRectangle(boundsX, boundsY, boundsWidth, boundsHeight);

	}

	protected void applyLayoutInternal(InternalNode[] entitiesToLayout, InternalRelationship[] relationshipsToConsider, double boundsX, double boundsY, double boundsWidth, double boundsHeight) {

		if (entitiesToLayout.length > 0) {
			int totalProgress = 4;
			fireProgressEvent(1, totalProgress);

			//List roots = new ArrayList();
			treeRoots = new ArrayList();
			buildForest(treeRoots, entitiesToLayout, relationshipsToConsider);
			fireProgressEvent(2, totalProgress);
			computePositions(treeRoots, entitiesToLayout);
			fireProgressEvent(3, totalProgress);
			defaultFitWithinBounds(entitiesToLayout, layoutBounds);

		}
	}

	protected void postLayoutAlgorithm(InternalNode[] entitiesToLayout, InternalRelationship[] relationshipsToConsider) {
		updateLayoutLocations(entitiesToLayout);
		fireProgressEvent(4, 4);
	}

	/**
	 * Returns the last found roots
	 */
	public List getRoots() {
		return treeRoots;
	}

	/**
	 * Finds all the relationships in which the node <code>obj<code>
	 * plays the specified <code>role</code>.
	 * @param entity The node that concerns the relations to be found.
	 * @param role The role played by the <code>obj</code>. Its type
	 * must be of <code>ACTOR_ROLE</code> or <code>ACTEE_ROLE</code>.
	 * @see SimpleRelationship
	 */
	private Collection findRelationships(Object entity, boolean objectAsSource, InternalRelationship[] relationshipsToConsider) {
		Collection foundRels = new ArrayList();
		for (int i = 0; i < relationshipsToConsider.length; i++) {
			InternalRelationship rel = relationshipsToConsider[i];
			if (objectAsSource && rel.getSource().equals(entity)) {
				foundRels.add(rel);
			} else if (!objectAsSource && rel.getDestination().equals(entity)) {
				foundRels.add(rel);
			}
		}
		return foundRels;
	}

	/**
	 * Finds the relation that has the lowest index in the relation
	 * repository in which the node <code>obj<code> plays the specified
	 * <code>role</code>.
	 * @param obj The node that concerns the relations to be found.
	 * @param role The role played by the <code>obj</code>. Its type must
	 * be of <code>ACTOR_ROLE</code> or <code>ACTEE_ROLE</code>.
	 * @see SimpleRelationship
	 * @see SimpleRelationship#ACTOR_ROLE
	 * @see SimpleRelationship#ACTEE_ROLE
	 */
	private InternalRelationship findRelationship(Object entity, boolean objectAsSource, InternalRelationship[] relationshipsToConsider) {
		InternalRelationship relationship = null;
		for (int i = 0; i < relationshipsToConsider.length && relationship == null; i++) {
			InternalRelationship possibleRel = relationshipsToConsider[i];
			if (objectAsSource && possibleRel.getSource().equals(entity)) {
				relationship = possibleRel;
			} else if (!objectAsSource && possibleRel.getDestination().equals(entity)) {
				relationship = possibleRel;
			}
		}
		return relationship;
	}

	/////////////////////////////////////////////////////////////////////////
	/////                        Private Methods                        /////
	/////////////////////////////////////////////////////////////////////////

	/**
	 * Builds the tree forest that is used to calculate positions
	 * for each node in this TreeLayoutAlgorithm.
	 */
	private void buildForest(List roots, InternalNode[] entities, InternalRelationship[] relationships) {
		List unplacedEntities = new ArrayList(Arrays.asList(entities));
		buildForestRecursively(roots, unplacedEntities, entities, relationships);
	}

	/**
	 * Builds the forest recursively. All entities
	 * will be placed somewhere in the forest. 
	 */
	private void buildForestRecursively(List roots, List unplacedEntities, InternalNode[] entities, InternalRelationship[] relationships) {
		if (unplacedEntities.size() == 0) {
			return; // no more entities to place
		}

		// get the first entity in the list of unplaced entities, find its root, and build this root's tree
		InternalNode layoutEntity = (InternalNode) unplacedEntities.get(0);
		InternalNode rootEntity = findRootObjectRecursive(layoutEntity, new HashSet(), relationships);
		int rootEntityIndex = indexOfInternalNode(entities, rootEntity);
		buildTreeRecursively(rootEntity, rootEntityIndex, 0, entities, relationships);
		roots.add(rootEntity);

		// now see which nodes are left to be placed in a tree somewhere
		List unmarkedCopy = new ArrayList(unplacedEntities);
		for (Iterator iter = unmarkedCopy.iterator(); iter.hasNext();) {
			InternalNode tmpEntity = (InternalNode) iter.next();
			int tmpEntityIndex = indexOfInternalNode(entities, tmpEntity);
			boolean isMarked = markedArr[tmpEntityIndex];
			if (isMarked) {
				unplacedEntities.remove(tmpEntity);
			}
		}
		buildForestRecursively(roots, unplacedEntities, entities, relationships);
	}

	/**
	 * Finds the root node that can be treated as the root of a tree.
	 * The found root node should be one of the unmarked nodes.
	 */
	private InternalNode findRootObjectRecursive(InternalNode currentEntity, Set seenAlready, InternalRelationship[] relationshipsToConsider) {
		InternalNode rootEntity = null;
		InternalRelationship rel = findRelationship(currentEntity, AS_DESTINATION, relationshipsToConsider);
		if (rel == null) {
			rootEntity = currentEntity;
		} else {
			InternalNode parentEntity = rel.getSource();
			if (!seenAlready.contains(parentEntity)) {
				seenAlready.add(parentEntity);
				rootEntity = findRootObjectRecursive(parentEntity, seenAlready, relationshipsToConsider);
			} else {
				rootEntity = currentEntity;
			}
		}
		return rootEntity;
	}

	/**
	 * Builds a tree of the passed in entity.
	 * The entity will pass a weight value to all of its children recursively.
	 */
	private void buildTreeRecursively(InternalNode layoutEntity, int i, double weight, InternalNode[] entities, final InternalRelationship[] relationships) {
		// No need to do further computation!
		if (layoutEntity == null) {
			return;
		}

		// A marked entity means that it has been added to the
		// forest, and its weight value needs to be modified.		
		if (markedArr[i]) {
			modifyWeightRecursively(layoutEntity, i, weight, new HashSet(), entities, relationships);
			return; //No need to do further computation.
		}

		// Mark this entity, set its weight value and create a new tree node.
		markedArr[i] = true;
		weights[i] = weight;

		// collect the children of this entity and put them in order
		Collection rels = findRelationships(layoutEntity, AS_SOURCE, relationships);
		List children = new ArrayList();
		for (Iterator iter = rels.iterator(); iter.hasNext();) {
			InternalRelationship layoutRel = (InternalRelationship) iter.next();
			InternalNode childEntity = layoutRel.getDestination();
			children.add(childEntity);
		}

		if (comparator != null) {
			Collections.sort(children, comparator);
		} else {
			// sort the children by level, then by number of descendents, then by number of children
			// TODO: SLOW
			Collections.sort(children, new Comparator() {
				public int compare(Object o1, Object o2) {
					InternalNode node1 = (InternalNode) o1;
					InternalNode node2 = (InternalNode) o2;
					int[] numDescendentsAndLevel1 = new int[2];
					int[] numDescendentsAndLevel2 = new int[2];
					int level1 = numDescendentsAndLevel1[NUM_LEVELS_INDEX];
					int level2 = numDescendentsAndLevel2[NUM_LEVELS_INDEX];
					if (level1 == level2) {
						getNumDescendentsAndLevel(node1, relationships, numDescendentsAndLevel1);
						getNumDescendentsAndLevel(node2, relationships, numDescendentsAndLevel2);
						int numDescendents1 = numDescendentsAndLevel1[NUM_DESCENDENTS_INDEX];
						int numDescendents2 = numDescendentsAndLevel2[NUM_DESCENDENTS_INDEX];
						if (numDescendents1 == numDescendents2) {
							int numChildren1 = getNumChildren(node1, relationships);
							int numChildren2 = getNumChildren(node1, relationships);
							return numChildren2 - numChildren1;
						} else {
							return numDescendents2 - numDescendents1;
						}
					} else {
						return level2 - level1;
					}
					//return getNumChildren(node2, relationships) - getNumChildren(node1, relationships);
				}
			});
		}

		// map children to this parent, and vice versa
		for (Iterator iter = children.iterator(); iter.hasNext();) {
			InternalNode childEntity = (InternalNode) iter.next();
			int childEntityIndex = indexOfInternalNode(entities, childEntity);
			if (!childrenLists[i].contains(childEntity)) {
				childrenLists[i].add(childEntity);
			}
			if (!parentLists[childEntityIndex].contains(layoutEntity)) {
				parentLists[childEntityIndex].add(layoutEntity);
			}
		}

		for (Iterator iter = children.iterator(); iter.hasNext();) {
			InternalNode childEntity = (InternalNode) iter.next();
			int childEntityIndex = indexOfInternalNode(entities, childEntity);
			buildTreeRecursively(childEntity, childEntityIndex, weight + 1, entities, relationships);
		}
	}

	private int getNumChildren(InternalNode layoutEntity, InternalRelationship[] relationships) {
		return findRelationships(layoutEntity, AS_SOURCE, relationships).size();
	}

	private void getNumDescendentsAndLevel(InternalNode layoutEntity, InternalRelationship[] relationships, int[] numDescendentsAndLevel) {
		getNumDescendentsAndLevelRecursive(layoutEntity, relationships, new HashSet(), numDescendentsAndLevel, 0);
	}

	private void getNumDescendentsAndLevelRecursive(InternalNode layoutEntity, InternalRelationship[] relationships, Set seenAlready, int[] numDescendentsAndLevel, int currentLevel) {
		if (seenAlready.contains(layoutEntity)) {
			return;
		}
		seenAlready.add(layoutEntity);
		numDescendentsAndLevel[NUM_LEVELS_INDEX] = Math.max(numDescendentsAndLevel[NUM_LEVELS_INDEX], currentLevel);
		Collection rels = findRelationships(layoutEntity, AS_SOURCE, relationships);
		for (Iterator iter = rels.iterator(); iter.hasNext();) {
			InternalRelationship layoutRel = (InternalRelationship) iter.next();
			InternalNode childEntity = layoutRel.getDestination();
			numDescendentsAndLevel[NUM_DESCENDENTS_INDEX]++;
			getNumDescendentsAndLevelRecursive(childEntity, relationships, seenAlready, numDescendentsAndLevel, currentLevel + 1);

		}
	}

	/**
	 * Modifies the weight value of the marked node recursively.
	 */
	private void modifyWeightRecursively(InternalNode layoutEntity, int i, double weight, Set descendentsSeenSoFar, InternalNode[] entities, InternalRelationship[] relationships) {
		// No need to do further computation!
		if (layoutEntity == null) {
			return;
		}

		if (descendentsSeenSoFar.contains(layoutEntity)) {
			return; //No need to do further computation.
		}

		descendentsSeenSoFar.add(layoutEntity);
		// No need to do further computation!
		if (weight < weights[i]) {
			return;
		}

		weights[i] = weight;
		Collection rels = findRelationships(layoutEntity, AS_SOURCE, relationships);

		for (Iterator iter = rels.iterator(); iter.hasNext();) {
			InternalRelationship tmpRel = (InternalRelationship) iter.next();
			InternalNode tmpEntity = tmpRel.getDestination();
			int tmpEntityIndex = indexOfInternalNode(entities, tmpEntity);
			modifyWeightRecursively(tmpEntity, tmpEntityIndex, weight + 1, descendentsSeenSoFar, entities, relationships);
		}
	}

	/**
	 * Gets the maxium weight of a tree in the forest of this TreeLayoutAlgorithm.
	 */
	private double getMaxiumWeightRecursive(InternalNode layoutEntity, int i, Set seenAlready, InternalNode[] entities) {
		double result = 0;
		if (seenAlready.contains(layoutEntity)) {
			return result;
		}
		seenAlready.add(layoutEntity);
		List children = childrenLists[i];
		if (children.isEmpty()) {
			result = weights[i];
		} else {
			//TODO: SLOW
			for (Iterator iter = children.iterator(); iter.hasNext();) {
				InternalNode childEntity = (InternalNode) iter.next();
				int childEntityIndex = indexOfInternalNode(entities, childEntity);
				result = Math.max(result, getMaxiumWeightRecursive(childEntity, childEntityIndex, seenAlready, entities));
			}
		}
		return result;
	}

	/**
	 * Computes positions for each node in this TreeLayoutAlgorithm by
	 * referencing the forest that holds those nodes.
	 */
	private void computePositions(List roots, InternalNode[] entities) {
		// No need to do further computation!
		if (roots.size() == 0) {
			return;
		}

		int totalLeafCount = 0;
		double maxWeight = 0;
		for (int i = 0; i < roots.size(); i++) {
			InternalNode rootEntity = (InternalNode) roots.get(i);
			int rootEntityIndex = indexOfInternalNode(entities, rootEntity);
			totalLeafCount = totalLeafCount + getNumberOfLeaves(rootEntity, rootEntityIndex, entities);
			maxWeight = Math.max(maxWeight, getMaxiumWeightRecursive(rootEntity, rootEntityIndex, new HashSet(), entities) + 1.0);
		}

		double width = 1.0 / totalLeafCount;
		double height = 1.0 / maxWeight;

		int leafCountSoFar = 0;

		//TODO: SLOW!
		for (int i = 0; i < roots.size(); i++) {
			InternalNode rootEntity = (InternalNode) roots.get(i);
			int rootEntityIndex = indexOfInternalNode(entities, rootEntity);
			computePositionRecursively(rootEntity, rootEntityIndex, leafCountSoFar, width, height, new HashSet(), entities);
			leafCountSoFar = leafCountSoFar + getNumberOfLeaves(rootEntity, rootEntityIndex, entities);
		}
	}

	/**
	 * Computes positions recursively until the leaf nodes are reached.
	 */
	private void computePositionRecursively(InternalNode layoutEntity, int i, int relativePosition, double width, double height, Set seenAlready, InternalNode[] entities) {
		if (seenAlready.contains(layoutEntity)) {
			return;
		}
		seenAlready.add(layoutEntity);
		double level = getLevel(layoutEntity, i, entities);
		int breadth = getNumberOfLeaves(layoutEntity, i, entities);
		double absHPosition = relativePosition + breadth / 2.0;
		double absVPosition = (level + 0.5);

		double posx = absHPosition * width;
		double posy = absVPosition * height;
		double weight = weights[i];
		posy = posy + height * (weight - level);
		layoutEntity.setInternalLocation(posx, posy);

		int relativeCount = 0;
		List children = childrenLists[i];
		//TODO: Slow
		for (Iterator iter = children.iterator(); iter.hasNext();) {
			InternalNode childEntity = (InternalNode) iter.next();
			int childEntityIndex = indexOfInternalNode(entities, childEntity);
			computePositionRecursively(childEntity, childEntityIndex, relativePosition + relativeCount, width, height, seenAlready, entities);
			relativeCount = relativeCount + getNumberOfLeaves(childEntity, childEntityIndex, entities);
		}
	}

	private int getNumberOfLeaves(InternalNode layoutEntity, int i, InternalNode[] entities) {
		return getNumberOfLeavesRecursive(layoutEntity, i, new HashSet(), entities);
	}

	private int getNumberOfLeavesRecursive(InternalNode layoutEntity, int i, Set seen, InternalNode[] entities) {
		int numLeaves = 0;
		List children = childrenLists[i];
		if (children.size() == 0) {
			numLeaves = 1;
		} else {
			//TODO: SLOW!
			for (Iterator iter = children.iterator(); iter.hasNext();) {
				InternalNode childEntity = (InternalNode) iter.next();
				if (!seen.contains(childEntity)) {
					seen.add(childEntity);
					int childEntityIndex = indexOfInternalNode(entities, childEntity);
					numLeaves += getNumberOfLeavesRecursive(childEntity, childEntityIndex, seen, entities);
				} else {
					numLeaves = 1;
				}
			}
		}
		return numLeaves;
	}

	private int getLevel(InternalNode layoutEntity, int i, InternalNode[] entities) {
		return getLevelRecursive(layoutEntity, i, new HashSet(), entities);
	}

	private int getLevelRecursive(InternalNode layoutEntity, int i, Set seen, InternalNode[] entities) {
		if (seen.contains(layoutEntity)) {
			return 0;
		}
		seen.add(layoutEntity);
		List parents = parentLists[i];
		int maxParentLevel = 0;
		for (Iterator iter = parents.iterator(); iter.hasNext();) {
			InternalNode parentEntity = (InternalNode) iter.next();
			int parentEntityIndex = indexOfInternalNode(entities, parentEntity);
			int parentLevel = getLevelRecursive(parentEntity, parentEntityIndex, seen, entities) + 1;
			maxParentLevel = Math.max(maxParentLevel, parentLevel);
		}
		return maxParentLevel;
	}

	/**
	 * Note: Use this as little as possible!
	 * TODO limit the use of this method 
	 * @param nodes
	 * @param nodeToFind
	 * @return
	 */
	private int indexOfInternalNode(InternalNode[] nodes, InternalNode nodeToFind) {
		for (int i = 0; i < nodes.length; i++) {
			InternalNode node = nodes[i];
			if (node.equals(nodeToFind)) {
				return i;
			}
		}
		throw new RuntimeException("Couldn't find index of internal node: " + nodeToFind);
	}

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

}
