/*
 *    This program is free software; you can redistribute it and/or modify
 *    it under the terms of the GNU General Public License as published by
 *    the Free Software Foundation; either version 2 of the License, or
 *    (at your option) any later version.
 *
 *    This program is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *    GNU General Public License for more details.
 *
 *    You should have received a copy of the GNU General Public License
 *    along with this program; if not, write to the Free Software
 *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

/*
 *    Node.java
 *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
 *
 */

package weka.gui.treevisualizer;

import java.awt.*;
import java.util.*;
import java.io.*;
import weka.core.Instances;


//this is a node structure that to be useful needs the Edge class as well

//note i have done an unintentional naughty thing
//getHeight() returns the pixel height of the node
//getHeight(Node,int) returns how many levels down the tree goes
//setHeight(int) is associated to the prior

/**
 * This class records all the data about a particular node for displaying.
 *
 * @author Malcolm Ware (mfw4@cs.waikato.ac.nz)
 * @version $Revision: 7059 $
 */
public class Node {
  
  /** The fill mode for the node (not in use). */
  private int m_backstyle;       //how the back color will fill

  /** The shape of the node. */
  private int m_shape;

  /** The color of the node. */
  private Color m_color;

  /** the text for the node. */
  private String m_label;

  /** the text broken up into lines */
  private Vector m_lines;

  //the coord of the left side .note all coords are
  //between 1-0 for scaling per Stuart's suggestion
  /** The center of the node (between 0 and 1). */
  private double m_center;       //coord of the center . main x value used

  /** The top of the node (between 0 and 1). */
  private double m_top;          //main y coord to go by
     
  /** true if this nodes descendants are visible (not in use currently). */
  private boolean m_cVisible;   //whether it's descendants are visible

  /** true if this node is visible (not currently in use). */
  private boolean m_visible;     //whether it's visible

  /** true if this is the top of the tree. ie has no parent */
  private boolean m_root;     //whether it is anscestor to all i.e top of tree 

  /** An array containing references to all the parent edges 
   * (only 1 currently). */
  private Vector m_parent;      //the edge to its parent edges(or itself 

  //if true root)
  /** An array containing references to all the child edges. */
  private Vector m_children;     //a vector list of edges to the nodes children

  /** The ID string for this node (used for construction purposes) */
  private String m_refer;

  /** A String containing extra information about the node. */
  private String m_data;

  /**
   * An Instances variable generated from the data.
   * Note that if this exists then the string shall be NULL to save space.
   */
  private Instances m_theData;

  /**
   * This will setup all the values of the node except for its top and center.
   *
   * @param label The text for the node.
   * @param refer The ID string for this node.
   * @param backstyle The backstyle of this node.
   * @param shape The shape of this node.
   * @param color The color of this node.
   */
  public Node(String label,String refer,int backstyle,int shape,
	      Color color,String d) {
    m_label = label;
    m_backstyle = backstyle;
    m_shape = shape;
    m_color = color;
    m_refer = refer;
   
    m_center = 0;
    m_top = 0;

    m_cVisible = true;
    m_visible = true;
    m_root = false;
    m_parent = new Vector(1,1);
    m_children = new Vector(20,10);
    m_lines = new Vector(4,2);
    breakupLabel();
    m_data = d;
    m_theData = null;
  } 	
  
  /**
   * This will return the Instances object related to this node.
   * If it has not been allocated then that will be done also.
   *
   * @return The Instances object.
   */
  public Instances getInstances() {
    if (m_theData == null && m_data != null) {
      try {
	m_theData = new Instances(new StringReader(m_data));
      } catch(Exception e) {
	System.out.println(Messages.getInstance().getString("Node_GetInstances_Exception_Text") + e);
      }
      m_data = null;
    }
    return m_theData;
  }

  /**
   * Get If this node's childs are visible.
   *
   * @return True if the childs are visible.
   */
  public boolean getCVisible() {
    return m_cVisible;
  }

  /** 
   * Recursively goes through the tree and sets all the children and the 
   * parent to visible.
   *
   * @param r The current node to set visible.
   */
  private void childVis(Node r) {
    Edge e;
    r.setVisible(true);
    if (r.getCVisible()) {
      for (int noa = 0;(e = r.getChild(noa)) != null;noa++) {
	childVis(e.getTarget());
      }
    }
  }

  /**
   * Sets all the children of this node either to visible or invisible
   *
   * @param v True if the children are to be visible
   */
  public void setCVisible(boolean v) {
    m_cVisible = v;
    if (v) {
      childVis(this);
    }
    else if (!v) {
      childInv(this);
    }
  }
  
  /**
   * Recursively goes through the tree and sets all the children to invisible,
   * Not the parent though.
   *
   * @param r The current node from whom the children are gonna be set 
   * invisible.
   */
  private void childInv(Node r) {
    Edge e;
    Node s;
    for (int noa = 0;(e = r.getChild(noa)) != null;noa++) {
      s = e.getTarget();
      s.setVisible(false);
      childInv(s);
    }
  }

  /**
   * Get the value of refer.
   *
   * @return Value of refer.
   */
  public String getRefer() {
    
    return m_refer;
  }
  
  /**
   * Set the value of refer.
   *
   * @param v  Value to assign to refer.
   */
  public void setRefer(String v) {
    
    m_refer = v;
  }
  
  
  
  /**
   * Get the value of shape.
   *
   * @return Value of shape.
   */
  public int getShape() {
    
    return m_shape;
  }
  
  /**
   * Set the value of shape.
   *
   * @param v  Value to assign to shape.
   */
  public void setShape(int v) {
    
    m_shape = v;
  }
  
  
  /**
   * Get the value of color.
   *
   * @return Value of color.
   */
  public Color getColor() {
    
    return m_color;
  }
  
  /**
   * Set the value of color.
   *
   * @param v  Value to assign to color.
   */
  public void setColor(Color v) {
    
    m_color = v;
  }
  
  
  /**
   * Get the value of label.
   *
   * @return Value of label.
   */
  public String getLabel() {
    
    return m_label;
  }
  
  /**
   * This Will break the node's text up into lines.
   *
   */
  private void breakupLabel() {
    int prev = 0,noa;
    for (noa = 0;noa < m_label.length();noa++) {
      if (m_label.charAt(noa) == '\n') {
	m_lines.addElement(m_label.substring(prev,noa));
	prev = noa+1;
      }
    }
    m_lines.addElement(m_label.substring(prev,noa));
    
  }
  
  /**
   * This will return the width and height of the rectangle that the text 
   * will fit into.
   *
   * @param f The size info for the Font.
   * @return A Dimension containing the size of the text.
   */
  public Dimension stringSize(FontMetrics f) {
    Dimension d = new Dimension();
    int old = 0;
    String s;
    int noa = 0;
    while ((s = getLine(noa)) != null) {
      noa++;
      old = f.stringWidth(s);
      
      if (old > d.width) {
	d.width = old;
      }
    }
    d.height = noa * f.getHeight();
    return d;
    
  }

  /**
   * Returns the text String for the specfied line.
   *
   * @param n The line wanted.
   * @return The String corresponding to that line.
   */
  public String getLine(int n) {
    if (n < m_lines.size()) {
      return (String)m_lines.elementAt(n);
    }
    else {
      return null;
    }
  }
  
  
  

  
 
  

  
  
  /**
   * Get the value of center.
   *
   * @return Value of center.
   */
  public double getCenter() {
    
    return m_center;
  }
  
  /**
   * Set the value of center.
   *
   * @param v  Value to assign to center.
   */
  public void setCenter(double v) {
    
    m_center = v;
  }
  
  /**
   * Will increase or decrease the postion of center.
   *
   * @param v The amount to increase or decrease center by.
   */
  public void adjustCenter(double v) {
    m_center += v;
  }
  
  
  /**
   * Get the value of top.
   *
   * @return Value of top.
   */
  public double getTop() {
    
    return m_top;
  }
  
  /**
   * Set the value of top.
   *
   * @param v  Value to assign to top.
   */
  public void setTop(double v) {
    
    m_top = v;
  }
  

  
  
  
  /**
   * Get the value of visible.
   *
   * @return Value of visible.
   */
  public boolean getVisible() {
    
    return m_visible;
  }
  
  /**
   * Set the value of visible.
   *
   * @param v  Value to assign to visible.
   */
  private void setVisible(boolean v) {
    
    m_visible = v;
  }
  
  
  
  
  /**
   * Get the value of root.
   *
   * @return True if has no parents.
   */
  public boolean getRoot() {
    
    return m_root;
  }
  
  /**
   * Set the value of root.
   *
   * @param v  Value to assign to root.
   */
  public void setRoot(boolean v) {
    
    m_root = v;
  }
  
  
  
  /**
   * Get the parent edge.
   *
   * @param i The parent number to get.
   * @return The parent edge or NULL if it doesn't exist.
   */
  public Edge getParent(int i) {
    
    if (i < m_parent.size()) {
      return (Edge)m_parent.elementAt(i);
    }
    else {
      return null;
    }

  }
  
  /**
   * Set the value of parent.
   *
   * @param v  Value to assign to parent.
   */
  public void setParent(Edge v) {
    
    m_parent.addElement(v);
  }
  
  
  
  /**
   * Get the Edge for the child number 'i'.
   *
   * @param i The child number to get.
   * @return The child Edge or NULL if it doesn't exist.
   */
  public Edge getChild(int i) {
    
    if (i < m_children.size()) {
      return (Edge)m_children.elementAt(i);
    }
    else {
      return null;
    }
  }
  
  /**
   * Set the value of children.
   *
   * @param v  Value to assign to children.
   */
  public void addChild(Edge v) {
    m_children.addElement(v);
  }
  

  /**
   * Recursively finds the number of visible groups of siblings there are.
   *
   * @param r The current Node upto.
   * @param n The current number of groups there are.
   * @return The number of groups found so far.
   */
  public static int getGCount(Node r,int n) {
    Edge e;
    
    if (r.getChild(0) != null && r.getCVisible()) {
      n++;
      for (int noa = 0;(e = r.getChild(noa)) != null;noa++) {
	n = getGCount(e.getTarget(),n);
      }
    }
    return n;
  }

  /**
   * Recursively finds the total number of groups of siblings there are.
   *
   * @param r The current Node upto.
   * @param n The current number of groups there are.
   * @return The number of groups found so far.
   */
  public static int getTotalGCount(Node r,int n) {
    Edge e;
    
    if (r.getChild(0) != null) {
      n++;
      for (int noa = 0;(e = r.getChild(noa)) != null;noa++) {
	n = getTotalGCount(e.getTarget(),n);
      }
    }
    return n;
  }
  




  /**
   * Recursively finds the number of visible nodes there are (this may 
   * accidentally count some of the invis nodes).
   *
   * @param r The current Node upto.
   * @param n The current number nodes there are.
   * @return The number of nodes found so far.
   */
  public static int getCount(Node r,int n) {
    Edge e;
    n++;
    for (int noa = 0;(e = r.getChild(noa)) != null && r.getCVisible();noa++) {
      n = getCount(e.getTarget(),n);
    }
    return n;
    
  }

  /**
   * Recursively finds the total number of nodes there are.
   *
   * @param r The current Node upto.
   * @param n The current number nodes there are.
   * @return The number of nodes found so far.
   */
  public static int getTotalCount(Node r,int n) {
    Edge e;
    n++;
    for (int noa = 0;(e = r.getChild(noa)) != null;noa++) {
      n = getTotalCount(e.getTarget(),n);
    }
    return n;
  }
  
  
  /**
   * Recursively finds the number of visible levels there are.
   *
   * @param r The current Node upto.
   * @param l The curent level.
   * @return The max number of levels found so far.
   */
  public static int getHeight(Node r,int l) {
    l++;
    int lev = l,temp = 0;
    Edge e;
    
    for (int noa = 0;(e = r.getChild(noa)) != null && r.getCVisible();noa++) {
      temp = getHeight(e.getTarget(),l);
      if (temp > lev) {
	lev = temp;
      }
      
    }
    
    return lev;


  }

  /**
   * Recursively finds the total number of levels there are.
   *
   * @param r The current Node upto.
   * @param l The curent level.
   * @return The max number of levels found so far.
   */
  public static int getTotalHeight(Node r,int l) {
    l++;
    int lev = l,temp = 0;
    Edge e;
    
    for (int noa = 0;(e = r.getChild(noa)) != null;noa++) {
      temp = getTotalHeight(e.getTarget(),l);
      if (temp > lev) {
	lev = temp;
      }
    }
    return lev;
  }
}
