package hep.aida.ref.dataSet;

/**
 *
 * @author The FreeHEP team @ SLAC
 *
 * This class is meant to calculate the mean and rms associated with a 
 * one dimensional data set. In particular it calculates the following quantities for
 * "fillable" data objects like Histograms, Clouds, Profiles, Tuples:
 *  - mean
 *  - rootMeanSquared
 * These quantities for a data set {xi} with weights {wi} are defined as:
 *  - mean = sum(xi*wi)/sumOfWeights
 *  - rootMeanSquared = sqrt( sum(xi*xi*wi)*sum(wi) - sum(xi*wi)*sum(xi*wi) )/sum(wi)
 *  - sumOfWeights = sum(wi)
 *
 * This class is meant to be used only within this package.
 *
 */
class MeanAndRmsStatistics {
    
    /**
     * Internally we keep track of the following additive quantities:
     * - m   =  sum(xi*wi)
     * - r   =  sum(xi*xi*wi)
     * - sw  = sum(wi)
     *
     * The mean and the rootMeanSquared are obtained as :
     * - mean = m/sw
     * - rootMeanSquared = sqrt( r*sw - m*m )/sw
     *
     */
    private double m, r, sw;
    
    //The description for this DataSetStatistics
    private String description;
    
    /** 
     * Creates a new instance of DataSetStatistics.
     * At creation the reset() method is invoked.
     */
    protected MeanAndRmsStatistics(String description) {
        setDescription( description );
        reset();
    }
    
    /**
     * Add a weighted entry to this DataSetStatistics.
     * The statistical information is updated.
     * @param x The added entry.
     * @param w The corresponding weight.
     *
     */
    public void addEntry( double x, double w ) {
        if ( w < 0 ) throw new IllegalArgumentException("Cannot accept an entry with negative weight "+w);
        m   += x*w;
        r   += x*x*w;
        sw  += w;
    }
    
    /**
     * Add a new entry to this DataSetStatistics with unit weight.
     * @param x The added entry.
     *
     */
    public void addEntry( double x ) {
        addEntry( x, 1. );
    }


    /**
     * Remove a weighted entry from this DataSetStatistics.
     * The statistical information is updated.
     * @param x The entry to remove.
     * @param w The corresponding weight.
     *
     */
    public void removeEntry( double x, double w ) {
        if ( w < 0 ) throw new IllegalArgumentException("Cannot accept an entry with negative weight "+w);
        m   -= x*w;
        r   -= x*x*w;
        sw  -= w;
    }
    
    /**
     * Remove an entry from this DataSetStatistics with unit weight.
     * @param x The entry to remove.
     *
     */
    public void removeEntry( double x ) {
        removeEntry( x, 1. );
    }

    /**
     * Add a set of weighted entries to this DataSetStatistics.
     * The statistical information is updated.
     * @param mean    The mean of the entries to be added.
     * @param rms     The rms of the entries to be added.
     * @param sumw    The sum of weights of the entries to be added (for entries
     *                with unit weight it is the number of entries).
     *
     */
    public void addEntries( double mean, double rms, double sumw ) {
        double mEntries = mean*sumw;
        double rEntries = (rms*rms + mean*mean)*sumw;
        m  += mEntries;
        r  += rEntries;
        sw += sumw;
    }

    /**
     * Remove the information corresponding to a set of weighted entries.
     * @param mean    The mean of the entries to be removed.
     * @param rms     The rms of the entries to be removed.
     * @param sumw    The sum of weights of the entries to be removed (for entries
     *                with unit weight it is the number of entries).
     *
     */
    public void removeEntries( double mean, double rms, double sumw ) {
        double mEntries = mean*sumw;
        double rEntries = (rms*rms + mean*mean)*sumw;
        m  -= mEntries;
        r  -= rEntries;
        if ( r < 0 ) r = 0;
        sw -= sumw;
    }
        
    /**
     * Get the mean.
     * @return The mean of the dataSet.
     *
     */
    public double mean() {
        if ( sw != 0 )
            return m/sw;
        return 0.;
    }
    
    /**
     * Get the rms.
     * @return The rms of the dataSet.
     *
     */
    public double rms() {
        if ( sw != 0 ) {
            double up2 = r*sw-m*m;
            if ( up2 < -1E-12*m*m ) up2 = 0;
            return Math.sqrt( Math.abs(up2) )/sw;
        }
        return 0.;
    }
       
    /**
     * Scale the statistics by a give scaleFactor
     * Rescaling is equivalent to multiplying all the weights by the scale factor.
     * @param scaleFactor The scaleFactor.
     *
     */
    public void scale( double scaleFactor ) {
        if ( scaleFactor > 0 ) {
            m  *= scaleFactor;
            r  *= scaleFactor;
            sw *= scaleFactor;
        } else
            throw new IllegalArgumentException("Invalid scale factor "+scaleFactor+". It must be positive");
    }    
    
    /**
     * Get the description of this DataSetStatistics
     * @return The description.
     *
     */
    public String description() {
        return description;
    }
    
    /**
     * Set the description of this DataSetStatistics
     * @param description The description.
     *
     */
    protected void setDescription( String description ) {
        this.description = description;
    }

    /**
     * Reset all the statistics quantities to zero.
     *
     */
    public void reset() {
        m   = 0;
        r   = 0;
        sw  = 0;
    }
    
}
