package hep.aida.ref.histogram;

import hep.aida.ref.Annotation;
import hep.aida.ref.ManagedObject;
import hep.aida.IAnnotation;
import hep.aida.IDataPointSet;
import hep.aida.IDataPoint;
import hep.aida.IMeasurement;

import hep.aida.ref.event.AIDAListener;
import hep.aida.ref.event.DataPointSetEvent;
import hep.aida.ref.event.IsObservable;

import java.util.*;
 
/**
 * Basic user-level interface class for holding and managing
 * a single set of "data points".
 *
 * @author The AIDA team @ SLAC.
 *
 */
public class DataPointSet extends ManagedObject implements IDataPointSet, IsObservable, AIDAListener {

    private int defaultSize = 0;
    private IAnnotation annotation;
    //private String title;
    private int dimension;
    private List points;

    // Constructors 
    protected DataPointSet() {
        super("");
        initDataPointSet();
        annotation.addItem(Annotation.titleKey,"Title", true);  
    }
    
    public DataPointSet(String name, String title, int dimOfPoints) {
        this(name, title, dimOfPoints, 0);
    }

    public DataPointSet(String name, String title, int dimOfPoints, int defaultCapacity) {
        super(name);
	//this.title = title;
	defaultSize = defaultCapacity;
	dimension = dimOfPoints;
        initDataPointSet();
        annotation.addItem(Annotation.titleKey,title, true);  
    }
    
    private void initDataPointSet() {
        annotation = new Annotation();
        clear();
	points = new ArrayList(defaultSize);
        for ( int i = 0; i < defaultSize; i++ ) {
            DataPoint point = new DataPoint(dimension);
            points.add( point );
            if (point instanceof IsObservable) {
                ((IsObservable) point).addListener(this);
            }
        }
    }
        
    // End of Constructors

    protected java.util.EventObject createEvent()
    {
       return new DataPointSetEvent(this);
    }

    
    public IAnnotation annotation() { return annotation; }

    public void setAnnotation( IAnnotation annotation ) {this.annotation = annotation; }
    public String title() { 
        //return title; 
        return annotation.value(Annotation.titleKey);
    }

    public void setTitle(String title) throws IllegalArgumentException { 
        //this.title = title; 
        annotation.setValue(Annotation.titleKey,title);
        if (isValid) fireStateChanged();
    }

    public int dimension() { return dimension; }

    public void clear() { 
	//annotation = new Annotation();
        if (points != null && points.size() > 0) {
            for (int i=0; i<points.size(); i++) {
                removePoint(i);
            }
        }
        points = new ArrayList(defaultSize);
    }

    public int size() { return points.size(); }

    public IDataPoint point(int index) { return (IDataPoint) points.get(index); }

    /**
     * Set the IDataPoint at a give index in the set.
     * This method is not in the IDataSet interface and is here for efficiency reasons
     * @param index The IDataPoint index.
     * @param point The corresponding IDataPoint to be set at the index
     * @throws      IllegalArgumentException If the index is < 0 or >= size().
     *
     */
    public void setPoint(int index, IDataPoint point) throws IllegalArgumentException {
	if (index >= points.size() || index < 0) throw new IllegalArgumentException("Wrong argument in setPoint():  index="+index+", size="+points.size());
	IDataPoint oldPoint = (IDataPoint) points.get(index);  
        if (oldPoint instanceof IsObservable) {
            ((IsObservable) oldPoint).removeListener(this);
        }
        points.set(index, point); 
        if (point instanceof IsObservable) {
            ((IsObservable) point).addListener(this);
        }
        if (isValid) fireStateChanged();
    }

    public IDataPoint addPoint() throws java.lang.RuntimeException {
        IDataPoint point = new DataPoint(dimension);
        addPoint( point ); 
        return point;
    }
    
    public void addPoint(IDataPoint point) throws IllegalArgumentException {
	if (point.dimension() == dimension) points.add(point);
	else throw new IllegalArgumentException("Wrong dimension in addPoint(): DataPointSet.dimension()="+dimension+
						", DataPoint.dimension()="+point.dimension());
        if (point instanceof IsObservable) {
            ((IsObservable) point).addListener(this);
        }
        if (isValid) fireStateChanged();
    }

    public void removePoint(int index) throws IllegalArgumentException { 
	if (index >= points.size() || index < 0) throw new IllegalArgumentException("Wrong argument in removePoint()  index="+index+", size="+points.size());
	IDataPoint point = (IDataPoint) points.remove(index);  
        if (point instanceof IsObservable) ((IsObservable) point).removeListener(this);
        if (point instanceof DataPoint) ((DataPoint) point).clear();
       
        if (isValid) fireStateChanged();
    }

    public double lowerExtent(int coord) throws IllegalArgumentException { 
	if (coord<0 || coord>=dimension || points.size()==0)
	    throw new IllegalArgumentException("Can not calculate lowerExtent: coord="+coord+", size="+points.size());
	double lowerExtent = Double.NaN;
	for (int i=0; i<points.size(); i++) { 
	    double lower = ((DataPoint) points.get(i)).lowerExtent(coord);
	    if (Double.isNaN(lowerExtent) || lowerExtent > lower) lowerExtent = lower;
	}
	return lowerExtent; 
    }

    public double upperExtent(int coord) throws IllegalArgumentException { 
	if (coord<0 || coord>=dimension || points.size()==0)
	    throw new IllegalArgumentException("Can not calculate upperExtent: coord="+coord+", size="+points.size());
	double upperExtent = Double.NaN;
	for (int i=0; i<points.size(); i++) { 
	    double upper = ((DataPoint) points.get(i)).upperExtent(coord);
	    if (Double.isNaN(upperExtent) || upper > upperExtent ) upperExtent = upper;
	}
	return upperExtent; 
    }

    public void scale(double scaleFactor) throws IllegalArgumentException {
	if (scaleFactor<=0)
	    throw new IllegalArgumentException("Illegal scale factor: scaleFactor="+scaleFactor);
	for (int i=0; i<points.size(); i++) { 
	    IDataPoint p = (IDataPoint) points.get(i);
	    for (int j=0; j<dimension; j++) {
		IMeasurement meas = p.coordinate(j);
		meas.setValue(meas.value() * scaleFactor);
		meas.setErrorMinus(meas.errorMinus() * scaleFactor);
		meas.setErrorPlus(meas.errorPlus() * scaleFactor);
	    }
	}
        if (isValid) fireStateChanged();
    }

    public void scaleValues(double scaleFactor) throws IllegalArgumentException {
	if (scaleFactor<=0)
	    throw new IllegalArgumentException("Illegal scale factor: scaleFactor="+scaleFactor);
	for (int i=0; i<points.size(); i++) { 
	    IDataPoint p = (IDataPoint) points.get(i);
	    for (int j=0; j<dimension; j++) {
		IMeasurement meas = p.coordinate(j);
		meas.setValue(meas.value() * scaleFactor);
	    }
	}
        if (isValid) fireStateChanged();
    }

    public void scaleErrors(double scaleFactor) throws IllegalArgumentException {
	if (scaleFactor<=0)
	    throw new IllegalArgumentException("Illegal scale factor: scaleFactor="+scaleFactor);
	for (int i=0; i<points.size(); i++) { 
	    IDataPoint p = (IDataPoint) points.get(i);
	    for (int j=0; j<dimension; j++) {
		IMeasurement meas = p.coordinate(j);
		meas.setErrorMinus(meas.errorMinus() * scaleFactor);
		meas.setErrorPlus(meas.errorPlus() * scaleFactor);
	    }
	}
        if (isValid) fireStateChanged();
    }

    public void setCoordinate(int coord, double[] values, double[] errors) throws IllegalArgumentException {
        setCoordinate(coord, values, errors, errors);
    }
    
    public void setCoordinate(int coord, double[] values, double[] errp, double[] errm) throws IllegalArgumentException {
        if ( coord < 0 || coord >= dimension() ) throw new IllegalArgumentException("Illegal coordinate "+coord+"!! It has to be 0 <= coord < "+dimension()); 
        int nPoints = values.length;
        if ( nPoints != errp.length ) throw new IllegalArgumentException("Incompatible array sizes! Value's size "+nPoints+" while error's size is "+errp.length);
        if ( nPoints != errm.length ) throw new IllegalArgumentException("Incompatible array sizes! Value's size "+nPoints+" while error's size is "+errm.length);
        if ( size() == 0 )
            addNPoints(nPoints);
        if ( nPoints != size() ) throw new IllegalArgumentException("Array size "+nPoints+" is incompatible with the number of points "+size());
        
        for ( int i = 0; i < nPoints; i++ ) {
            IMeasurement meas = point(i).coordinate(coord);
            meas.setValue( values[i] );
            meas.setErrorMinus( errm[i] );
            meas.setErrorPlus( errp[i] );
        }
    }

    /**
     * Non-AIDA methods.
     *
     */
    protected void setDimension( int dimOfPoints ) {
        dimension = dimOfPoints;
    }
    
    private void addNPoints( int n ) {
        for( int i = 0; i < n; i++ )
            addPoint();
    }
      
    protected void finalize() throws Throwable {
        defaultSize = 0;
        clear();
    }
    
    // AIDAListener interface
    public void stateChanged(EventObject e) {
        //System.out.println("DataPointSet.stateChanged  isValid="+isValid);
        if (isValid) fireStateChanged();
    }
    
} // class or interface
