package elaprendiz.estructuras.nodos;

import java.awt.Color;
import java.awt.Point;
import java.awt.Graphics;

import java.util.Vector;

import elaprendiz.graficos.imagenes.ImageFrame;


public class ArbolNodo extends Nodo {


  /** The rank allows the binary tree to linearly order all the nodos.
   *  Rank is defined to be
   *  <PRE>
   *    left child rank  = (2 * parent rank)
   *    right child rank = (2 * parent rank) + 1
   *  </PRE>
   *  where the root node has rank 1. A node may be accessed via its
   *  rank index in constant time. The default rank of a node is set
   *  to a negative valor to indicate that it has not yet been defined.
   *  Rank cannot be assigned until the placement of a node within
   *  the binary tree has been determined.
   */
  protected int rank = -1;


  /** Root Constructor.
   */
  public ArbolNodo(Point origin, ImageFrame frame, int value) {
    super(origin, frame, value);
  }


  /** Copy Constructor. This constructor creates a near exact copy of the 
   *  ArbolNodo valor passed as a parameter. The child and parent references,
   *  however, are <STRONG>NOT</STRONG> copied (see Nodo superclass).
   *
   * @param node        The ArbolNodo to be copied.
   */
  public ArbolNodo(ArbolNodo node) {
    super(node);

    this.rank = node.rank;
  }


  /** Null Constructor. This constructor creates an abstract ArbolNodo. 
   *  That is, the node has no graphical representation. The returned node
   *  is used mainly for graphics operation which require a location on
   *  the graphics context but no physical representation (e.g. the move-
   *  ment of edges).
   */
  public ArbolNodo(Point origin) {
    super(origin);
  }


  /** Retrieve the rank of the current node. Note that if this valor
   *  has not been set then a negative rank will be returned (indicating
   *  an error).
   *
   * @return            The rank of the current node within the tree.
   */
  public int getRank() {
    return rank;
  }


  /** Set the rank of the current node. This valor should be set as soon
   *  as the parent of the current node is known (since rank depends upon
   *  the parent's rank). <STRONG>Note:</STRONG> this function recursively
   *  sets the rank of all the current node's children, too (if the node
   *  has any children).
   *
   * @param rank        The rank to set the current node to.
   */
  public void setRank(int rank) {
    this.rank = rank;

    ArbolNodo leftChild;
    ArbolNodo rightChild;

    if ( (leftChild = getLeftChild()) != null)
      leftChild.setRank(rank * 2);

    if ( (rightChild = getRightChild()) != null)
      rightChild.setRank((rank * 2) + 1);
  }


  /** Set the left child of the current node to node (this valor may be
   *  null).
   *
   * @param node        The node valor the current node's left child 
   *                    reference is set to.
   */
  public void setLeftChild(ArbolNodo node) {

    ////////////////////////////////////////
    // All tree references are elements in a
    // hashtable, and thus cannot be set to
    // null. They must be removed.

    if (node == null)
      nodos.remove("leftChild");
    else
      nodos.put("leftChild", node);
  }


  /** Retrieve the current node's left child reference (either null or
   * another node).
   *
   * @return            The current node's left child reference.
   */         
  public ArbolNodo getLeftChild() {
    return (ArbolNodo) nodos.get("leftChild");
  }


  /** Set the right child of the current node to node (this valor may be
   *  null).
   *
   * @param node        The node valor the current node's right child 
   *                    reference is set to.
   */
  public void setRightChild(ArbolNodo node) {

    ////////////////////////////////////////
    // All tree references are elements in a
    // hashtable, and thus cannot be set to
    // null. They must be removed.

    if (node == null)
      nodos.remove("rightChild");
    else
      nodos.put("rightChild", node);
  }


  /** Retrieve the current node's right child reference (either null or
   * another node).
   *
   * @return            The current node's right child reference.
   */         
  public ArbolNodo getRightChild() {
    return (ArbolNodo) nodos.get("rightChild");
  }


  /** Set the left parent of the current node to node (this valor may be
   *  null).
   *
   * @param node        The node valor the current node's parent reference 
   *                    is set to.
   */
  public void setParent(ArbolNodo node) {

    ////////////////////////////////////////
    // All tree references are elements in a
    // hashtable, and thus cannot be set to
    // null. They must be removed.

    if (node == null)
      nodos.remove("parent");
    else
      nodos.put("parent", node);
  }


  /** Retrieve the current node's parent reference (either null or
   * another node).
   *
   * @return            The current node's parent reference.
   */         
  public ArbolNodo getParent() {
    return (ArbolNodo) nodos.get("parent");
  }


  /** Return the node with the least valor in the current sub-tree.
   *  If the root of the sub-tree has no left children then the root
   *  is returned (the root is the node with the least valor).
   *
   * @return            The node with the least valor in the current
   *                    sub-tree.
   */
  public ArbolNodo getLeastChild() {
    ArbolNodo check;

    ////////////////////////////////////////
    // If no left child then the current 
    // node is the least child.

    if ( (check = (ArbolNodo) nodos.get("leftChild")) == null)
      return this;

    ////////////////////////////////////////
    // Else iterate until the least child is
    // found, and return reference.

    else
      while (check.nodos.get("leftChild") != null) {
	check = (ArbolNodo) check.nodos.get("leftChild");
      }

    return check;
  }


  /** Return the node with the greatest valor in the current sub-tree.
   *  If the root of the sub-tree has no right children then the root
   *  is returned (the root is the node with the greatest valor).
   *
   * @return            The node with the greatest valor in the current
   *                    sub-tree.
   */
  public ArbolNodo getGreatestChild() {
    ArbolNodo check;

    ////////////////////////////////////////
    // If no right child then the current 
    // node is the greatest child.

    if ( (check = (ArbolNodo) nodos.get("rightChild")) == null)
      return this;

    ////////////////////////////////////////
    // Else iterate until the greatest child
    // is found, and return reference.

    else
      while (check.nodos.get("rightChild") != null) {
	check = (ArbolNodo) check.nodos.get("rightChild");
      }

    return check;
  }


  /** Translates a node and its subtree. That is, both the node and its 
   *  children are shifted vertically and horizontally by x and y amounts.
   *
   * @param x          The lateral amount to shift the subtree.
   * @param y          The vertical amount to shift the subtree.
   */
  public void translate(int x, int y) {
    origen.translate(x, y);

    ArbolNodo leftChild  = (ArbolNodo) nodos.get("leftChild");
    ArbolNodo rightChild = (ArbolNodo) nodos.get("rightChild");
 
    if (leftChild != null)
      leftChild.translate(x, y);

    if (rightChild != null)
      rightChild.translate(x, y);
  }


  /** Translates a single node. <STRONG>Note:</STRONG> this method does
   *  <I>not</I> translate the node's subtree.
   *
   * @param x           The lateral amount to shift the current node.
   * @param y           the vertical amount to shift the current node.
   */
  public void translateNode(int x, int y) {
    origen.translate(x, y);
  }


  /** Move a node and its subtree to a new location. That is, both the node
   *  and its children are moved to a new location, (x, y). The whole sub-
   *  structure (tree) is moved intacted centered upon the head (root) node.
   *  That is, the distances between the current node and its children are
   *  maintained.
   * 
   * @param x          The lateral point to move the subtree to.
   * @param y          The vertical point to move the subtree to.
   */
  public void move(int x, int y) {
    ArbolNodo leftChild  = (ArbolNodo) nodos.get("leftChild");
    ArbolNodo rightChild = (ArbolNodo) nodos.get("rightChild");

    int width;
    int depth;

    if (leftChild != null) {
      width = origen.x - leftChild.origen.x;
      depth = leftChild.origen.y - origen.y;

      leftChild.move(x - width, y + depth);
    }
      
    if (rightChild != null) {
      width = rightChild.origen.x - origen.x;
      depth = rightChild.origen.y - origen.y;

      rightChild.move(x + width, y + depth);
    }

    origen.move(x, y);
  }


  /** Move a single node. <STRONG>Note:</STRONG> this method does <I>not</I>
   *  move the node's subtree.
   *
   * @param x           The lateral location the current node is moved to.
   * @param y           the vertical location the current node is moved to.
   */
  public void moveNode(int x, int y) {
    origen.move(x, y);
  }

// dibuja las lineas y genera la imagen del arbol
  public void draw(Graphics g) {
    Color backColor = g.getColor();
    
    ArbolNodo parent  = (ArbolNodo) nodos.get("parent");

    g.setColor(Color.black);
    if (parent != null) {

      g.setColor(Color.GREEN);
      g.drawLine(origen.x, origen.y, parent.origen.x, parent.origen.y);

      g.setColor(Color.green.brighter());
      g.drawLine(origen.x, origen.y + 1, parent.origen.x,
		 parent.origen.y + 1);

      g.setColor(Color.gray);
      g.drawLine(origen.x, origen.y + 4, parent.origen.x,
		 parent.origen.y + 4);
    }
    g.setColor(backColor);

    super.draw(g);
  }


  protected ArbolNodo cloneNode(Vector returnVector, ArbolNodo parent) {
    ArbolNodo copy = new ArbolNodo(this);

    if (parent != null)
      copy.setParent(parent);

    ArbolNodo leftChild  = (ArbolNodo) nodos.get("leftChild");
    ArbolNodo rightChild = (ArbolNodo) nodos.get("rightChild");

    if (leftChild != null)
      copy.setLeftChild(leftChild.cloneNode(returnVector, copy));

    if (rightChild != null)
      copy.setRightChild(rightChild.cloneNode(returnVector, copy));

    returnVector.addElement(copy);
    return copy;
  }


  public synchronized Vector cloneStructure() {
    Vector treeVector = new Vector();
    cloneNode(treeVector, null);
    return treeVector;
  }
    
}
