/*
 * Cloud2D.java
 *
 * Created on February 26, 2001, 2:11 PM
 */

package hep.aida.ref.histogram;
import hep.aida.*;
import java.util.ArrayList;

/**
 *
 * @author  The AIDA team @ SLAC.
 *
 */
public class Cloud2D extends Cloud implements ICloud2D {
    
    /**
     * Create a new Cloud2D
     */
    public Cloud2D() {
        super("","",2,0,"");
    }
    
    /**
     * Create a new Cloud2D
     * @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 Cloud2D(String name,String title,int nMax,String options) {
        super(name,title,2,nMax,options);
    }
    
    /**
     * Fill the Cloud with new values with unit weight
     * @param xValue The x value to add to the Cloud.
     * @param yValue The y value to add to the Cloud.
     * @return <code>true</code> if the fill was successful.
     *
     */
    public void fill(double xValue, double yValue) {
        fill(xValue,yValue,1.0);
    }
    /**
     * Fill the Cloud with new values with given weight
     * @param xValue The x value to add to the Cloud.
     * @param yValue The y value to add to the Cloud.
     * @param weight The values weight.
     * @return <code>true</code> if the fill was successful.
     *
     */
    public void fill(double xValue, double yValue, double weight) {
        if (nEntries == 0) {
            lowerEdgeX = upperEdgeX = xValue;
            lowerEdgeY = upperEdgeY = yValue;
        }
        else {
            if (xValue<lowerEdgeX) lowerEdgeX = xValue;
            if (xValue>upperEdgeX) upperEdgeX = xValue;
            if (yValue<lowerEdgeY) lowerEdgeY = yValue;
            if (yValue>upperEdgeY) upperEdgeY = yValue;
        }
        
        if ( histo != null ) {
            histo.fill(xValue,yValue,weight);
        } else if ( autoConvert() && nEntries == maxEntries ) {
            if ( histo != null ) throw new RuntimeException("Cloud already been converted");
            histo= toShowableHistogram(convBins,lowerEdgeXWithMargin(),upperEdgeXWithMargin(),convBins,lowerEdgeYWithMargin(),upperEdgeYWithMargin());
            histo.fill(xValue,yValue,weight);
            weights = null;
            xValues = null;
            yValues = null;
            xValuesArray.clear();
            yValuesArray.clear();
            weightsArray.clear();
            xValuesArray = null;
            yValuesArray = null;
            weightsArray = null;
        } else {
            if ( nEntries%arraySize == 0 ) {
                xValues = new double[ arraySize ];
                yValues = new double[ arraySize ];
                weights = new double[ arraySize ];
                xValuesArray.add( xValues );
                yValuesArray.add( yValues );
                weightsArray.add( weights );
            }
            
            xValues[ nEntries%arraySize ] = xValue;
            yValues[ nEntries%arraySize ] = yValue;
            weights[ nEntries%arraySize ] = weight;
            
            if ( ( !Double.isNaN(xValue) ) && ( !Double.isNaN(yValue) ) && ( !Double.isNaN(weight) ) ) {
                sumOfWeights += weight;
                
                meanX += xValue*weight;
                rmsX  += xValue*xValue*weight;
                meanY += yValue*weight;
                rmsY  += yValue*yValue*weight;
                validEntries++;                
            }
            nEntries++;
            if ( nEntries > maxEntries && maxEntries > 0 ) throw new IllegalArgumentException();
        }
        if (isValid) fireStateChanged();
    }


    /**
     * Get the Cloud's x lower edge.
     * @return The Cloud's x lower edge.
     *
     */
    public double lowerEdgeX() {
        return lowerEdgeX;
    }
    /**
     * Get the Cloud's y lower edge.
     * @return The Cloud's y lower edge.
     *
     */
    public double lowerEdgeY() {
        return lowerEdgeY;
    }
    /**
     * Get the Cloud's x upper edge.
     * @return The Cloud's x upper edge.
     *
     */
    public double upperEdgeX() {
        return upperEdgeX;
    }
    /**
     * Get the Cloud's y upper edge.
     * @return The Cloud's y upper edge.
     *
     */
    public double upperEdgeY() {
        return upperEdgeY;
    }
    /**
     * Set the Cloud's x lower edge
     * @param lowerEdgeX The Cloud's x lower edge.
     *
     */
    public void setLowerEdgeX( double lowerEdgeX ) {
        this.lowerEdgeX = lowerEdgeX;
    }
    /**
     * Set the Cloud's y lower edge
     * @param lowerEdgeY The Cloud's y lower edge.
     *
     */
    public void setLowerEdgeY( double lowerEdgeY ) {
        this.lowerEdgeY = lowerEdgeY;
    }
    /**
     * Set the Cloud's x upper edge
     * @param upperEdgeX The Cloud's x upper edge.
     *
     */
    public void setUpperEdgeX( double upperEdgeX ) {
        this.upperEdgeX = upperEdgeX;
    }
    /**
     * Set the Cloud's y upper edge
     * @param upperEdgeY The Cloud's y upper edge.
     *
     */
    public void setUpperEdgeY( double upperEdgeY ) {
        this.upperEdgeY = upperEdgeY;
    }
    /**
     * Get a given x value from the Cloud.
     * @param index The x value's index.
     * @return The Cloud's corresponding x value.
     * @exception RuntimeException if the Cloud has been converted
     *
     */
    public double valueX(int index) {
        if (histo!=null) throw new RuntimeException("Cloud has been converted");
        double[] val = (double[])xValuesArray.get( index/arraySize );
        return val[index%arraySize];
    }
    /**
     * Get a given y value from the Cloud.
     * @param index The y value's index.
     * @return The Cloud's corresponding y value.
     * @exception RuntimeException if the Cloud has been converted
     *
     */
    public double valueY(int index) {
        if (histo!=null) throw new RuntimeException("Cloud has been converted");
        double[] val = (double[])yValuesArray.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 x mean.
     * @return The Cloud's x mean.
     *
     */
    public double meanX() {
        if ( histo != null ) return histo.meanX();
        return meanX / sumOfWeights();
    }
    /**
     * Get the Cloud's y mean.
     * @return The Cloud's y mean.
     *
     */
    public double meanY() {
        if ( histo != null ) return histo.meanY();
        return meanY / sumOfWeights();
    }
    /**
     * Get the Cloud's x rms.
     * @return The Cloud's x rms.
     *
     */
    public double rmsX() {
        if ( histo != null ) return histo.rmsX();
        return Math.sqrt( rmsX / sumOfWeights() - meanX*meanX/sumOfWeights()/sumOfWeights() );
    }
    /**
     * Get the Cloud's y rms.
     * @return The Cloud's y rms.
     *
     */
    public double rmsY() {
        if ( histo != null ) return histo.rmsY();
        return Math.sqrt( rmsY / sumOfWeights() - meanY*meanY/sumOfWeights()/sumOfWeights() );
    }
    /**
     * Get the Cloud's entries.
     * @return The Cloud's entries.
     *
     */
    public int entries() {
        if ( histo != null ) return histo.allEntries();
        return nEntries;
    }
    /**
     * Convert the Cloud to a Histogram.
     * @param nBinsX     The Histogram's x number of bins.
     * @param lowerEdgeX The Histogram's x lower edge.
     * @param upperEdgeX The Histogram's x upper edge.
     * @param nBinsY     The Histogram's y number of bins.
     * @param lowerEdgeY The Histogram's y lower edge.
     * @param upperEdgeY The Histogram's y upper edge.
     *
     */
    public void convert(int nBinsX, double lowerEdgeX, double upperEdgeX,
    int nBinsY, double lowerEdgeY, double upperEdgeY) {
        if ( histo != null ) throw new RuntimeException("Cloud already been converted");
        histo= toShowableHistogram(nBinsX, lowerEdgeX, upperEdgeX, nBinsY, lowerEdgeY, upperEdgeY);
    }
    /**
     * Represent the Cloud as a Histogram.
     * @param nBinsX     The Histogram's x number of bins.
     * @param lowerEdgeX The Histogram's x lower edge.
     * @param upperEdgeX The Histogram's x upper edge.
     * @param nBinsY     The Histogram's y number of bins.
     * @param lowerEdgeY The Histogram's y lower edge.
     * @param upperEdgeY The Histogram's y upper edge.
     * @return The Histogram representing the Cloud.
     *
     */
    private IHistogram2D toShowableHistogram(int nBinsX, double lowerEdgeX, double upperEdgeX,
    int nBinsY, double lowerEdgeY, double upperEdgeY) {
        if ( histo != null ) return histo;
        return HistUtils.toShowableHistogram(this, nBinsX, lowerEdgeX, upperEdgeX, nBinsY, lowerEdgeY, upperEdgeY);
    }
    
    /**
     * Convert the ICloud to an IHistogram by specifying the bin edges.
     *
     */
    public void convert( double[] binEdgesX, double[] binEdgesY ) {
        if ( histo != null ) throw new RuntimeException("Cloud already been converted");
        IHistogram2D hist = new Histogram2D(name(),title(),new VariableAxis(binEdgesX),new VariableAxis(binEdgesY),"");
        for(int i=0; i<nEntries; i++) hist.fill( valueX(i), valueY(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 IHistogram2D 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( IHistogram2D hist ) {
        if ( histo != null ) throw new RuntimeException("Cloud already been converted");
        histo = hist;
    }
    
    public void fillHistogram(hep.aida.IHistogram2D hist2d) {
        if ( histo != null ) throw new IllegalArgumentException("Cloud has already been converted");
        for(int i=0; i<nEntries; i++) hist2d.fill( valueX(i), valueY(i), weight(i) );
    }
    
    public void reset() {
        nEntries = 0;
        lowerEdgeX = Double.NaN;
        upperEdgeX = Double.NaN;
        lowerEdgeY = Double.NaN;
        upperEdgeY = Double.NaN;
        meanX = 0.;
        rmsX = 0.;
        meanY = 0.;
        rmsY = 0.;
        sumOfWeights = 0.;
        if ( histo != null )
            histo.reset();
        histo = null;
        xValuesArray = new ArrayList();
        yValuesArray = new ArrayList();
        weightsArray = new ArrayList();
        xValues = null;
        yValues = 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, lowerEdgeXWithMargin(), upperEdgeXWithMargin(),50, lowerEdgeYWithMargin(), upperEdgeYWithMargin());
    }
    
    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;
            meanX *= scaleFactor;
            rmsX *= scaleFactor;
            meanY *= scaleFactor;
            rmsY *= scaleFactor;
        }
        if (isValid) fireStateChanged();
    }
    
    public double lowerEdgeXWithMargin() {
        if ( Double.isNaN(lowerEdgeX) )
            return Double.NaN;
        double le = lowerEdgeX != upperEdgeX ? lowerEdgeX : lowerEdgeX - 1;
        double ue = lowerEdgeX != upperEdgeX ? upperEdgeX : upperEdgeX + 1;
        double delta = ue - le;
        return le - margin()*Math.abs(delta);
    }
    public double upperEdgeXWithMargin() {
        if ( Double.isNaN(upperEdgeX) )
            return Double.NaN;
        double le = lowerEdgeX != upperEdgeX ? lowerEdgeX : lowerEdgeX - 1;
        double ue = lowerEdgeX != upperEdgeX ? upperEdgeX : upperEdgeX + 1;
        double delta = ue - le;
        return ue + margin()*Math.abs(delta);
    }
    public double lowerEdgeYWithMargin() {
        if ( Double.isNaN(lowerEdgeY) )
            return Double.NaN;
        double le = lowerEdgeY != upperEdgeY ? lowerEdgeY : lowerEdgeY - 1;
        double ue = lowerEdgeY != upperEdgeY ? upperEdgeY : upperEdgeY + 1;
        double delta = ue - le;
        return le - margin()*Math.abs(delta);
    }
    public double upperEdgeYWithMargin() {
        if ( Double.isNaN(upperEdgeY) )
            return Double.NaN;
        double le = lowerEdgeY != upperEdgeY ? lowerEdgeY : lowerEdgeY - 1;
        double ue = lowerEdgeY != upperEdgeY ? upperEdgeY : upperEdgeY + 1;
        double delta = ue - le;
        return ue + margin()*Math.abs(delta);
    }
    
    /** 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;
    }
    
    protected IHistogram hist() {
        return (IHistogram) histogram();
    }
    
    private int nEntries=0;
    private double lowerEdgeX, upperEdgeX;
    private double lowerEdgeY, upperEdgeY;
    
    private double meanX, rmsX;
    private double meanY, rmsY;
    private IHistogram2D histo;
    protected double sumOfWeights;
    
    private ArrayList xValuesArray = new ArrayList();
    private ArrayList yValuesArray = new ArrayList();
    private ArrayList weightsArray = new ArrayList();
    private double[] xValues,yValues,weights;
}
