/*
 *   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 3 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, see <http://www.gnu.org/licenses/>.
 */

/*
 * TreePerformanceStats.java
 * Copyright (C) 2007-2012 University of Waikato, Hamilton, New Zealand
 */

package weka.core.neighboursearch;

import java.util.Enumeration;
import java.util.Vector;

import weka.core.RevisionUtils;

/**
 * The class that measures the performance of a tree based 
 * nearest neighbour search algorithm.
 * 
 * @author Ashraf M. Kibriya (amk14[at-the-rate]cs[dot]waikato[dot]ac[dot]nz)
 * @version $Revision: 8034 $
 */
public class TreePerformanceStats
  extends PerformanceStats {
  
  /** for serialization. */
  private static final long serialVersionUID = -6637636693340810373L;
  
  // Variables for leaves
  /** The min and max number leaf nodes looked 
   * for a query by the tree based NNS algorithm. */
  protected int m_MinLeaves, m_MaxLeaves;
  
  /** The sum of leaf nodes looked 
   * at for all the queries. 
   */
  protected int m_SumLeaves;
  /** The squared sum of leaf nodes looked 
   * at for all the queries. 
   */
  protected int m_SumSqLeaves;
  /** The number of leaf nodes looked at
   * for the current/last query.
   */
  protected int m_LeafCount;
  
  // Variables for internal nodes
  /** The min and max number internal nodes looked 
   * for a query by the tree based NNS algorithm. */
  protected int m_MinIntNodes, m_MaxIntNodes;
  /** The sum of internal nodes looked 
   * at for all the queries. 
   */
  protected int m_SumIntNodes;
  /** The squared sum of internal nodes looked 
   * at for all the queries. 
   */
  protected int m_SumSqIntNodes;
  /** The number of internal nodes looked at
   * for the current/last query.
   */
  protected int m_IntNodeCount;
  
  /**
   * Default constructor.
   */
  public TreePerformanceStats() { 
    reset(); 
  }
  
  /**
   * Resets all internal fields/counters.
   */
  public void reset() {
    super.reset();
    //initializing leaf variables
    m_SumLeaves = m_SumSqLeaves = m_LeafCount = 0;
    m_MinLeaves = Integer.MAX_VALUE;
    m_MaxLeaves = Integer.MIN_VALUE;
    //initializing internal variables
    m_SumIntNodes = m_SumSqIntNodes = m_IntNodeCount = 0;
    m_MinIntNodes = Integer.MAX_VALUE;
    m_MaxIntNodes = Integer.MIN_VALUE;
  }
  
  /**
   * Signals start of the nearest neighbour search.
   * Initializes the stats object.
   */
  public void searchStart() {
    super.searchStart();
    m_LeafCount = 0;
    m_IntNodeCount = 0;
  }
  
  /**
   * Signals end of the nearest neighbour search.
   * Calculates the statistics for the search.
   */
  public void searchFinish() {
    super.searchFinish();
    //updating stats for leaf nodes
    m_SumLeaves += m_LeafCount;  m_SumSqLeaves += m_LeafCount*m_LeafCount;
    if (m_LeafCount < m_MinLeaves) m_MinLeaves = m_LeafCount;
    if (m_LeafCount > m_MaxLeaves) m_MaxLeaves = m_LeafCount;
    //updating stats for internal nodes
    m_SumIntNodes += m_IntNodeCount;  m_SumSqIntNodes += m_IntNodeCount*m_IntNodeCount;
    if (m_IntNodeCount < m_MinIntNodes) m_MinIntNodes = m_IntNodeCount;
    if (m_IntNodeCount > m_MaxIntNodes) m_MaxIntNodes = m_IntNodeCount;
  }
  
  /**
   * Increments the leaf count.
   */
  public void incrLeafCount() {
    m_LeafCount++;
  }

  /**
   * Increments the internal node count.
   */
  public void incrIntNodeCount() {
    m_IntNodeCount++;
  }

  // Getter functions for leaves
  
  /**
   * Returns the total number of leaves visited.
   * 
   * @return The total number.
   */
  public int getTotalLeavesVisited() {
    return m_SumLeaves;
  }
  
  /**
   * Returns the mean of number of leaves visited.
   * 
   * @return The mean number of leaves visited.
   */
  public double getMeanLeavesVisited() {
    return m_SumLeaves/(double)m_NumQueries;
  }
  
  /**
   * Returns the standard deviation of leaves visited.
   * 
   * @return The standard deviation of leaves visited.
   */
  public double getStdDevLeavesVisited() {
    return Math.sqrt((m_SumSqLeaves - (m_SumLeaves*m_SumLeaves)/(double)m_NumQueries)/(m_NumQueries-1));
  }
  
  /**
   * Returns the minimum number of leaves visited.
   * 
   * @return The minimum number of leaves visited.
   */
  public int getMinLeavesVisited() {
    return m_MinLeaves;
  }
  
  /**
   * Returns the maximum number of leaves visited.
   * 
   * @return The maximum number of leaves visited.
   */
  public int getMaxLeavesVisited() {
    return m_MaxLeaves;
  }
  
  // Getter functions for internal nodes
  
  /**
   * Returns the total number of internal nodes visited.
   * 
   * @return The total number of internal nodes visited.
   */
  public int getTotalIntNodesVisited() {
    return m_SumIntNodes;
  }
  
  /**
   * Returns the mean of internal nodes visited.
   * 
   * @return The mean number of internal nodes 
   * visited.
   */
  public double getMeanIntNodesVisited() {
    return m_SumIntNodes/(double)m_NumQueries;
  }
  
  /**
   * Returns the standard deviation of internal nodes visited.
   * 
   * @return The standard deviation of internal nodes visited.
   */
  public double getStdDevIntNodesVisited() {
    return Math.sqrt((m_SumSqIntNodes - (m_SumIntNodes*m_SumIntNodes)/(double)m_NumQueries)/(m_NumQueries-1));
  }
  
  /**
   * Returns the minimum of internal nodes visited.
   * 
   * @return The minimum of internal nodes visited. 
   */
  public int getMinIntNodesVisited() {
    return m_MinIntNodes;
  }
  
  /**
   * returns the maximum of internal nodes visited.
   * 
   * @return The maximum of internal nodes visited.
   */
  public int getMaxIntNodesVisited() {
    return m_MaxIntNodes;
  }
  
  /**
   * Returns an enumeration of the additional measure names.
   * 
   * @return An enumeration of the measure names.
   */
  public Enumeration enumerateMeasures() {
    Vector<String> newVector = new Vector<String>();
    
    Enumeration en = super.enumerateMeasures();
    while(en.hasMoreElements())
      newVector.addElement((String)en.nextElement());
    
    newVector.addElement("measureTotal_nodes_visited");
    newVector.addElement("measureMean_nodes_visited");
    newVector.addElement("measureStdDev_nodes_visited");
    newVector.addElement("measureMin_nodes_visited");
    newVector.addElement("measureMax_nodes_visited");
    //coord stats
    newVector.addElement("measureTotal_leaves_visited");
    newVector.addElement("measureMean_leaves_visited");
    newVector.addElement("measureStdDev_leaves_visited");
    newVector.addElement("measureMin_leaves_visited");
    newVector.addElement("measureMax_leaves_visited");
    
    return newVector.elements();
  }
  
  /**
   * Returns the value of the named measure.
   * 
   * @param additionalMeasureName The name of the measure to query for 
   * its value.
   * @return The value of the named measure.
   * @throws IllegalArgumentException If the named measure is not 
   * supported.
   */
  public double getMeasure(String additionalMeasureName) throws IllegalArgumentException {
    if (additionalMeasureName.compareToIgnoreCase("measureTotal_nodes_visited") == 0) {
      return (double) getTotalIntNodesVisited();
    } else if (additionalMeasureName.compareToIgnoreCase("measureMean_nodes_visited") == 0) {
      return (double) getMeanIntNodesVisited();
    } else if (additionalMeasureName.compareToIgnoreCase("measureStdDev_nodes_visited") == 0) {
      return (double) getStdDevIntNodesVisited();
    } else if (additionalMeasureName.compareToIgnoreCase("measureMin_nodes_visited") == 0) {
      return (double) getMinIntNodesVisited();
    } else if (additionalMeasureName.compareToIgnoreCase("measureMax_nodes_visited") == 0) {
      return (double) getMaxIntNodesVisited();
    }
    //coord stats
    else if (additionalMeasureName.compareToIgnoreCase("measureTotal_leaves_visited") == 0) {
      return (double) getTotalLeavesVisited();
    } else if (additionalMeasureName.compareToIgnoreCase("measureMean_leaves_visited") == 0) {
      return (double) getMeanLeavesVisited();
    } else if (additionalMeasureName.compareToIgnoreCase("measureStdDev_leaves_visited") == 0) {
      return (double) getStdDevLeavesVisited();
    } else if (additionalMeasureName.compareToIgnoreCase("measureMin_leaves_visited") == 0) {
      return (double) getMinLeavesVisited();
    } else if (additionalMeasureName.compareToIgnoreCase("measureMax_leaves_visited") == 0) {
      return (double) getMaxLeavesVisited();
    } else {
      return super.getMeasure(additionalMeasureName);
    }
  }
  
  /**
   * Returns a string representation of the statistics.
   * 
   * @return The statistics as string.
   */
  public String getStats() {
    StringBuffer buf = new StringBuffer(super.getStats());
    
    buf.append("leaves:    "+getMinLeavesVisited()+", "+getMaxLeavesVisited()+
	       ","+getTotalLeavesVisited()+","+getMeanLeavesVisited()+", "+getStdDevLeavesVisited()+"\n");
    buf.append("Int nodes: "+getMinIntNodesVisited()+", "+getMaxIntNodesVisited()+
	       ","+getTotalIntNodesVisited()+","+getMeanIntNodesVisited()+", "+getStdDevIntNodesVisited()+"\n");

    return buf.toString();
  }
  
  /**
   * Returns the revision string.
   * 
   * @return		the revision
   */
  public String getRevision() {
    return RevisionUtils.extract("$Revision: 8034 $");
  }
}
