package com.srbenoit.math;

/**
 * A data class to store a list of counters (planes) over a series of times.
 */
public class Histogram {

    /** the histogram data (first index is plane, second is time) */
    private final transient int[][] data;

    /**
     * Constructs a new <code>Histogram</code>.
     *
     * @param  numPlanes  the number of planes of data to include in the histogram
     * @param  numTimes   the number of time indexes of data array to build
     */
    public Histogram(final int numPlanes, final int numTimes) {

        if ((numPlanes <= 0) || (numTimes <= 0)) {
            throw new IllegalArgumentException("Histogram must have length");
        }

        this.data = new int[numPlanes][numTimes];
    }

    /**
     * Gets the number of planes in the histogram.
     *
     * @return  the number of planes
     */
    public int getNumPlanes() {

        return this.data.length;
    }

    /**
     * Gets the number of time points in the histogram.
     *
     * @return  the number of time points
     */
    public int getNumTimes() {

        return this.data[0].length;
    }

    /**
     * Increments the current (time index 0) value in the histogram.
     *
     * @param  plane  the plane of the data value to increment
     */
    public void incrementValue(final int plane) {

        synchronized (this.data) {
            this.data[plane][0]++;
        }
    }

    /**
     * Gets a particular value in the histogram.
     *
     * @param   plane  the plane of the data value to get
     * @param   time   the time index of the data value to get
     * @return  the data value
     */
    public int getValue(final int plane, final int time) {

        synchronized (this.data) {
            return this.data[plane][time];
        }
    }

    /**
     * Gets the list of all values in the histogram.
     *
     * @return  the list of values
     */
    public int[][] getValues() {

        int[][] values;

        synchronized (this.data) {
            values = new int[this.data.length][];

            for (int i = 0; i < this.data.length; i++) {
                values[i] = this.data[i].clone();
            }
        }

        return values;
    }

    /**
     * Sets the current (index 0) value of a particular plane.
     *
     * @param  plane     the plane for which to set the value
     * @param  newValue  the new value
     */
    public void setValue(final int plane, final int newValue) {

        synchronized (this.data) {
            this.data[plane][0] = newValue;
        }
    }

    /**
     * Sets the list of all values in the histogram.
     *
     * @param  newValues  the list of new values
     */
    public void setValues(final int[][] newValues) {

        synchronized (this.data) {

            if (newValues.length != this.data.length) {
                throw new IllegalArgumentException("Histogram length mismatch");
            }

            for (int plane = 0; plane < this.data.length; plane++) {

                if (newValues[plane].length != this.data[plane].length) {
                    throw new IllegalArgumentException("Histogram length mismatch");
                }

                System.arraycopy(newValues[plane], 0, this.data[plane], 0, this.data[plane].length);
            }
        }
    }

    /**
     * Shifts each counter in the histogram to the next larger slot, discarding the data in the
     * highest slot, and initializes the lowest slot to a particular value.
     *
     * <p>This is useful when the slots represent windows of time, and we move into a new window -
     * the oldest count is discarded, all counts are shifted to the next older window, and the new
     * window is started.
     */
    public void shiftHigher() {

        synchronized (this.data) {

            for (int plane = 0; plane < this.data.length; plane++) {

                // shift all histogram data up one slot
                for (int i = this.data.length - 1; i > 0; i--) {
                    this.data[plane][i] = this.data[plane][i - 1];
                }

                // initialize the [0] entry in each plane to zero
                this.data[plane][0] = 0;
            }
        }
    }
}
