package yi.stat.hist;

/**
 * Given a value and the histogram description, return the corresponding bin
 * index of that value.  This method is linear based.  The first bin
 * (bin_index = 0) corresponding to values fallen into
 * [base_value, base_value + bin_width) if left_inclusive is set to true;
 * or (base_value, base_value + bin_width] if right_inclusive is set to true.
 * The second bin corresponding to
 * [base_value + bin_width, base_value + 2 * bin_width) or
 * (base_value + bin_width, base_value + 2 * bin_width].
 * If the data <= based_value, it will be set to bin index 0.
 */
public class LinearQuantizer implements Quantizer{

  /**
   * The base value of the quantizer.  Any value below m_baseValue will be
   * quantized as 0.
   */
  double m_baseValue;

  /**
   * The width of each bin
   */
  double m_binWidth;

  /**
   * Bin number
   */
  int m_binNum;

  /**
   * Is the left value inclusive in ranges.
   */
  boolean m_leftInclusive;

  /**
   * Is the data circular (like for orientation/direction)
   */
  boolean m_isCircular;

  /**
   * Max possible value.  This is a cached value and it can be calculated from
   * m_maxValue = m_baseValue + (m_binWidth * m_binNum)
   */
  double m_maxValue;

  /**
   * The value range.  This is a cached value and it can be calculated from
   * m_valueRange = m_maxValue - m_baseValue
   */
  double m_valueRange;

  public LinearQuantizer(double base_value, double bin_width, int bin_num, boolean left_inclusive, boolean is_circular) {
    m_baseValue = base_value;
    m_binWidth = bin_width;
    m_binNum = bin_num;
    m_leftInclusive = left_inclusive;
    m_isCircular = is_circular;
    m_maxValue = m_baseValue + (m_binWidth * m_binNum);
    m_valueRange = m_maxValue - m_baseValue;
  }

  public LinearQuantizer(double base_value, double bin_width, int bin_num, boolean left_inclusive) {
    this(base_value, bin_width, bin_num, left_inclusive, false);
  }

  /**
   * Given a value and the histogram description, return the corresponding bin
   * index of that value.  This method is linear based.  The first bin
   * (bin_index = 0) corresponding to values fallen into
   * [base_value, base_value + bin_width) if left_inclusive is set to true;
   * or (base_value, base_value + bin_width] if right_inclusive is set to true.
   * The second bin corresponding to
   * [base_value + bin_width, base_value + 2 * bin_width) or
   * (base_value + bin_width, base_value + 2 * bin_width].
   * If the data <= based_value, it will be set to bin index 0.
   * @param value
   * @return
   */
  public int quantize(double value) {
    if (m_isCircular) { // if the data is circular
      if (m_leftInclusive) {  // left inclusive, data -> [base_value, max_value)
        if (value >= m_maxValue) {
          value -= (int)((value - m_baseValue) / m_valueRange) * m_valueRange;
        } else if (value < m_baseValue) {
          value += (int)((m_maxValue - value) / m_valueRange) * m_valueRange;
          if (value >= m_maxValue) value = m_baseValue;
        }
      } else { // right inclusive, data -> (base_value, max_value]
        if (value > m_maxValue) {
          value -= (int)((value - m_baseValue) / m_valueRange) * m_valueRange;
          if (value <= m_baseValue) value = m_maxValue;
        } else if (value <= m_baseValue) {
          value += (int)((m_maxValue - value) / m_valueRange) * m_valueRange;
        }
      }
    }
    double bin_index_double = (value - m_baseValue) / m_binWidth;
    int bin_index = (int)bin_index_double;
    if (!m_leftInclusive && bin_index_double == (double)bin_index) { // right inclusive
      bin_index --;
    }
    if (bin_index >= m_binNum) bin_index = m_binNum - 1;
    if (bin_index < 0) bin_index = 0;
    return bin_index;
  }

  /**
   *
   */
  public static void main(String[] args) {
    LinearQuantizer quantizer = new LinearQuantizer(-0.5, 1, 2, true, true);
    for (int i = -10; i <= 10; i++) {
      System.out.println(Double.toString(i*0.5) + "->" + quantizer.quantize(i*0.5));
    }
    quantizer = new LinearQuantizer(-0.5, 1, 2, false, true);
    for (int i = -10; i <= 10; i++) {
      System.out.println(Double.toString(i*0.5) + "->" + quantizer.quantize(i*0.5));
    }
  }

}