/*
 * 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 = 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 = 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) {
        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;
  }

}
