/*
 * AbstractDevModelFunction.java
 *
 * Created on September 1, 2002, 4:07 AM
 */

package hep.aida.ref.function;
import java.util.*;
import hep.aida.*;
import hep.aida.ref.*;
import hep.aida.dev.*;
import hep.aida.ext.*;

/**
 *
 * @author  serbo
 */
public abstract class AbstractDevModelFunction implements hep.aida.dev.IDevModelFunction {

    protected int dimension;
    //private int numberOfParameters;
    protected double[] x;
    protected double[] p;
    protected String[] varNames;
    protected String[] parNames;
    protected IAnnotation annotation;
    protected String codeletString;
    protected String title;
    protected IFunction function;

    protected ArrayList[] min;
    protected ArrayList[] max;

    private boolean providesGradient;
    private boolean gradientValid;
    private double[] gradient;

    private boolean providesParameterGradient;
    private boolean parameterGradientValid;
    private double[] parameterGradient;

    private boolean providesNormalization;
    private boolean isNormalized;
    private boolean normalizationValid;
    private double normalizationAmplitude;

    protected IRangeSet[] rangeSet;

    public AbstractDevModelFunction() {
	init();
    }

    public abstract int dimension();

    public abstract int numberOfParameters();

    public abstract double functionValue(double[] var);

    private void init() {
	dimension = -1;
	x = null;
	p = null;
	varNames = new String[] {"x0"};
	parNames = new String[] {"p0"};
	annotation = null;
	codeletString = null;
	function = null;
	min = null;
	max = null;
	gradient = null;
	parameterGradient = null;
	
	providesGradient = false;
	gradientValid = false;
	providesParameterGradient = false;
	parameterGradientValid = false;
	providesNormalization = false;
	isNormalized = false;
	normalizationValid = false;
	normalizationAmplitude = Double.NaN;

	annotation = new Annotation();
	rangeSet = new RangeSet[dimension];
	rangeSet[0] = new RangeSet();
    }

    public final double value(double[] var) { 
	if (normalizationValid) 
	    return normalizationAmplitude * functionValue(var);
	else {
	    normalizationAmplitude = normalizationAmplitude();
	    normalizationValid = true;
	    return normalizationAmplitude * functionValue(var);	    
	}
    }

    public IAnnotation annotation() { return annotation; }

    public String variableName(int i)  { return varNames[i]; }

    public String[] variableNames() { return varNames; }

    public String[] parameterNames() { return parNames; }

    public int indexOfParameter(String name) {
	for (int i=0; i<numberOfParameters(); i++) {
	    if (name.equals(parNames[i])) return i;
	}
	throw new IllegalArgumentException("Function does not have variable named \"" + name + "\"");
    }

    public void setParameters(double[] params) { 
	normalizationValid = false;
	for (int i=0; i<numberOfParameters(); i++) p[i] = params[i]; 
    }

    public void setParameter(String name, double x) throws IllegalArgumentException { 
	normalizationValid = false;
	p[indexOfParameter(name)] = x; 
    }

    public double[] parameters() { return p; }

    public double parameter(String name) { 
	return p[indexOfParameter(name)];
    } 

    public boolean isEqual(IFunction f) {
	throw new UnsupportedOperationException("This method is not implemented yet");
    }

    public boolean providesGradient() { return providesGradient; }

    public double[] gradient(double[] x) {
	return gradient;
    }

    public String codeletString() { return codeletString; }


    // IDevFunction methods
    public void setCodeletString(String codelet) { codeletString = codelet; }

    public void setDimension(int dim) {
	dimension = dim;
	x = new double[dim];
	if (dim != varNames.length) {
	    varNames = new String[dim];
	    min = new ArrayList[dim];
	    max = new ArrayList[dim];

	    for (int i=0; i<dim; i++) {
		min[i] = new ArrayList();
		max[i] = new ArrayList();
		varNames[i] = "x" + i;
	    }
	}
    }

    public void setProvidesGradient(boolean yes) { providesGradient = yes; }

    public boolean setVariableNames(String[] names) {
	if (dimension != names.length)
	    throw new IllegalArgumentException("Number of parameters in the function (" + dimension +
					       ") is not equal to the number of elements in array (" + names.length + ").");
	for (int i=0; i<dimension; i++) { varNames[i] = names[i]; }
	return true;
    }
 

    public void setNumberOfParameters(int parnum) {
	p = new double[parnum];
	if (parnum != parNames.length) parNames = new String[parnum];
	for (int i=0; i<numberOfParameters(); i++) { parNames[i] = "p" + i; }
    }

    public boolean setParameterNames(String[] names) {
	if (numberOfParameters() != names.length)
	    throw new IllegalArgumentException("Number of parameters in the function (" + numberOfParameters() +
					       ") is not equal to the number of elements in array (" + names.length + ").");
	for (int i=0; i<numberOfParameters(); i++) { parNames[i] = names[i]; }
	return true;
    }

    public void setAnnotation(IAnnotation ptr) { annotation = ptr; }


    // IModelFunction methods
    public boolean providesNormalization() { return providesNormalization; }

    public void normalize(boolean on) { 
	if (on && !isNormalized && providesNormalization) {
	    normalizationAmplitude = normalizationAmplitude();
	    isNormalized = true;
	}
    }

    public boolean isNormalized() { return isNormalized; }

    public double[] parameterGradient(double[] x) {
	return parameterGradient;
    }

    public boolean providesParameterGradient() { 
        return providesParameterGradient; 
    }

    public void setNormalizationRange(double rMin, double rMax, int iAxis) {
	normalizationValid = false;

	min = new ArrayList[dimension];
	max = new ArrayList[dimension];

	for (int i=0; i<dimension; i++) {
	    min[i] = new ArrayList();
	    max[i] = new ArrayList();
	}

	min[iAxis].add(new Double(rMin));
	max[iAxis].add(new Double(rMax));
    }

    public void includeNormalizationRange(double xMin, double xMax, int iAxis) {
	normalizationValid = false;

	min[iAxis].add(new Double(xMin));
	max[iAxis].add(new Double(xMax));
    }

    public void excludeNormalizationRange(double xMin, double xMax, int iAxis) {
	normalizationValid = false;
    }

    public void includeNormalizationAll(int iAxis) {
	normalizationValid = false;
    }

    public void excludeNormalizationAll(int iAxis) {
	normalizationValid = false;
    }

    public void includeNormalizationAll() {
	normalizationValid = false;
    }

    public void excludeNormalizationAll() {
	normalizationValid = false;
    }

    public IRangeSet normalizationRange(int iAxis) { return rangeSet[iAxis]; }
    
    // IDevModelFunction methods
  /// Fails if you try to set (NOT provides AND is_normalized).
    public boolean setNormalization(boolean provides, boolean is_normalized) {
	if (!provides && is_normalized)
	    throw new IllegalArgumentException("Function can not be \"normalized\" and not provide normalization at the same time");
	providesNormalization = provides;
	isNormalized = is_normalized;
	return true;
    }

    public void setProvidesParameterGradient(boolean yes) { providesParameterGradient = yes; }


    // Extra methods
    public final double normalizationAmplitude() {
	return normalizationAmplitude(min, max);
    }

    public double normalizationAmplitude(ArrayList[] xMin, ArrayList[] xMax) {
	return 1;
    }

    public String title() { return title; }

    public void setTitle(String t) { title = t; }
}
