package hep.aida.util.comparison;

import hep.aida.ext.IComparisonAlgorithm;
import hep.aida.ext.IComparisonData;
import hep.aida.ext.IComparisonResult;
import hep.aida.ref.AidaUtils;
import java.util.Map;

/**
 *
 * @author The FreeHEP team @ SLAC.
 *
 */
public abstract class AbstractComparisonAlgorithm implements IComparisonAlgorithm {
        
    private Map optionsMap;
    
    public static final int ONLY_BINNED_DATA = 0;
    public static final int ONLY_UNBINNED_DATA = 1;
    public static final int ANY_DATA = 2;
    
    public static final int ONLY_SAME_NUMBER_OF_EVENTS = 0;
    public static final int ANY_NUMBER_OF_EVENTS = 1;
    
    private int dataType;
    private int eventsType;
    
    private double rejectionLevel = 0.05;
    
    AbstractComparisonAlgorithm(int dataType, int eventsType) {
        this.dataType = dataType;
        this.eventsType = eventsType;
    }
    
    public IComparisonResult compare(IComparisonData d1, IComparisonData d2, String options) {
        
        if ( ! canCompare(d1, d2) )
            throw new IllegalArgumentException("This algorithm "+algorithmNames()[0]+" cannot compare the given data sets.");
        
        this.optionsMap = AidaUtils.parseOptions(options); 

        applyOptions();
        
        setRejectionLevel();
        
        ComparisonResult result = new ComparisonResult();
        
        result.setMatchBounds(matchLowerBound(), matchUpperBound());
        
        result.setQuality( quality(d1,d2) );
        
        result.setnDof( nDof(d1,d2) );
        
        return result;

    }
    
    public abstract double quality(IComparisonData d1, IComparisonData d2);
        
    public void applyOptions() {
    }
    
    public int nDof(IComparisonData d1, IComparisonData d2) {
        return d1.nPoints();
    }
    
    public double matchLowerBound() {
        return rejectionLevel();
    }

    public double matchUpperBound() {
        return 1;
    }
    
    public boolean isOptionSet(String option) {
        return optionsMap.containsKey(option);
    }
    
    public String optionValue(String option) {
        return (String)optionsMap.get(option);
    }
    
    public abstract String[] algorithmNames();
    
    public void setRejectionLevel() {
        String rejectionLevelStr = optionValue("rejectionLevel");
        if ( rejectionLevelStr != null )
            rejectionLevel = Double.valueOf(rejectionLevelStr).doubleValue();
        
    }
    
    public double rejectionLevel() {
        return rejectionLevel;
    }
    
    public double[] getCumulativeArray(IComparisonData d) {
        int nPoints = d.nPoints();
        
        double[] cumulativeWeights = new double[nPoints];
        double sumOfWeights = 0;
        
        for ( int i = 0; i < nPoints; i++ ) {
            double weight = d.weight(i);
            if ( weight < 0 )
                weight = 0;
            sumOfWeights += weight;
            cumulativeWeights[i] = sumOfWeights;
        }
        
        if ( sumOfWeights != 0 )
            for( int i=0; i < nPoints; i++ )
                cumulativeWeights[i] /= sumOfWeights;
        
        return cumulativeWeights;
    }
    
    public boolean canCompare(IComparisonData d1, IComparisonData d2) {
        if ( d1.type() != d2.type() )
            throw new IllegalArgumentException("Cannot compare a binned data set with an unbinned one.");
        
        if ( d1.type() == IComparisonData.BINNED_DATA && dataType == ONLY_UNBINNED_DATA )
            return false;

        if ( d1.type() == IComparisonData.UNBINNED_DATA && dataType == ONLY_BINNED_DATA )
            return false;

        if ( d1.type() == IComparisonData.BINNED_DATA )
            if ( ! isBinningCompatible(d1, d2) )
                return false;
        
        if ( eventsType == ONLY_SAME_NUMBER_OF_EVENTS )
            if ( d1.nPoints() != d2.nPoints() )
                return false;
        return true;
    }
    
    private boolean isBinningCompatible(IComparisonData d1, IComparisonData d2 ) {
        int nBins = d1.nPoints();
        if ( nBins != d2.nPoints() )
            return false;
        for ( int i = 0; i < nBins; i++ )
            if ( d1.value(i) != d2.value(i) )
                return false;
        return true;
    }
    
    public double sumOfWeights(IComparisonData d) {
        double sumOfWeights = 0;
        for ( int i = 0; i < d.nPoints(); i++ )
            sumOfWeights += d.weight(i);
        return sumOfWeights;
    }
    
    public int entries(IComparisonData d) {
        int entries = 0;
        for ( int i = 0; i < d.nPoints(); i++ )
            entries += d.entries(i);
        return entries;
    }
}
