/*
 * Copyright (c) 2005, David Benson
 *
 * All rights reserved.
 *
 * This file is licensed under the JGraph software license, a copy of which
 * will have been provided to you in the file LICENSE at the root of your
 * installation directory. If you are unable to locate this file please
 * contact JGraph sales for another copy.
 */
package com.mxgraph.layout.hierarchical.model;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;

/**
 * An abstraction of an internal node in the hierarchy layout
 */
public class mxGraphHierarchyNode extends mxGraphAbstractHierarchyCell
{

	/**
	 * Shared empty connection map to return instead of null in applyMap.
	 */
	public static Collection<mxGraphHierarchyEdge> emptyConnectionMap = new ArrayList<mxGraphHierarchyEdge>(0);

	/**
	 * The graph cell this object represents.
	 */
	public Object cell = null;

	/**
	 * Collection of hierarchy edges that have this node as a target
	 */
	public Collection<mxGraphHierarchyEdge> connectsAsTarget = emptyConnectionMap;

	/**
	 * Collection of hierarchy edges that have this node as a source
	 */
	public Collection<mxGraphHierarchyEdge> connectsAsSource = emptyConnectionMap;

	/**
	 * Assigns a unique hashcode for each node. Used by the model dfs instead
	 * of copying HashSets
	 */
	public int[] hashCode;

	/**
	 * Constructs an internal node to represent the specified real graph cell
	 * @param cell the real graph cell this node represents
	 */
	public mxGraphHierarchyNode(Object cell)
	{
		this.cell = cell;
	}

	/**
	 * Returns the integer value of the layer that this node resides in
	 * @return the integer value of the layer that this node resides in
	 */
	public int getRankValue()
	{
		return maxRank;
	}

	/**
	 * Returns the cells this cell connects to on the next layer up
	 * @param layer the layer this cell is on
	 * @return the cells this cell connects to on the next layer up
	 */
	@SuppressWarnings("unchecked")
	public List<mxGraphAbstractHierarchyCell> getNextLayerConnectedCells(int layer)
	{
		if (nextLayerConnectedCells == null)
		{
			nextLayerConnectedCells = new ArrayList[1];
			nextLayerConnectedCells[0] = new ArrayList<mxGraphAbstractHierarchyCell>(connectsAsTarget.size());
			Iterator<mxGraphHierarchyEdge> iter = connectsAsTarget.iterator();
			
			while (iter.hasNext())
			{
				mxGraphHierarchyEdge edge = (mxGraphHierarchyEdge) iter.next();

				if (edge.maxRank == -1 || edge.maxRank == layer + 1)
				{
					// Either edge is not in any rank or
					// no dummy nodes in edge, add node of other side of edge
					nextLayerConnectedCells[0].add(edge.source);
				}
				else
				{
					// Edge spans at least two layers, add edge
					nextLayerConnectedCells[0].add(edge);
				}
			}
		}

		return nextLayerConnectedCells[0];
	}

	/**
	 * Returns the cells this cell connects to on the next layer down
	 * @param layer the layer this cell is on
	 * @return the cells this cell connects to on the next layer down
	 */
	@SuppressWarnings("unchecked")
	public List<mxGraphAbstractHierarchyCell> getPreviousLayerConnectedCells(int layer)
	{
		if (previousLayerConnectedCells == null)
		{
			previousLayerConnectedCells = new ArrayList[1];
			previousLayerConnectedCells[0] = new ArrayList<mxGraphAbstractHierarchyCell>(connectsAsSource
					.size());
			Iterator<mxGraphHierarchyEdge> iter = connectsAsSource.iterator();

			while (iter.hasNext())
			{
				mxGraphHierarchyEdge edge = (mxGraphHierarchyEdge) iter.next();

				if (edge.minRank == -1 || edge.minRank == layer - 1)
				{
					// No dummy nodes in edge, add node of other side of edge
					previousLayerConnectedCells[0].add(edge.target);
				}
				else
				{
					// Edge spans at least two layers, add edge
					previousLayerConnectedCells[0].add(edge);
				}
			}
		}

		return previousLayerConnectedCells[0];
	}

	/**
	 * 
	 * @return whether or not this cell is an edge
	 */
	public boolean isEdge()
	{
		return false;
	}

	/**
	 * 
	 * @return whether or not this cell is a node
	 */
	public boolean isVertex()
	{
		return true;
	}

	/**
	 * Gets the value of temp for the specified layer
	 * 
	 * @param layer
	 *            the layer relating to a specific entry into temp
	 * @return the value for that layer
	 */
	public int getGeneralPurposeVariable(int layer)
	{
		return temp[0];
	}

	/**
	 * Set the value of temp for the specified layer
	 * 
	 * @param layer
	 *            the layer relating to a specific entry into temp
	 * @param value
	 *            the value for that layer
	 */
	public void setGeneralPurposeVariable(int layer, int value)
	{
		temp[0] = value;
	}

	public boolean isAncestor(mxGraphHierarchyNode otherNode)
	{
		// Firstly, the hash code of this node needs to be shorter than the
		// other node
		if (otherNode != null && hashCode != null && otherNode.hashCode != null
				&& hashCode.length < otherNode.hashCode.length)
		{
			if (hashCode == otherNode.hashCode)
			{
				return true;
			}
			
			if (hashCode == null || hashCode == null)
			{
				return false;
			}
			
			// Secondly, this hash code must match the start of the other
			// node's hash code. Arrays.equals cannot be used here since
			// the arrays are different length, and we do not want to
			// perform another array copy.
			for (int i = 0; i < hashCode.length; i++)
			{
				if (hashCode[i] != otherNode.hashCode[i])
				{
					return false;
				}
			}

			return true;
		}

		return false;
	}

}
