/*
 *    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.
 */

/*
 *    UserClassifier.java
 *    Copyright (C) 1999 Malcolm Ware
 *
 */

package weka.classifiers.trees;

import weka.classifiers.functions.LinearRegression;
import weka.classifiers.Classifier;
import weka.classifiers.CostMatrix;
import weka.classifiers.Evaluation;
import weka.classifiers.rules.ZeroR;
import weka.classifiers.lazy.IB1;
import java.awt.event.*;
import java.io.*;
import javax.swing.*;
import weka.gui.treevisualizer.*;
import weka.core.*;
import weka.filters.unsupervised.attribute.Remove;
import weka.filters.Filter;
import weka.classifiers.trees.j48.*;
import weka.gui.visualize.*;
/*import weka.gui.visualize.VisualizePanel;
import weka.gui.visualize.VisualizePanelListener;
import weka.gui.visualize.VisualizePanelEvent; */
import weka.gui.GenericObjectEditor;
import weka.gui.PropertyDialog;

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeSupport;


/**
 * Class for generating an user defined decision tree. For more info see <p>
 *
 * Ware M., Frank E., Holmes G., Hall M. and Witten I.H. (2000).
 * <i>interactive machine learning - letting users build classifiers</i>,
 * Working Paper 00/4, Department of Computer Science, 
 * University of Waikato; March. Also available online at
 * <a href="http://www.cs.waikato.ac.nz/~ml/publications/2000/
 * 00MW-etal-Interactive-ML.ps">
 * http://www.cs.waikato.ac.nz/~ml/publications/2000/
 * 00MW-etal-Interactive-ML.ps</a>. <p>
 *
 * @author Malcolm Ware (mfw4@cs.waikato.ac.nz)
 * @version $Revision: 1.18.2.1 $
 */
public class UserClassifier extends Classifier implements Drawable,
TreeDisplayListener, VisualizePanelListener {
  

  /** I am not sure if these are strictly adhered to in visualizepanel
   * so I am making them private to avoid confusion, (note that they will
   * be correct in this class, VLINE and HLINE aren't used).
   */
  private static final int LEAF = 0;
  private static final int RECTANGLE = 1;
  private static final int POLYGON = 2;
  private static final int POLYLINE = 3;
  private static final int VLINE = 5;
  private static final int HLINE =6;
  

  /** The tree display panel. */
  private transient TreeVisualizer m_tView = null;
  /** The instances display. */
  private transient VisualizePanel m_iView = null;
  /** Two references to the structure of the decision tree. */
  private TreeClass m_top, m_focus;
  /** The next number that can be used as a unique id for a node. */
  private int m_nextId;
  /** These two frames aren't used anymore. */
  private transient JFrame m_treeFrame;
  private transient JFrame m_visFrame;
  
  /** The tabbed window for the tree and instances view. */
  private transient JTabbedPane m_reps;
  /** The window. */
  private transient JFrame m_mainWin;
  /** The status of whether there is a decision tree ready or not. */
  private boolean m_built=false;
  /** A list of other m_classifiers. */
  private GenericObjectEditor m_classifiers;
  /** A window for selecting other classifiers. */
  private PropertyDialog m_propertyDialog;

  /* Register the property editors we need */
  static {
    GenericObjectEditor.registerEditors();
  }

  /**
   * Main method for testing this class.
   *
   * @param argv should contain command line options (see setOptions)
   */
  public static void main(String [] argv) {

    try {
      System.out.println(Evaluation.evaluateModel(new UserClassifier(), argv));
    } catch (Exception e) {
      System.err.println(e.getMessage());
      e.printStackTrace();
    }
    System.exit(0);
    //System.out.println("im done");
  }

  /**
   * @return a string that represents this objects tree.
   */
  public String toString() {
    if (!m_built) {

      return "Tree Not Built";
    }
    StringBuffer text = new StringBuffer();
    try {
      m_top.toString(0, text);
      
      m_top.objectStrings(text);

    } catch(Exception e) {
      System.out.println("error: " + e.getMessage());
    }
    
    return text.toString();


  }


  /**
   * Receives user choices from the tree view, and then deals with these 
   * choices. 
   * @param e The choice. 
   */
  public void userCommand(TreeDisplayEvent e) {
    
    if (m_propertyDialog != null) {
      m_propertyDialog.dispose();
      m_propertyDialog = null;
    }
    try {
      if (m_iView == null || m_tView == null) {
	//throw exception
      }
      if (e.getCommand() == e.NO_COMMAND) {
	//do nothing
      }
      else if (e.getCommand() == e.ADD_CHILDREN) {
	//highlight the particular node and reset the vis panel
	if (m_top == null) {
	  //this shouldn't happen , someone elses code would 
	  //have to have added a trigger to this listener.
	  System.out.println("Error : Received event from a TreeDisplayer"
			     + " that is unknown to the classifier.");
	}
	else {
	  m_tView.setHighlight(e.getID());
	  /*if (m_iView == null)
	    {
	    m_iView = new VisualizePanel(this);
	    m_iView.setSize(400, 300);
	    }*/
	  m_focus = m_top.getNode(e.getID());
	  m_iView.setInstances(m_focus.m_training);
	  if (m_focus.m_attrib1 >= 0) {
	    m_iView.setXIndex(m_focus.m_attrib1);
	  }
	  if (m_focus.m_attrib2 >= 0) {
	    m_iView.setYIndex(m_focus.m_attrib2);
	  }
	  m_iView.setColourIndex(m_focus.m_training.classIndex());
	  if (((Double)((FastVector)m_focus.m_ranges.elementAt(0)).
	       elementAt(0)).intValue() != LEAF) {
	    m_iView.setShapes(m_focus.m_ranges);
	  }
	  //m_iView.setSIndex(2);
	}
      }
      else if (e.getCommand() == e.REMOVE_CHILDREN) {
	/*if (m_iView == null)
	  {
	  m_iView = new VisualizePanel(this);
	  m_iView.setSize(400, 300);
	  }*/
	m_focus = m_top.getNode(e.getID());
	m_iView.setInstances(m_focus.m_training);
	if (m_focus.m_attrib1 >= 0) {
	  m_iView.setXIndex(m_focus.m_attrib1);
	}
	if (m_focus.m_attrib2 >= 0) {
	  m_iView.setYIndex(m_focus.m_attrib2);
	}
	m_iView.setColourIndex(m_focus.m_training.classIndex());
	if (((Double)((FastVector)m_focus.m_ranges.elementAt(0)).
	     elementAt(0)).intValue() != LEAF) {
	  m_iView.setShapes(m_focus.m_ranges);
	}
	//m_iView.setSIndex(2);
	//now to remove all the stuff
	m_focus.m_set1 = null;
	m_focus.m_set2 = null;
	m_focus.setInfo(m_focus.m_attrib1, m_focus.m_attrib2, null);
	//tree_frame.getContentPane().removeAll();
	m_tView = new TreeVisualizer(this, graph(), new PlaceNode2());
	//tree_frame.getContentPane().add(m_tView);
	m_reps.setComponentAt(0, m_tView);
	//tree_frame.getContentPane().doLayout();
	m_tView.setHighlight(m_focus.m_identity);
      }
      else if (e.getCommand() == e.CLASSIFY_CHILD) {
	/*if (m_iView == null)
	  {
	  m_iView = new VisualizePanel(this);
	  m_iView.setSize(400, 300);
	  }*/
	m_focus = m_top.getNode(e.getID());
	m_iView.setInstances(m_focus.m_training);
	if (m_focus.m_attrib1 >= 0) {
	  m_iView.setXIndex(m_focus.m_attrib1);
	}
	if (m_focus.m_attrib2 >= 0) {
	  m_iView.setYIndex(m_focus.m_attrib2);
	}
	m_iView.setColourIndex(m_focus.m_training.classIndex());
	if (((Double)((FastVector)m_focus.m_ranges.elementAt(0)).
	     elementAt(0)).intValue() != LEAF) {
	  m_iView.setShapes(m_focus.m_ranges);
	}
	
	
	m_propertyDialog = new PropertyDialog(m_classifiers, 
					      m_mainWin.getLocationOnScreen().x,
					      m_mainWin.getLocationOnScreen().y);
	
	//note property dialog may change all the time
	//but the generic editor which has the listeners does not
	//so at the construction of the editor is when I am going to add
	//the listeners.
	
	
	
	//focus.setClassifier(new IB1());
	//tree_frame.getContentPane().removeAll();
	//////m_tView = new Displayer(this, graph(), new PlaceNode2());
	//tree_frame.getContentPane().add(m_tView);
	//tree_frame.getContentPane().doLayout();
	/////////////reps.setComponentAt(0, m_tView);
	m_tView.setHighlight(m_focus.m_identity);
      }
      /*else if (e.getCommand() == e.SEND_INSTANCES) {
	TreeClass source = m_top.getNode(e.getID());
	m_iView.setExtInstances(source.m_training);
	}*/
      else if (e.getCommand() == e.ACCEPT) {
	
	int well = JOptionPane.showConfirmDialog(m_mainWin, 
						 "Are You Sure...\n"
						 + "Click Yes To Accept The"
						 + " Tree" 
						 + "\n Click No To Return",
						 "Accept Tree", 
						 JOptionPane.YES_NO_OPTION);
	
	if (well == 0) {
	  m_mainWin.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
	  m_mainWin.dispose();
	  blocker(false);  //release the thread waiting at blocker to 
	  //continue.
	}
	
      }
    } catch(Exception er) {
      System.out.println("Error : " + er);
      System.out.println("Part of user input so had to catch here");
      er.printStackTrace();
    }
  }
  

  /**
   * This receives shapes from the data view. 
   * It then enters these shapes into the decision tree structure. 
   * @param e Contains the shapes, and other info.
   */
  public void userDataEvent(VisualizePanelEvent e) {
    
    if (m_propertyDialog != null) {
      m_propertyDialog.dispose();
      m_propertyDialog = null;
    }
    
    try {
      if (m_focus != null) {
	

	double wdom = e.getInstances1().numInstances() 
	  + e.getInstances2().numInstances();
	if (wdom == 0) {
	  wdom = 1;
	}
	
	TreeClass tmp = m_focus;
	m_focus.m_set1 = new TreeClass(null, e.getAttribute1(), 
				       e.getAttribute2(), m_nextId, 
				       e.getInstances1().numInstances() / wdom,
 				       e.getInstances1(), m_focus);
	
	m_focus.m_set2 = new TreeClass(null, e.getAttribute1(), 
				       e.getAttribute2(), m_nextId, 
				       e.getInstances2().numInstances() / wdom,
				       e.getInstances2(), m_focus); 
	//this needs the other instance
	
	
	//tree_frame.getContentPane().removeAll();  
	m_focus.setInfo(e.getAttribute1(), e.getAttribute2(), e.getValues());
	//System.out.println(graph());
	m_tView = new TreeVisualizer(this, graph(), new PlaceNode2());
	//tree_frame.getContentPane().add(m_tView);
	//tree_frame.getContentPane().doLayout();
	m_reps.setComponentAt(0, m_tView);
	
	m_focus = m_focus.m_set2;
	m_tView.setHighlight(m_focus.m_identity);
	m_iView.setInstances(m_focus.m_training);
	if (tmp.m_attrib1 >= 0) {
	  m_iView.setXIndex(tmp.m_attrib1);
	}
	if (tmp.m_attrib2 >= 0) {
	  m_iView.setYIndex(tmp.m_attrib2);
	}
	m_iView.setColourIndex(m_focus.m_training.classIndex());
	if (((Double)((FastVector)m_focus.m_ranges.elementAt(0)).
	     elementAt(0)).intValue() != LEAF) {
	  m_iView.setShapes(m_focus.m_ranges);
	}
	//m_iView.setSIndex(2);
      }
      else {
	System.out.println("Somehow the focus is null");
      }
    } catch(Exception er) {
      System.out.println("Error : " + er);
      System.out.println("Part of user input so had to catch here");
      //er.printStackTrace();
    }
    
  }
  
  /** 
   * Constructor
   */
  public UserClassifier() {
    //do nothing here except set alot of variables to default values
    m_top = null;
    m_tView = null;
    m_iView = null;
    m_nextId = 0; 
    
  }
  
 /**
   *  Returns the type of graph this classifier
   *  represents.
   *  @return Drawable.TREE
   */   
  public int graphType() {
      return Drawable.TREE;
  }

  /**
   * @return A string formatted with a dotty representation of the decision
   * tree.
   * @exception Exception if String can't be built properly.
   */
  public String graph() throws Exception {
    //create a dotty rep of the tree from here
    StringBuffer text = new StringBuffer();
    text.append("digraph UserClassifierTree {\n" +
		"node [fontsize=10]\n" +
		"edge [fontsize=10 style=bold]\n");
    
    m_top.toDotty(text);
    return text.toString() +"}\n";
    
    
  }
  
  /**
   * A function used to stop the code that called buildclassifier
   * from continuing on before the user has finished the decision tree.
   * @param tf True to stop the thread, False to release the thread that is
   * waiting there (if one).
   */
  private synchronized void blocker(boolean tf) {
    if (tf) {
      try {
	wait();
      } catch(InterruptedException e) {
      }
    }
    else {
      notifyAll();
    }
    
    //System.out.println("out");
  }

  /**
   * This will return a string describing the classifier.
   * @return The string.
   */
  public String globalInfo() {

    return "Interactively classify through visual means."
      + " You are Presented with a scatter graph of the data against two user"
      + " selectable attributes, as well as a view of the decision tree."
      + " You can create binary splits by creating polygons around data"
      + " plotted on the scatter graph, as well as by allowing another"
      + " classifier to take over at points in the decision tree should you"
      + " see fit.";
  }

  /**
   * Call this function to build a decision tree for the training
   * data provided.
   * @param i The training data.
   * @exception Exception if can't build classification properly.
   */
  public void buildClassifier(Instances i) throws Exception {
    //construct a visualizer
    //construct a tree displayer and feed both then do nothing
    //note that I will display at the bottom of each split how many 
    //fall into each catagory
    
    m_classifiers = new GenericObjectEditor(true);
    m_classifiers.setClassType(Classifier.class);
    m_classifiers.setValue(new weka.classifiers.rules.ZeroR());
    
    ((GenericObjectEditor.GOEPanel)m_classifiers.getCustomEditor())
      .addOkListener(new ActionListener() {
	  public void actionPerformed(ActionEvent e) {
	    //I want to use the focus variable but to trust it I need
	    //to kill the window if anything gets changed by either
	    //editor
	    try {
	      m_focus.m_set1 = null;
	      m_focus.m_set2 = null;
	      m_focus.setInfo(m_focus.m_attrib1, m_focus.m_attrib2, null);
	      m_focus.setClassifier((Classifier)m_classifiers.getValue());
	      m_classifiers = new GenericObjectEditor();
	      m_classifiers.setClassType(Classifier.class);
	      m_classifiers.setValue(new weka.classifiers.rules.ZeroR());
	      ((GenericObjectEditor.GOEPanel)m_classifiers.getCustomEditor())
		.addOkListener(this);
	      m_tView = new TreeVisualizer(UserClassifier.this, graph(), 
				     new PlaceNode2());
	      m_tView.setHighlight(m_focus.m_identity);
	      m_reps.setComponentAt(0, m_tView);
	      m_iView.setShapes(null);
	    } catch(Exception er) {
	      System.out.println("Error : " + er);
	      System.out.println("Part of user input so had to catch here");
	    }
	  }
	});
    
    m_built = false;
    m_mainWin = new JFrame();
    
    m_mainWin.addWindowListener(new WindowAdapter() {
	public void windowClosing(WindowEvent e) {
	  int well = JOptionPane.showConfirmDialog(m_mainWin, 
						   "Are You Sure...\n"
						   + "Click Yes To Accept"
						   + " The Tree" 
						   + "\n Click No To Return",
						   "Accept Tree", 
						   JOptionPane.YES_NO_OPTION);
	  
	  if (well == 0) {
	    m_mainWin.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
	    blocker(false);
	    
	  }
	  else {
	    m_mainWin.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
	  }
	}
      });
    
    m_reps = new JTabbedPane();
    m_mainWin.getContentPane().add(m_reps);
    

    

    
    //make a backup of the instances so that any changes don't go past here.
    Instances te = new Instances(i, i.numInstances());
    for (int noa = 0; noa < i.numInstances(); noa++) {
      te.add(i.instance(noa));
    }
    
    te.deleteWithMissingClass(); //remove all instances with a missing class
    //from training
    
    m_top = new TreeClass(null, 0, 0, m_nextId, 1, te, null);
    m_focus = m_top;
    //System.out.println(graph());
    m_tView = new TreeVisualizer(this, graph(), new PlaceNode1());
    
    m_reps.add("Tree Visualizer", m_tView);
    //tree_frame = new JFrame();
    //tree_frame.getContentPane().add(m_tView);
    //tree_frame.setSize(800,600);
    //tree_frame.setVisible(true);
    
    m_tView.setHighlight(m_top.m_identity);
    m_iView = new VisualizePanel(this);
    //m_iView.setSize(400, 300);
    m_iView.setInstances(m_top.m_training);
    m_iView.setColourIndex(te.classIndex());
    //vis_frame = new JFrame();
    //vis_frame.getContentPane().add(m_iView);
    //vis_frame.setSize(400, 300);
    //vis_frame.setVisible(true);
    m_reps.add("Data Visualizer", m_iView);
    m_mainWin.setSize(560, 420);
    m_mainWin.setVisible(true);
    blocker(true);          //a call so that the main thread of 
    //execution has to wait for the all clear message from the user.
    
    //so that it can be garbage 
    if (m_propertyDialog != null) {
      m_propertyDialog.dispose();
      m_propertyDialog = null;
    }
    
    //collected
    m_classifiers = null;
    m_built = true;
  }

  /**
   * Call this function to get a double array filled with the probability
   * of how likely each class type is the class of the instance.
   * @param i The instance to classify.
   * @return A double array filled with the probalities of each class type.
   * @exception Exception if can't classify instance.
   */
  public double[] distributionForInstance(Instance i) throws Exception {
    

    if (!m_built) {
      return null;
    }
    
    double[] res = m_top.calcClassType(i);
    if (m_top.m_training.classAttribute().isNumeric()) {
      return res;
    }

    double most_likely = 0, highest = -1;
    double count = 0;
    for (int noa = 0; noa < m_top.m_training.numClasses(); noa++) {
      count += res[noa];
      if (res[noa] > highest) {
	most_likely = noa;
	highest = res[noa];
      }
    }
    
    if (count <= 0) {
      //not sure how this happened.
      return null;
    }

    for (int noa = 0; noa < m_top.m_training.numClasses(); noa++) {
      res[noa] = res[noa] / count;
    }
    //System.out.println("ret");
    
    return res;
  }
  

  
  
  
  /**
   * Inner class used to represent the actual decision tree structure and data.
   */
  private class TreeClass implements Serializable {
    
    /**
     * This contains the info for the coords of the shape converted 
     * to attrib coords, 
     * for polygon the first attrib is the number of points, 
     * This is not more object oriented because that would 
     * be over kill.
     */
    public FastVector m_ranges;

    public int m_attrib1;
    public int m_attrib2;
    
    public TreeClass m_set1;
    public TreeClass m_set2;

    public TreeClass m_parent;

    /** A string to uniquely identify this node. */
    public String m_identity;
    
    public double m_weight;
    
    public Instances m_training;
    
    /** Used instead of the standard leaf if one exists. */
    public Classifier m_classObject;

    /** Used on the instances while classifying if one exists. */
    public Filter m_filter;
    
    /**
     * Constructs a TreeClass node  with all the important information.
     * @param r A FastVector containing the shapes, null if it's a leaf node.
     * @param a1 The first attribute.
     * @param a2 The second attribute.
     * @param id The unique id number for this node.
     * @param w The weight of this node.
     * @param i The instances that make it to this node from the training data.
     * @exception Exception if can't use 'i' properly.
     */
    public TreeClass(FastVector r, int a1, int a2, int id, double w, 
		     Instances i, TreeClass p) throws Exception {
      m_set1 = null;
      m_set2 = null;
      m_ranges = r;
      m_classObject = null;
      m_filter = null;
      m_training = i;
      m_attrib1 = a1;
      m_attrib2 = a2;
      m_identity = "N" + String.valueOf(id);
      m_weight = w;
      m_parent = p;
      m_nextId++;
      if (m_ranges == null) {
	
	setLeaf();
	//this will fill the ranges array with the 
	//number of times each class type occurs for the instances.
	/*m_ranges = new FastVector(1);
	  m_ranges.addElement(new FastVector(i.numClasses() + 1));
	  FastVector tmp = (FastVector)m_ranges.elementAt(0);
	  tmp.addElement(new Double(0));
	  for (int noa = 0; noa < i.numClasses(); noa++) {
	  tmp.addElement(new Double(0));
	  }
	  for (int noa = 0; noa < i.numInstances(); noa++) {
	  tmp.setElementAt(new Double(((Double)tmp.elementAt
	  ((int)i.instance(noa).
	  classValue() + 1)).doubleValue() + 
	  i.instance(noa).weight()),
	  (int)i.instance(noa).classValue() + 1);  
	  //this gets the current class value and alters it and replaces it
	  }*/
      }     
    }
    
    /**
     * Call this to set an alternate classifier For this node.
     * @param c The alternative classifier to use.
     * @exception Exception if alternate classifier can't build classification.
     */
    public void setClassifier(Classifier c) throws Exception {
      m_classObject = c;
      m_classObject.buildClassifier(m_training);
    }
    
    /**
     * Call this to set this node with different information to what
     * it was created with.
     * @param a1 The first attribute.
     * @param a2 The second attribute.
     * @param ar The shapes at this node, null if leaf node, or 
     * alternate classifier.
     * @exception Exception if leaf node and cant't create leaf info.
     */
    public void setInfo(int at1, int at2, FastVector ar) throws Exception {
      m_classObject = null;
      m_filter = null;
      m_attrib1 = at1;
      m_attrib2 = at2;
      m_ranges = ar;
      
      //FastVector tmp;
      if (m_ranges == null) {
	setLeaf();
	/*
	//this will fill the ranges array with the number of times 
	//each class type occurs for the instances.
	  if (m_training != null) {
	    m_ranges = new FastVector(1);
	    m_ranges.addElement(new FastVector(m_training.numClasses() + 1));
	    tmp = (FastVector)m_ranges.elementAt(0);
	    tmp.addElement(new Double(0));
	    for (int noa = 0; noa < m_training.numClasses(); noa++) {
	      tmp.addElement(new Double(0));
	    }
	    for (int noa = 0; noa < m_training.numInstances(); noa++) {
	      tmp.setElementAt(new Double(((Double)tmp.elementAt
					   ((int)m_training.instance(noa).
					    classValue() + 1)).doubleValue() + 
					  m_training.instance(noa).weight()), 
			       (int)m_training.instance(noa).classValue() + 1);
	      //this gets the current class val and alters it and replaces it
	      }
	      }*/
      }
    }
    
    /**
     * This sets up the informtion about this node such as the s.d or the
     * number of each class.
     * @exception Exception if problem with training instances.
     */
    private void setLeaf() throws Exception {
      //this will fill the ranges array with the number of times 
      //each class type occurs for the instances.
      //System.out.println("ihere");
      if (m_training != null ) {
	
	if (m_training.classAttribute().isNominal()) {
	  FastVector tmp;
	  
	  //System.out.println("ehlpe");
	  m_ranges = new FastVector(1);
	  m_ranges.addElement(new FastVector(m_training.numClasses() + 1));
	  tmp = (FastVector)m_ranges.elementAt(0);
	  tmp.addElement(new Double(0));
	  for (int noa = 0; noa < m_training.numClasses(); noa++) {
	    tmp.addElement(new Double(0));
	  }
	  for (int noa = 0; noa < m_training.numInstances(); noa++) {
	    tmp.setElementAt(new Double(((Double)tmp.elementAt
					 ((int)m_training.instance(noa).
					  classValue() + 1)).doubleValue() + 
					m_training.instance(noa).weight()), 
			     (int)m_training.instance(noa).classValue() + 1);
	    //this gets the current class val and alters it and replaces it
	  }
	}
	else {
	  //then calc the standard deviation.
	  m_ranges = new FastVector(1);
	  double t1 = 0;
	  for (int noa = 0; noa < m_training.numInstances(); noa++) {
	    t1 += m_training.instance(noa).classValue();
	  }
	  
	  if (m_training.numInstances() != 0) {
	    t1 /= m_training.numInstances();
	  }
	  double t2 = 0;
	  for (int noa = 0; noa < m_training.numInstances(); noa++) {
	    t2 += Math.pow(m_training.instance(noa).classValue() - t1, 2);
	  }
	  FastVector tmp;
	  if (m_training.numInstances() != 0) {
	    t1 = Math.sqrt(t2 / m_training.numInstances());
	    m_ranges.addElement(new FastVector(2));
	    tmp = (FastVector)m_ranges.elementAt(0);
	    tmp.addElement(new Double(0));
	    tmp.addElement(new Double(t1));
	  }
	  else {
	    m_ranges.addElement(new FastVector(2));
	    tmp = (FastVector)m_ranges.elementAt(0);
	    tmp.addElement(new Double(0));
	    tmp.addElement(new Double(Double.NaN));
	  }

	}
      }
      
      
    }

    
    /**
     * This will recursively go through the tree and return inside the 
     * array the weightings of each of the class types
     * for this instance. Note that this function returns an otherwise 
     * unreferenced double array so there are no worry's about
     * making changes.
     *
     * @param i The instance to test
     * @return A double array containing the results.
     * @exception Exception if can't use instance i properly.
     */
    public double[] calcClassType(Instance i) throws Exception {
      //note that it will be the same calcs for both numeric and nominal
      //attrib types.
      //note the weightings for returning stuff will need to be modified 
      //to work properly but will do for now.
      double x = 0, y = 0;
      if (m_attrib1 >= 0) {
	x = i.value(m_attrib1);
      }
      if (m_attrib2 >= 0) {
	y = i.value(m_attrib2);
      }
      double[] rt;
      if (m_training.classAttribute().isNominal()) {
	rt = new double[m_training.numClasses()];
      }
      else {
	rt = new double[1];
      }

      
      FastVector tmp;
      if (m_classObject != null) {
	//then use the classifier.
	if (m_training.classAttribute().isNominal()) {
	  rt[(int)m_classObject.classifyInstance(i)] = 1;
	}
	else {
	  if (m_filter != null) {
	    m_filter.input(i);
	    rt[0] = m_classObject.classifyInstance(m_filter.output());
	  }
	  else {
	    rt[0] = m_classObject.classifyInstance(i);
	  }
	}
	//System.out.println("j48");
	return rt;
      }
      else if (((Double)((FastVector)m_ranges.elementAt(0)).
		elementAt(0)).intValue() == LEAF) {
	//System.out.println("leaf");
	//then this is a leaf
	//rt = new double[m_training.numClasses()];
	
	if (m_training.classAttribute().isNumeric()) {
	 
	  setLinear();
	  m_filter.input(i);
	  rt[0] = m_classObject.classifyInstance(m_filter.output());
	  return rt;
	}
	
	int totaler = 0;
	tmp = (FastVector)m_ranges.elementAt(0);
	for (int noa = 0; noa < m_training.numClasses();noa++) {
	  rt[noa] = ((Double)tmp.elementAt(noa + 1)).doubleValue();
	  totaler += rt[noa];
	}
	for (int noa = 0; noa < m_training.numClasses(); noa++) {
	  rt[noa] = rt[noa] / totaler;
	}
	return rt;
      }
      
      

      for (int noa = 0; noa < m_ranges.size(); noa++) {
	
	tmp = (FastVector)m_ranges.elementAt(noa);
	
	if (((Double)tmp.elementAt(0)).intValue() 
	    == VLINE && !i.isMissingValue(x)) {
	  
	}
	else if (((Double)tmp.elementAt(0)).intValue() 
		 == HLINE && !i.isMissingValue(y)) {
	  
	}
	else if (i.isMissingValue(x) || i.isMissingValue(y)) {
	  //System.out.println("miss");
	  //then go down both branches using their weights
	  rt = m_set1.calcClassType(i);
	  double[] tem = m_set2.calcClassType(i);
	  if (m_training.classAttribute().isNominal()) {
	    for (int nob = 0; nob < m_training.numClasses(); nob++) {
	      rt[nob] *= m_set1.m_weight;
	      rt[nob] += tem[nob] * m_set2.m_weight;
	    }
	  }
	  else {
	    rt[0] *= m_set1.m_weight;
	    rt[0] += tem[0] * m_set2.m_weight;
	  }
	  return rt;
	}
	else if (((Double)tmp.elementAt(0)).intValue() == RECTANGLE) {
	  //System.out.println("RECT");
	  if (x >= ((Double)tmp.elementAt(1)).doubleValue() && 
	      x <= ((Double)tmp.elementAt(3)).doubleValue() && 
	      y <= ((Double)tmp.elementAt(2)).doubleValue() && 
	      y >= ((Double)tmp.elementAt(4)).doubleValue()) {
	    //then falls inside the rectangle
	    //System.out.println("true");
	    rt = m_set1.calcClassType(i);
	    return rt;
	  }
	  
	}
	else if (((Double)tmp.elementAt(0)).intValue() == POLYGON) {
	  if (inPoly(tmp, x, y)) {
	    rt = m_set1.calcClassType(i);
	    return rt;
	  }
	}
	else if (((Double)tmp.elementAt(0)).intValue() == POLYLINE) {
	  if (inPolyline(tmp, x, y)) {
	    rt = m_set1.calcClassType(i);
	    return rt;
	  }
	}
      }
      //is outside the split
      if (m_set2 != null) {
	rt = m_set2.calcClassType(i);
      }
      return rt;
    }
    
    
    /**
     * This function gets called to set the node to use a linear regression
     * and attribute filter.
     * @exception If can't set a default linear egression model.
     */
    private void setLinear() throws Exception {
      //then set default behaviour for node.
      //set linear regression combined with attribute filter
      
      //find the attributes used for splitting.
      boolean[] attributeList = new boolean[m_training.numAttributes()];
      for (int noa = 0; noa < m_training.numAttributes(); noa++) {
	attributeList[noa] = false;
      }
      
      TreeClass temp = this;
      attributeList[m_training.classIndex()] = true;
      while (temp != null) {
	attributeList[temp.m_attrib1] = true;
	attributeList[temp.m_attrib2] = true;
	temp = temp.m_parent;
      }
      int classind = 0;
      
      
      //find the new class index
      for (int noa = 0; noa < m_training.classIndex(); noa++) {
	if (attributeList[noa]) {
	  classind++;
	}
      }
      //count how many attribs were used
      int count = 0;
      for (int noa = 0; noa < m_training.numAttributes(); noa++) {
	if (attributeList[noa]) {
	  count++;
	}
      }
      
      
      //fill an int array with the numbers of those attribs
      int[] attributeList2 = new int[count];
      count = 0;
      for (int noa = 0; noa < m_training.numAttributes(); noa++) {
	if (attributeList[noa]) {
	  attributeList2[count] = noa;
	  count++;
	}
      }
      
      m_filter = new Remove();
      ((Remove)m_filter).setInvertSelection(true);
      ((Remove)m_filter).setAttributeIndicesArray(attributeList2);
      m_filter.setInputFormat(m_training);
      
      Instances temp2 = Filter.useFilter(m_training, m_filter);
      temp2.setClassIndex(classind);
      m_classObject = new LinearRegression();
      m_classObject.buildClassifier(temp2);
      
      
      
    }
    
    
    /**
     * Call to find out if an instance is in a polyline.
     * @param ob The polyline to check.
     * @param x The value of attribute1 to check.
     * @param y The value of attribute2 to check.
     * @return True if inside, false if not.
     */
    private boolean inPolyline(FastVector ob, double x, double y) {
      //this works similar to the inPoly below except that
      //the first and last lines are treated as extending infinite 
      //in one direction and 
      //then infinitly in the x dirction their is a line that will 
      //normaly be infinite but
      //can be finite in one or both directions
      
      int countx = 0;
      double vecx, vecy;
      double change;
      double x1, y1, x2, y2;
      
      for (int noa = 1; noa < ob.size() - 4; noa+= 2) {
	y1 = ((Double)ob.elementAt(noa+1)).doubleValue();
	y2 = ((Double)ob.elementAt(noa+3)).doubleValue();
	x1 = ((Double)ob.elementAt(noa)).doubleValue();
	x2 = ((Double)ob.elementAt(noa+2)).doubleValue();
	vecy = y2 - y1;
	vecx = x2 - x1;
	if (noa == 1 && noa == ob.size() - 6) {
	  //then do special test first and last edge
	  if (vecy != 0) {
	    change = (y - y1) / vecy;
	    if (vecx * change + x1 >= x) {
	      //then intersection
	      countx++;
	    }
	  }
	  
	  
	}
	else if (noa == 1) {
	  if ((y < y2 && vecy > 0) || (y > y2 && vecy < 0)) {
	    //now just determine intersection or not
	    change = (y - y1) / vecy;
	    if (vecx * change + x1 >= x) {
	      //then intersection on horiz
	      countx++;
	    }
	  }
	}
	else if (noa == ob.size() - 6) {
	  //then do special test on last edge
	  if ((y <= y1 && vecy < 0) || (y >= y1 && vecy > 0)) {
	    change = (y - y1) / vecy;
	    if (vecx * change + x1 >= x) {
	      countx++;
	    }
	  }
	  
	}
	else if ((y1 <= y && y < y2) || (y2 < y && y <= y1)) {
	  //then continue tests.
	  if (vecy == 0) {
	    //then lines are parallel stop tests in 
	    //ofcourse it should never make it this far
	  }
	  else {
	    change = (y - y1) / vecy;
	    if (vecx * change + x1 >= x) {
	      //then intersects on horiz
	      countx++;
	    }
	  }
	}
	
      }
      
      //now check for intersection with the infinity line
      y1 = ((Double)ob.elementAt(ob.size() - 2)).doubleValue();
      y2 = ((Double)ob.elementAt(ob.size() - 1)).doubleValue();
      
      if (y1 > y2) {
	//then normal line
	if (y1 >= y && y > y2) {
	  countx++;
	}
      }
      else {
	//then the line segment is inverted
	if (y1 >= y || y > y2) {
	  countx++;
	}
      }
      
      
      if ((countx % 2) == 1) {
	return true;
      }
      else {
	return false;
      }
      
      
    }
    
    
    /** 
     * Call this to determine if an instance is in a polygon.
     * @param ob The polygon.
     * @param x The value of attribute 1.
     * @param y The value of attribute 2.
     * @return True if in polygon, false if not.
     */
    private boolean inPoly(FastVector ob, double x, double y) {
      int count = 0;
      double vecx, vecy;
      double change;
      double x1, y1, x2, y2;
      for (int noa = 1; noa < ob.size() - 2; noa += 2) {
	y1 = ((Double)ob.elementAt(noa+1)).doubleValue();
	y2 = ((Double)ob.elementAt(noa+3)).doubleValue();
	if ((y1 <= y && y < y2) || (y2 < y && y <= y1)) {
	  //then continue tests.
	  vecy = y2 - y1;
	  if (vecy == 0) {
	    //then lines are parallel stop tests for this line
	  }
	  else {
	    x1 = ((Double)ob.elementAt(noa)).doubleValue();
	    x2 = ((Double)ob.elementAt(noa+2)).doubleValue();
	    vecx = x2 - x1;
	    change = (y - y1) / vecy;
	    if (vecx * change + x1 >= x) {
	      //then add to count as an intersected line
	      count++;
	    }
	  }
	  
	}
      }
      if ((count % 2) == 1) {
	//then lies inside polygon
	//System.out.println("in");
	return true;
      }
      else {
	//System.out.println("out");
	return false;
      }
      //System.out.println("WHAT?!?!?!?!!?!??!?!");
      //return false;
    }
    

    /**
     * Goes through the tree structure recursively and returns the node that
     * has the id.
     * @param id The node to find.
     * @return The node that matches the id.
     */
    public TreeClass getNode(String id) {
      //returns the treeclass object with the particular ident
      if (id.equals(m_identity)) {
	return this;
      }
      
      if (m_set1 != null) {
	TreeClass tmp = m_set1.getNode(id);
	if (tmp != null) {
	  return tmp;
	}
      }
      if (m_set2 != null) {
	TreeClass tmp = m_set2.getNode(id);
	if (tmp != null) {
	  return tmp;
	}
      }
      return null;
    }
    
    
    /**
     * Returns a string containing a bit of information about this node, in 
     * alternate form.
     * @param s The string buffer to fill.
     * @exception Exception if can't create label.
     */
    public void getAlternateLabel(StringBuffer s) throws Exception {
      
      //StringBuffer s = new StringBuffer();
      
      FastVector tmp = (FastVector)m_ranges.elementAt(0);
      
      if (m_classObject != null && m_training.classAttribute().isNominal()) {
	s.append("Classified by " + m_classObject.getClass().getName());
      }
      else if (((Double)tmp.elementAt(0)).intValue() == LEAF) {
	if (m_training.classAttribute().isNominal()) {
	  double high = -1000;
	  int num = 0;
	  double count = 0;
	  for (int noa = 0; noa < m_training.classAttribute().numValues();
	       noa++) {
	    if (((Double)tmp.elementAt(noa + 1)).doubleValue() > high) {
	      high = ((Double)tmp.elementAt(noa + 1)).doubleValue();
	      num  = noa + 1;
	    }
	    count += ((Double)tmp.elementAt(noa + 1)).doubleValue();
	  }
	  s.append(m_training.classAttribute().value(num-1) + "(" + count);
	  if (count > high) {
	    s.append("/" + (count - high));
	  }
	  s.append(")");
	}
	else {
	  if (m_classObject == null 
	      && ((Double)tmp.elementAt(0)).intValue() == LEAF) {
	    setLinear();
	  }
	  s.append("Standard Deviation = " 
		   + Utils.doubleToString(((Double)tmp.elementAt(1))
					  .doubleValue(), 6));
	  
	}
      }
      else {
	s.append("Split on ");
	s.append(m_training.attribute(m_attrib1).name() + " AND ");
	s.append(m_training.attribute(m_attrib2).name());
	
	
      }
      
      //return s.toString();
    }
    

    
    /**
     * Returns a string containing a bit of information about this node.
     * @param s The stringbuffer to fill.
     * @exception Exception if can't create label.
     */
    public void getLabel(StringBuffer s) throws Exception {
      //for now just return identity
      //StringBuffer s = new StringBuffer();
      
      FastVector tmp = (FastVector)m_ranges.elementAt(0);
      
      
      if (m_classObject != null && m_training.classAttribute().isNominal()) {
	s.append("Classified by\\n" + m_classObject.getClass().getName());
      }
      else if (((Double)tmp.elementAt(0)).intValue() == LEAF) {
	
	if (m_training.classAttribute().isNominal()) {
	  boolean first = true;
	  for (int noa = 0; noa < m_training.classAttribute().numValues(); 
	       noa++) {
	    if (((Double)tmp.elementAt(noa + 1)).doubleValue() > 0) {
	      if (first)
		{
		  s.append("[" + m_training.classAttribute().value(noa));
		  first = false;
		}
	      else
		{
		  s.append("\\n[" + m_training.classAttribute().value(noa));
		}
	      s.append(", " + ((Double)tmp.elementAt(noa + 1)).doubleValue() 
		       + "]");
	    }      
	  }
	}
	else {
	  if (m_classObject == null 
	      && ((Double)tmp.elementAt(0)).intValue() == LEAF) {
	    setLinear();
	  }
	  s.append("Standard Deviation = " 
		   + Utils.doubleToString(((Double)tmp.elementAt(1))
		   .doubleValue(), 6));
	}
      }
      else {
	s.append("Split on\\n");
	s.append(m_training.attribute(m_attrib1).name() + " AND\\n");
	s.append(m_training.attribute(m_attrib2).name());
      }
      //return s.toString();
    }

    /**
     * Converts The tree structure to a dotty string.
     * @param t The stringbuffer to fill with the dotty structure.
     * @exception Exception if can't convert structure to dotty.
     */
    public void toDotty(StringBuffer t) throws Exception {
      //this will recursively create all the dotty info for the structure
      t.append(m_identity + " [label=\"");
      getLabel(t);
      t.append("\" ");
      //System.out.println(((Double)((FastVector)ranges.elementAt(0)).
      //elementAt(0)).intValue() + " A num ");
      if (((Double)((FastVector)m_ranges.elementAt(0)).elementAt(0)).intValue()
	  == LEAF) {
	t.append("shape=box ");
      }
      else {
	t.append("shape=ellipse ");
      }
      t.append("style=filled color=gray95]\n");
      
      if (m_set1 != null) {
	t.append(m_identity + "->");
	t.append(m_set1.m_identity + " [label=\"True\"]\n");//the edge for 
	//the left
	m_set1.toDotty(t);
      }
      if (m_set2 != null) {
	t.append(m_identity + "->");
	t.append(m_set2.m_identity + " [label=\"False\"]\n"); //the edge for 
	//the 
	//right
	m_set2.toDotty(t);
      }
      
    }
    
    /**
     * This will append the class Object in the tree to the string buffer.
     * @param t The stringbuffer.
     */
    public void objectStrings(StringBuffer t) {
      
      if (m_classObject != null) {
	t.append("\n\n" + m_identity +" {\n" + m_classObject.toString()+"\n}");
      }
      if (m_set1 != null) {
	m_set1.objectStrings(t);
      }
      if (m_set2 != null) {
	m_set2.objectStrings(t);
      }
    }
    
    /**
     * Converts the tree structure to a string. for people to read.
     * @param l How deep this node is in the tree.
     * @param t The stringbuffer to fill with the string.
     * @exception Exception if can't convert th string.
     */
    public void toString(int l, StringBuffer t) throws Exception {
      

      
      if (((Double)((FastVector)m_ranges.elementAt(0)).elementAt(0)).intValue()
	  == LEAF) {
	t.append(": " + m_identity + " ");
	getAlternateLabel(t);
      }
      if (m_set1 != null) {
	t.append("\n");
	for (int noa = 0; noa < l; noa++) {
	  t.append("|   ");
	  
	}
	getAlternateLabel(t);
	t.append(" (In Set)");
	m_set1.toString(l+1, t);
      }
      if (m_set2 != null) {
	t.append("\n");
	for (int noa = 0; noa < l; noa++) {
	  t.append("|   ");
	}
	getAlternateLabel(t);
	t.append(" (Not in Set)");
	m_set2.toString(l+1, t);
      }
      //return t.toString();
    }
    
  }
  
  
  
}
