package yi.stat.hist;

import java.util.*;
import yi.util.log.*;

public class Hist{
  public final static String COUNT = "COUNT";
  public final static String MEAN  = "MEAN";
  public final static String STERR = "STERR";
  public final static String MAX   = "MAX";
  public final static String MIN   = "MIN";

  public final static double UNKNOWN = Double.NaN;

  private static Hist m_singleton = null;

  public int LOG_LEVEL = 1;

  private Hist() {
    super();
  }

  public static Hist getSingleton() {
    if (m_singleton == null) m_singleton = new Hist();
    return m_singleton;
  }

  public double[] getHist(double[] keys, double[] values,
                           String stat,
                           double min, double bsize, int bnum) {
    // histogram length
    double[] h = new double[bnum];

    ArrayList[] bins = new ArrayList[bnum];
    for (int i=0; i<bnum; i++) bins[i] = new ArrayList();

    // begin to seperate a2 according to its key
    for (int i=0; i<values.length; i++) {
      int k = (int)((keys[i] - min) / bsize);
      if (k < 0) k = 0;
      if (k >= bnum) k = bnum - 1;
      bins[k].add(new Double(values[i]));
    }

    // calculate the statistical information for every bin
    for (int i=0; i<bnum; i++) {
      double[] temp = new double[bins[i].size()];
      for (int j=0; j<bins[i].size(); j++) temp[j] = ((Double)bins[i].get(j)).doubleValue();
      h[i] = getStat(temp, stat);
    }
    return h;
  }

  public double[] getHist(double[] keys, double[] values,
                           String stat,
                           double min, double bsize, int bnum,
                           boolean[] valid) {
    // histogram length
    double[] h = new double[bnum];

    ArrayList[] bins = new ArrayList[bnum];
    for (int i=0; i<bnum; i++) bins[i] = new ArrayList();

    // begin to seperate a2 according to its key
    for (int i=0; i<values.length; i++) {
      if (!valid[i]) continue;
      int k = (int)((keys[i] - min) / bsize);
      if (k < 0) k = 0;
      if (k >= bnum) k = bnum - 1;
      bins[k].add(new Double(values[i]));
    }

    // calculate the statistical information for every bin
    for (int i=0; i<bnum; i++) {
      double[] temp = new double[bins[i].size()];
      for (int j=0; j<bins[i].size(); j++) temp[j] = ((Double)bins[i].get(j)).doubleValue();
      h[i] = getStat(temp, stat);
    }
    return h;
  }

  public static double[] makeHist(double[] keys, double[] values,
                           String stat,
                           double min, double bsize, int bnum, boolean[] valid) {
    return Hist.getSingleton().getHist(keys, values, stat, min, bsize, bnum, valid);
  }

  public double[] getHist(double key, double min, double bsize, int bnum) {
    // histogram length
    double[] h = new double[bnum];

    int k = (int)((key - min) / bsize);
    if (k < 0) k = 0;
    if (k >= bnum) k = bnum - 1;
    h[k]++;
    return h;
  }

  public static double[] makeHist(double[] keys, double[] values,
                           String stat,
                           double min, double bsize, int bnum) {
    return Hist.getSingleton().getHist(keys, values, stat, min, bsize, bnum);
  }

  public double getStat(double[] data, String stat) {
    if (data == null) data = new double[0];

    if (stat.compareTo(COUNT) == 0) return data.length;

    // for statistics information, data length can't be zero
    if (data.length == 0) return UNKNOWN;

    if (stat.compareTo(MEAN) == 0) {
      double sum = 0;
      for (int i=0; i<data.length; i++) sum += data[i];
      return sum/data.length;
    }
    if (stat.compareTo(STERR) == 0) {
      if (data.length <= 1) return 0;
      double sum = 0;
      for (int i=0; i<data.length; i++) sum += data[i];
      double mean = sum/data.length;
      double squaredErr = 0;
      for (int i=0; i<data.length; i++) {
        double err = data[i]-mean;
        squaredErr += err * err;
      }
      return Math.sqrt(squaredErr/(data.length - 1));
    }
    if (stat.compareTo(MAX) == 0) {
      double max = data[0];
      for (int i=1; i<data.length; i++) {
        if (data[i] > max) max = data[i];
      }
      return max;
    }
    if (stat.compareTo(MIN) == 0) {
      double min = data[0];
      for (int i=1; i<data.length; i++) {
        if (data[i] < min) min = data[i];
      }
      return min;
    }
    // unknown statistics
    LogHandler.log(this, LOG_LEVEL, 1, "Unknown Statistics: " + stat);
    return 0;
  }
}