package hep.aida.ref.histogram;

/**
 * Implementation of ICloud1D.
 * @author The AIDA team at SLAC.
 *
 */

import hep.aida.*;
import java.util.ArrayList;
import java.util.Hashtable;

public class Cloud1D extends Cloud implements ICloud1D {
    
    /**
     * Create a new Cloud1D
     */
    public Cloud1D() {
        super("","",1,0,"");
    }
    
    /**
     * Create a new Cloud1D
     * @param name  The Cloud's name.
     * @param title The Cloud's title.
     * @param nMax  The maximum number of entries stored in the Cloud. If nMax is greater than zero the Cloud
     *              will be converted to an Histogram when the number of entries is more than nMax.
     * @param options Some options.
     *
     */
    protected Cloud1D(String name,String title,int nMax,String options) {
        super(name,title,1,nMax,options);
    }
    
    /**
     * Fill the Cloud with a new value with unit weight
     * @param value The value to add to the Cloud.
     * @return <code>true</code> if the fill was successful.
     *
     */
    public void fill(double value) {
        fill(value,1.0);
    }
    /**
     * Fill the Cloud with a new value with given weight
     * @param value The value to add to the Cloud.
     * @param weight The value's weight.
     * @return <code>true</code> if the fill was successful.
     *
     */
    public void fill(double value, double weight) {
        if (nEntries == 0) {
            lowerEdge = upperEdge=value;
        }
        else {
            if (value<lowerEdge) lowerEdge=value;
            if (value>upperEdge) upperEdge=value;
        }
        
        if ( histo != null ) {
            histo.fill(value,weight);
        } else if ( autoConvert() && nEntries == maxEntries ) {
            if ( histo != null ) throw new RuntimeException("Cloud already been converted");
            histo= toShowableHistogram(convBins, lowerEdgeWithMargin(), upperEdgeWithMargin());
            histo.fill(value,weight);
            values = null;
            weights = null;
            valuesArray.clear();
            weightsArray.clear();
            valuesArray = null;
            weightsArray = null;
        } else {
            if ( nEntries%arraySize == 0 ) {
                values  = new double[ arraySize ];
                weights = new double[ arraySize ];
                valuesArray.add( values );
                weightsArray.add( weights );
            }
            
            values[ nEntries%arraySize ] = value;
            weights[ nEntries%arraySize ] = weight;
            if ( ( !Double.isNaN(value) ) && ( !Double.isNaN(weight) ) ) {
                sumOfWeights += weight;
                mean += value*weight;
                rms += value*value*weight;
                validEntries++;
            }
            nEntries++;
            //            if ( nEntries > maxEntries && maxEntries > 0 ) throw new IllegalArgumentException();
        }
        if (isValid) fireStateChanged();
    }
    
    /**
     * Get the Cloud's lower edge.
     * @return The Cloud's lower edge.
     *
     */
    public double lowerEdge() {
        return lowerEdge;
    }
    /**
     * Get the Cloud's upper edge.
     * @return The Cloud's upper edge.
     *
     */
    public double upperEdge() {
        return upperEdge;
    }
    /**
     * Set the Cloud's upper edge
     * @param upperEdge The Cloud's upper edge.
     *
     */
    public void setUpperEdge( double upperEdge ) {
        this.upperEdge = upperEdge;
    }
    /**
     * Set the Cloud's lower edge
     * @param lowerEdge The Cloud's lower edge.
     *
     */
    public void setLowerEdge( double lowerEdge ) {
        this.lowerEdge = lowerEdge;
    }
    /**
     * Get a given value from the Cloud.
     * @param index The value's index.
     * @return The Cloud's corresponding value.
     * @exception RuntimeException if the Cloud has been converted
     *
     */
    public double value(int index) {
        if (histo!=null) throw new RuntimeException("Cloud has been converted");
        double[] val = (double[])valuesArray.get( index/arraySize );
        return val[index%arraySize];
    }
    /**
     * Get a given weight from the Cloud.
     * @param index The weight's index.
     * @return The Cloud's corresponding weight.
     * @exception RuntimeException if the Cloud has been converted
     *
     */
    public double weight(int index) {
        if (histo!=null) throw new RuntimeException("Cloud has been converted");
        double[] val = (double[])weightsArray.get( index/arraySize );
        return val[index%arraySize];
    }
    /**
     * Get the Cloud's mean.
     * @return The Cloud's mean.
     *
     */
    public double mean() {
        if ( histo != null ) return histo.mean();
        return mean / sumOfWeights();
    }
    /**
     * Get the Cloud's rms.
     * @return The Cloud's rms.
     *
     */
    public double rms() {
        if ( histo != null ) return histo.rms();
        return Math.sqrt( rms / sumOfWeights() - mean*mean/sumOfWeights()/sumOfWeights() );
    }
    /**
     * Get the Cloud's entries.
     * @return The Cloud's entries.
     *
     */
    public int entries() {
        if ( histo != null ) return histo.allEntries();
        return nEntries;
    }
    /**
     * Represent the Cloud as a Histogram.
     * @param nBins The Histogram's number of bins.
     * @param lowerEdge The Histogram's lower edge.
     * @param upperEdge The Histogram's upper edge.
     * @return The Histogram representing the Cloud.
     *
     */
    private IHistogram1D toShowableHistogram(int nBins, double lowerEdge, double upperEdge) {
        if ( histo != null ) return histo;
        return HistUtils.toShowableHistogram(this, nBins, lowerEdge, upperEdge);
    }
    
    /**
     * Convert the ICloud to an IHistogram.
     *
     */
    public void convert(int nBins, double lowerEdge, double upperEdge) {
        if ( histo != null ) throw new RuntimeException("Cloud already been converted");
        histo= toShowableHistogram(nBins, lowerEdge, upperEdge);
    }
    
    /**
     * Convert the ICloud to an IHistogram by specifying the bin edges.
     *
     */
    public void convert( double[] binEdges ) {
        if ( histo != null ) throw new RuntimeException("Cloud already been converted");
        IHistogram1D hist = new Histogram1D(name(),title(),new VariableAxis(binEdges));
        for(int i=0; i<nEntries; i++) hist.fill( value(i), weight(i) );
        histo = hist;
    }
    
    /**
     * Has the Cloud been converted to a Histogram?
     * @return <code>true<\code> if the Cloud has been converted to a Histogram.
     *
     */
    public boolean isConverted() {
        return histo != null ? true : false;
    }
    /**
     * Get the Histogram representing the Cloud
     * @return the histogram.
     * @exception RuntimeException if the histogram is not auto-convertible and "convert"
     * has not been called.
     */
    public IHistogram1D histogram() throws RuntimeException {
        if ( histo == null ) throw new RuntimeException("Cloud has not been converted");
        return histo;
    }
    /**
     * Set the Histogram representation of the Cloud.
     * @param hist The Histogram representing the Cloud.
     *
     */
    public void setHistogram( IHistogram1D hist ) {
        if ( histo != null ) throw new RuntimeException("Cloud already been converted");
        histo = hist;
    }
    
    public void fillHistogram(hep.aida.IHistogram1D hist1d) {
        if ( histo != null ) throw new IllegalArgumentException("Cloud has already been converted");
        for(int i=0; i<nEntries; i++) hist1d.fill( value(i), weight(i) );
    }
    
    public void reset() {
        nEntries = 0;
        lowerEdge = Double.NaN;
        upperEdge = Double.NaN;
        mean = 0.;
        rms = 0.;
        sumOfWeights = 0.;
        if ( histo != null )
            histo.reset();
        histo = null;
        valuesArray  = new ArrayList();
        weightsArray = new ArrayList();
        values = null;
        weights = null;
        super.reset();
    }
    
    public void convertToHistogram() {
        if ( histo != null ) throw new IllegalArgumentException("Cloud has already been converted to an Histogram");
        histo = toShowableHistogram(50, lowerEdgeWithMargin(), upperEdgeWithMargin());
    }
    
    public void scale(double scaleFactor) throws IllegalArgumentException {
        if ( scaleFactor <= 0 ) throw new IllegalArgumentException("Illegal scale factor "+scaleFactor+" it has to be positive");
        if ( isConverted() ) histo.scale( scaleFactor );
        else {
            for ( int i = 0; i < entries(); i++ ) {
                double[] weights = (double[])weightsArray.get( i/arraySize );
                weights[i%arraySize] *= scaleFactor;
            }
            sumOfWeights *= scaleFactor;
            mean *= scaleFactor;
            rms *= scaleFactor;
        }
        if (isValid) fireStateChanged();
    }
    
    /** Get the sum of weights of of all the entries
     * @return The sum of the weights of all the entries.
     *
     */
    public double sumOfWeights() {
        if ( histo != null ) return histo.sumAllBinHeights();
        return sumOfWeights;
    }
    
    public double lowerEdgeWithMargin() {
        if ( Double.isNaN(lowerEdge) )
            return Double.NaN;
        double le = lowerEdge != upperEdge ? lowerEdge : lowerEdge - 1;
        double ue = lowerEdge != upperEdge ? upperEdge : upperEdge + 1;
        double delta = ue - le;
        return le - margin()*Math.abs(delta);
    }
    public double upperEdgeWithMargin() {
        if ( Double.isNaN(upperEdge) )
            return Double.NaN;
        double le = lowerEdge != upperEdge ? lowerEdge : lowerEdge - 1;
        double ue = lowerEdge != upperEdge ? upperEdge : upperEdge + 1;
        double delta = ue - le;
        return ue + margin()*Math.abs(delta);
    }
    
    protected IHistogram hist() {
        return (IHistogram) histogram();
    }
    
    private int nEntries=0;
    private double lowerEdge = Double.NaN, upperEdge = Double.NaN;
    protected double sumOfWeights;
    
    private double mean, rms;
    private IHistogram1D histo;
    private ArrayList valuesArray  = new ArrayList();
    private ArrayList weightsArray = new ArrayList();
    private double[] values, weights;
}
