package yi.stat.hist;

import java.util.*;

/**
 * A simple histogram with integer number of bins and integer value
 * @author Yi Li
 * @version 1.0
 */
public class SimpleHistogram {

  /**
   * The number of bins.
   */
  int m_binNum;
  /**
   * Total data in the histogram
   */
  int m_total;
  /**
   * The histogram
   */
  int[] m_histogram;
  /**
   * The minimum value of the data.  It should be mapped to bin zero
   */
  int m_minValue;

  /**
   * Create a histogram with bin_num empty bins.
   * @param bin_num
   */
  public SimpleHistogram(int bin_num) {
    this(0, bin_num - 1);
  }

  /**
   * Create a histogram with the min value and max value specified
   * @param min the minimum value of the data (inclusive)
   * @param max the maximum value of the data (inclusive)
   */
  public SimpleHistogram(int min, int max) {
    m_minValue = min;
    m_binNum = max - min + 1;
    m_total  = 0;
    m_histogram = new int[m_binNum];
  }

  /**
   * Create a histogram with the data specified.  The minimum and maximum data
   * value is decided by the current data.  If later, bigger or smaller data
   * is added to the histogram, they will be put into the last or the first bin.
   * So this constructor is suitable for the situation that you have all the
   * data already
   * @param data the data to be put into the histogram
   */
  public SimpleHistogram(int[] data) {
    int min = yi.stat.StatUtil.minimum(data);
    int max = yi.stat.StatUtil.maximum(data);
    m_minValue = min;
    m_binNum = max - min + 1;
    m_total  = 0;
    m_histogram = new int[m_binNum];
    putToBin(data);
  }

  /**
   * Put data value into the histogram.  If the value is greater than the
   * pre-defined maximum, it will be put into the last bin; if it is less than
   * the pre-define minmum, it will be put into bin #0.
   * @param value the value of the data to be put into the histogram
   */
  public void putToBin(int value){
    int bin_index = value - m_minValue;
    if (bin_index >= m_binNum) bin_index = m_binNum - 1;
    else if (bin_index < 0) bin_index = 0;
    m_histogram[bin_index] ++;
    m_total ++;
  }

  /**
   * Put data values into the histogram.  If a value is greater than the
   * pre-defined maximum, it will be put into the last bin; if it is less than
   * the pre-define minmum, it will be put into bin #0.
   * @param values the values of the data to be put into the histogram
   */
  public void putToBin(int[] values){
    for (int i=0; i<values.length; i++) putToBin(values[i]);
  }

  /**
   * Get the value in the bin specified by bin_index.
   * @param bin_index the bin index
   * @return the value in the bin
   * @throws OutOfHistRangeException
  */
  public int getBin(int bin_index) throws OutOfHistRangeException{
    if (bin_index >= m_binNum || bin_index < 0) {
      throw new OutOfHistRangeException("Out of Histogram Range: "
        + "bin# = " + m_binNum + "; requested = " + bin_index);
    }
    return m_histogram[bin_index];
  }

  /**
   * Return the total data number in the histogram
   * @return the number
   */
  public int getTotal() {
    return m_total;
  }

  /**
   * Compare the histogram with the given histogram.  For each bin, assign it
   * the maximum value of the current value and corresponding value in the given
   * histogram.  For example, if the current histogram = {3, 5, 7} and the given
   * histogram is {9, 5, 1}, after pickUpMaximum, the histogram will be {9, 5, 7}
   * @param h the histogram to be compare
   * @return the histogram itself
   */
  public SimpleHistogram pickUpMaximum(SimpleHistogram h) {
    for (int i=0; i<m_binNum; i++) {
      if (m_histogram[i] < h.m_histogram[i]) {
        m_total += (h.m_histogram[i] - m_histogram[i]);
        m_histogram[i] = h.m_histogram[i];
      }
    }
    return this;
  }

  /**
   * Examine each histogram bin, return the list contains the indices of those
   * bins whose value is not less thanthan the given threshould
   * @param threshold the threshold
   * @return the list contains the indices of those bins whose value is not less
   *         than the given threshould
   */
  public List passThreshold(int threshold) {
    LinkedList passed = new LinkedList();
    for (int i=0; i<m_binNum; i++) {
      if (m_histogram[i] >= threshold) passed.add(new Integer(i));
    }
    return passed;
  }

  /**
   * Return the tag values of all the bins.  Eg. the minimum value = -10, the
   * histogram length = 20, then return {-10, -9, ... , 9}
   * @return the tag values of all the bins
   */
  public int[] getBinTags() {
    int[] tags = new int[m_binNum];
    for (int i=0; i<m_binNum; i++) tags[i] = m_minValue + i;
    return tags;
  }

  /**
   * Return the histogram
   * @return the histogram
   */
  public int[] getHistogram() {
    return m_histogram;
  }

  /**
   * Return the number of bins
   * @return the number of bins
   */
  public int getBinNum() {
    return m_binNum;
  }

  /**
   * Return the minimum value of the data
   * @return the minimum value of the data
   */
  public int getMinValue() {
    return m_minValue;
  }

  public void output(String file_name_root) {
    int[][] hist_data = new int[][] {
        getBinTags(), m_histogram
    };

    hist_data = yi.util.data.Conversion.swapXandY(hist_data, m_binNum, 2);
    yi.util.io.OutputResult.print(file_name_root, hist_data, 2, m_binNum);
  }

  public String toString() {
    StringBuffer str = new StringBuffer();
    int[] tags = getBinTags();
    for (int i=0;i<tags.length; i++) {
      if (i != 0) str.append(",");
      str.append("[" + tags[i] + "]=" + m_histogram[i]);
    }
    return str.toString();
  }

  /**
   * Return the sum of difference of each histogram element
   * @param h
   * @return
   */
  public int difference(SimpleHistogram h) {
    int diff = 0;
    for (int i=0; i<m_binNum; i++) {
      diff += Math.abs(h.m_histogram[i] - m_histogram[i]);
    }
    return diff;
  }

  /**
   * Return the value the max count.  If the number of bin is 0 then 0 will be
   * returned.  The index returned is the real value (not the bin index, which
   * is adjusted by the min_value).  If there are values have the same count,
   * then the one with smallest index will be returned.
   * @return
   */
  public int getValueWithMaxCount() {
    if (m_binNum <= 0) return 0;
    int max = 0;
    int max_index = -1;
    for (int i=0; i<m_binNum; i++) {
      if (m_histogram[i] > max) {
        max = m_histogram[i];
        max_index = i;
      }
    }
    return max_index+m_minValue ;
  }

  /**
   * This function is similar to getBin except that the index used in getBin
   * is 0 based (values are adjusted by min value).
   * @param value
   * @return
   * @throws OutOfHistRangeException
   */
  public int getCount(int value) throws OutOfHistRangeException{
    return getBin(value - m_minValue);
  }

  /**
   * This function is similar to setBin except that the index used in getBin
   * is 0 based (values are adjusted by min value).
   * @param value
   * @throws OutOfHistRangeException
   */
  public void setCount(int value, int count) throws OutOfHistRangeException{
    setBin(value - m_minValue, count);
  }

  public void setBin(int bin_index, int count) throws OutOfHistRangeException{
    if (bin_index >= m_binNum || bin_index < 0) {
      throw new OutOfHistRangeException("Out of Histogram Range: "
        + "bin# = " + m_binNum + "; requested = " + bin_index);
    }
    int old = m_histogram[bin_index];
    m_histogram[bin_index] = count;
    m_total += (count - old);
  }



  /**
   * Add the two histogram together.  The merged histogram is constructed and
   * returned.  The min_value is the min of those of that two histogram
   * @param h
   * @return
   */
  public SimpleHistogram add(SimpleHistogram h) {
    int min = Math.min(m_minValue, h.m_minValue);
    int max = Math.max(m_minValue + m_binNum -1, h.m_minValue + h.m_binNum - 1);
    SimpleHistogram merged = new SimpleHistogram(min, max);
    try {
      for (int i = 0; i < m_binNum; i++) {
        merged.setCount(i + m_minValue, m_histogram[i]);
      }
      for (int i = 0; i < h.m_binNum; i++) {
        merged.setCount(i + h.m_minValue, h.m_histogram[i]);
      }
    } catch (OutOfHistRangeException e) {
      e.printStackTrace();
    }
    return this;
  }


}