package hep.aida.ref.function;

import hep.aida.IAnnotation;
import hep.aida.IFunction;
import hep.aida.IManagedObject;
import hep.aida.ref.Annotation;
import java.util.ArrayList;

/**
 *
 * @author The FreeHEP team @ SLAC
 *
 */
public class SumOfFunctions implements IManagedObject, IFunction {
        
    private ArrayList functions;
    private boolean hasFunctions = false;
    private int dimension;
    private Annotation annotation = new Annotation();
    private String[] parameters;
    private double[] parValues;
    private String name;
    private String title;
    
    public SumOfFunctions(String name, ArrayList functions) {
        this.functions = functions;
        this.name = name;
        updateFunction();
    }
    
    void updateFunction() {
        if ( nFunctions() > 0 ) {
            hasFunctions = true;
            dimension = function(0).dimension();
            int nPars = function(0).numberOfParameters();
            for ( int i = 1; i < nFunctions(); i++ ) {
                if ( function(i).dimension() != dimension )
                    throw new IllegalArgumentException("To be added functions must have the same dimension");
                nPars += function(i).numberOfParameters();
            }
            
            parameters = new String[nPars];
            parValues = new double[nPars];
            int count = 0;
            for ( int i = 0; i < nFunctions(); i++ ) {
                String[] pars = function(i).parameterNames();
                for ( int j = 0; j < pars.length; j++ ) {
                    String parName = pars[j];
                    for ( int k = 0; k < parameters.length; k++ )
                        if ( parName.equals( parameters[k] ) )
                            parName += "_"+indexOfFunction(function(i));
                    parameters[count++] = parName;
                }
            }
            title = codeletString();
        } else {
            hasFunctions = false;
            parameters = null;
            parValues = null;
            title = "";
        }
    }
    
    public IAnnotation annotation() {
        return (IAnnotation) annotation;
    }    
    
    public String codeletString() {
        if ( hasFunctions ) {
            String codelet = FunctionCatalog.prefix+CodeletUtils.modelFromCodelet(function(0).codeletString());
            for ( int i = 1; i < nFunctions(); i++ )
                codelet +=" + "+CodeletUtils.modelFromCodelet(function(i).codeletString());
            codelet += ":catalog";
            return codelet;
        }
        return null;
    }
    
    public int dimension() {
        return dimension;
    }
    
    public double[] gradient(double[] values) {
        if ( ! providesGradient() )
            throw new IllegalArgumentException("This function does not provide the gradient.");
        double[] gradient = function(0).gradient(values);
        for ( int i = 1; i < nFunctions(); i++ ) {
            double[] tmpGrad = function(i).gradient(values);
            for ( int j = 0; j < dimension(); j++ )
                gradient[j] += tmpGrad[j];
        }
        return gradient;
    }
    
    public int indexOfParameter(String str) {
        for ( int i = 0; i < parameters.length; i++ )
            if ( str.equals( parameters[i] ) )
                return i;
        throw new IllegalArgumentException("Illegal parameter name "+str);
    }
    
    public boolean isEqual(hep.aida.IFunction iFunction) {
        return false;
    }
    
    public String name() {
        return name;
    }
    
    public int numberOfParameters() {
        return parameters.length;
    }
    
    public double parameter(String str) {
        int index = indexOfParameter(str);
        for ( int i = 0; i < nFunctions(); i++ ) {
            IFunction func = function(i);
            int nPars = func.numberOfParameters();
            if ( index > nPars-1 )
                index -= nPars;
            else
                return func.parameter( parameterNames()[index] );
        }
        throw new IllegalArgumentException("Illegal parameter "+str);
    }
    
    public String[] parameterNames() {
        return parameters;
    }
    
    public double[] parameters() {
        int count = 0;
        for ( int i = 0; i < nFunctions(); i++ ) {
            double[] pars = function(i).parameters();
            for ( int j = 0; j < pars.length; j++ )
                parValues[count++] = pars[j];
        }
        return parValues;
    }
    
    public boolean providesGradient() {
        for ( int i = 0; i < nFunctions(); i++ )
            if ( ! function(i).providesGradient() )
                return false;
        return true;
    }
    
    public void setParameter(String str, double param) throws java.lang.IllegalArgumentException {
        int index = indexOfParameter(str);
        for ( int i = 0; i < nFunctions(); i++ ) {
            IFunction func = function(i);
            int nPars = func.numberOfParameters();
            if ( index > nPars-1 )
                index -= nPars;
            else {
                func.setParameter( func.parameterNames()[index], param );
                return;
            }
        }
    }
    
    public void setParameters(double[] values) throws java.lang.IllegalArgumentException {
        if ( values.length != numberOfParameters() )
            throw new IllegalArgumentException("Illegal size "+values.length+". It has to be equal to the number of parameters "+numberOfParameters()+".");
        for ( int i = 0; i < values.length; i++ )
            setParameter(parameters[i], values[i]);
    }
    
    public void setTitle(String str) throws java.lang.IllegalArgumentException {
        this.title = str;
    }
    
    public String title() {
        return title;
    }
    
    public double value(double[] values) {
        double result = 0;
        for ( int i = 0; i < nFunctions(); i++ )
            result += function(i).value(values);
        return result;
    }
    
    public String variableName(int param) {
        return function(0).variableName(param);
    }
    
    public String[] variableNames() {
        return function(0).variableNames();
    }
    
    public void addFunction(IFunction func) {
        functions.add(func);
        updateFunction();
    }

    public void removeFunction(IFunction func) {
        functions.remove(func);
        updateFunction();
    }
    
    public void removeAllFunctions() {
        functions.clear();
        updateFunction();
    }

    public IFunction function(int index) {
        return (IFunction) functions.get(index);
    }
    
    public int indexOfFunction(IFunction function) {
        return functions.indexOf(function);
    }
    
    public int nFunctions() {
        return functions.size();
    }
    
    public boolean containsFunction(IFunction function) {
        return functions.contains(function);
    }
}
