package yi.stat.hist;

/**
 * DistributedWeightsHistogram distributes the weight of a sample to its closest
 * bins according to its distance to them.  For example, if the bins are
 * centered at 0, 5 , 10, ..., then a sample of 2 will contribute 2/5 to bin of
 * "0" and 3/5 to bin of "5".  A sample of 2.5 will contribute 1/2 to bin of
 * "0" and 1/2 to bin of "5".
 */

public class DistributedWeightsHistogram {

  double m_minCenter;
  double m_binWidth;
  int m_numBins;
  boolean m_isCircular;
  /**
   * to save the histogram
   */
  WeightedHistogram m_hist;

  /**
   * Initialize a DistributedWeightsHistogram with the first bin having the
   * center of "min_center".  A circular bin is like the histogram of
   * angles where 0 degree is the same as 180 degree.
   *
   * @param min_center the center of the first bin
   * @param bin_width the width of the bins
   * @param num_bins the number of bins
   * @param is_circular whether the histogram is circular or not
   */
  public DistributedWeightsHistogram(double min_center, double bin_width,
                                     int num_bins, boolean is_circular) {
    m_minCenter = min_center;
    m_binWidth = bin_width;
    m_numBins = num_bins;
    m_isCircular = is_circular;
    m_hist = new WeightedHistogram(m_numBins);
  }

  public WeightedHistogram getHistogram() {
    return m_hist;
  }

  /**
   * Put a data into the bin with distributed weights.  The procedure will
   * first find its closest left bin and its closest right bin.  Then it put
   * weights into that two bins according to the distance
   * @param data
   * @param weight
   */
  public void put(double data, double weight) {
    // find the closest bin on its left
    int left_index = (int)Math.floor((data - m_minCenter) / m_binWidth);
    double left_center = m_minCenter + left_index * m_binWidth;
    // distance from the left bin
    double d_left = data - left_center;
    // weight distributed to left
    double w_left = 1 - d_left / m_binWidth;

    // weight distributed to right
    int right_index = left_index + 1;
    double w_right = 1 - w_left;

    // if it is circular histogram, make it circular
    if (m_isCircular && (left_index < 0 || left_index >= m_numBins)) {
      left_index %= m_numBins;
    }
    if (m_isCircular && (right_index < 0 || right_index >= m_numBins)) {
      right_index %= m_numBins;
    }

    // put the weights into the histogram
    if (left_index >=0 && left_index < m_numBins) {
      m_hist.putToBin(left_index, w_left * weight);
    }
    if (right_index >=0 && right_index < m_numBins * weight) {
      m_hist.putToBin(right_index, w_right * weight);
    }
  }

  /**
   * See details in put(double data, double weight).  In here, use default
   * weight of 1.0
   * @param data
   */
  public void put(double data) {
    put(data, 1.0);
  }

  public String toString() {
    return m_hist.toString();
  }

  public static void main(String[] args) {
    DistributedWeightsHistogram h = new DistributedWeightsHistogram(0, 45, 4, true);

    h.put(0);
    System.out.println(h);

    h.put(45);
    System.out.println(h);

    h.put(90);
    System.out.println(h);

    h.put(135);
    System.out.println(h);

    h.put(180);
    System.out.println(h);

    h.put(20);
    System.out.println(h);

    h.put(160);
    System.out.println(h);
  }
}