/*
 * FunctionCatalog.java
 *
 * Created on September 18, 2002, 2:01 PM
 */

package hep.aida.ref.function;
import java.util.*;
import java.io.*;
import hep.aida.*;
import hep.aida.ref.*;
import hep.aida.dev.*;
import hep.aida.ref.function.SumOfFunctions;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 *
 * @author  serbo
 */
public class FunctionCatalog implements IDevFunctionCatalog, Serializable {

    private static Pattern pattern = Pattern.compile("\\s*(\\w+)\\s*\\+??");
    
    public static String[] defaultNames = { "g2", "g", "e", "p0", "p1", "p2", "p3", "p4", "p5", "p6", "p7", "p8", "p9" };
    public static String prefix = "codelet:";

    protected static FunctionCatalog catalog = null;
    protected TreeMap hash;
    protected TreeMap userHash;
    protected FunctionCreator creator;

    // Constructors
    public static FunctionCatalog getFunctionCatalog() {
	if (catalog == null) catalog = new FunctionCatalog();
	return catalog;
    }
    public static FunctionCatalog getFunctionCatalog(String fileName) throws IOException {
	try {
	    if (catalog == null) catalog = new FunctionCatalog(fileName);
	} catch (IOException ioe) { 
	    throw ioe; 
	} 
	return catalog;
    }

    protected FunctionCatalog() {
	creator = new FunctionCreator();
	hash = new TreeMap();
	userHash = new TreeMap();
	for (int i=0; i<defaultNames.length; i++) { 
	    hash.put(defaultNames[i], new String("codelet:"+defaultNames[i]+":catalog"));
	}
    }

    protected FunctionCatalog(String fileName) throws IOException {
	creator = new FunctionCreator();
	hash = new TreeMap();
	userHash = new TreeMap();
	for (int i=0; i<defaultNames.length; i++) { 
	    hash.put(defaultNames[i], new String("codelet:"+defaultNames[i]+":catalog"));
	}
	loadAll(fileName);
    }

    // IFunctionCatalog and IDevFunctionCatalog methods

    public boolean add(String nameId, IFunction f) {
	return add(nameId, f.codeletString());
    }

    public boolean add(String nameId, String codelet) {
	if (hash.containsKey(nameId.toLowerCase())) return false;
	if (userHash.containsKey(nameId)) return false;

	userHash.put(nameId, codelet);
	return true;
    }

    public String[] list() {
	String[] array = new String[hash.size()+userHash.size()];
	Object[] entries = (hash.keySet()).toArray();
	Object[] userEntries = (userHash.keySet()).toArray();

	for (int i=0; i<hash.size(); i++) { array[i] = (String) entries[i]; }
	for (int i=0; i<userHash.size(); i++) { array[i+hash.size()] = (String) userEntries[i]; }

	return array;
    }

    private String codeletForName(String catalogName) {
	String tmp = catalogName.trim();
	String str = (String) hash.get(tmp.toLowerCase());
	if (str == null)  str = (String) userHash.get(tmp);
	return str;
    }

    public IFunction create(String model) {
        return create(null, model);
    }

    public IFunction create(String name, String model) {
        if ( model.indexOf("*") > 0 || model.indexOf("-") > 0 || model.indexOf("/") >0 )
            throw new IllegalArgumentException("Unsupported operation. We currently only support adding functions.");
        
        ArrayList functions = new ArrayList();
        Matcher matcher = pattern.matcher(model);
        while (matcher.find()) {
            String m = matcher.group(1);
            String codeletString = codeletForName(m);
            functions.add( creator.createFromCodelet(name, codeletString) );
        }
        if ( functions.size() == 0 )
            throw new IllegalArgumentException("Problem parsing model "+model+". Please report this problem.");
        else if ( functions.size() == 1 )
            return (IFunction) functions.get(0);
        else
            return new SumOfFunctions(name,functions);
    }

    public IFunction clone(String nameInATree, IFunction f) {
	IFunction clone;
        String codeletString = f.codeletString();
        if ( CodeletUtils.isCodeletFromCatalog(codeletString) ) {
            String functionModel = CodeletUtils.modelFromCodelet(f.codeletString());
            clone = create(nameInATree,functionModel);
        } else {
            clone = creator.createFromCodelet(nameInATree,codeletString);
        }

        if ( !(clone instanceof IModelFunction) )
            clone = new BaseModelFunction(nameInATree, clone.title(), clone);
        
	if (f instanceof IModelFunction) {
	    ((IModelFunction) clone).normalize(((IModelFunction) f).isNormalized());
	    IRangeSet c_rs = null;
	    IRangeSet f_rs = null;
	    double[] upper = null;
	    double[] lower = null;
	    ((IModelFunction) clone).excludeNormalizationAll();

	    for (int i=0; i<f.dimension(); i++) {
		f_rs = ((IModelFunction) f).normalizationRange(i);
		c_rs = ((IModelFunction) clone).normalizationRange(i);
		upper = f_rs.upperBounds();
		lower = f_rs.lowerBounds();
		for (int n=0; n<f_rs.size(); n++) {
		    c_rs.include(lower[n], upper[n]); 
		}
	    }
	} //else if (f instanceof IFunction) {
          //  BaseModelFunction bmf = new BaseModelFunction(nameInATree, nameInATree, clone);
          //  clone = bmf;
        //}
	    
	if (clone instanceof BaseModelFunction) {
	    ((BaseModelFunction) clone).setParameterNames(f.parameterNames());
	}
	
	clone.setParameters(f.parameters());
	return clone;
    }

    // Other methods
    public String[] defaultFunctions() { return defaultNames; }

    public void loadAll(String nameOnDisk) throws IOException { 
	try {
	    FileInputStream in = new FileInputStream(nameOnDisk); 
	    ObjectInputStream s = new ObjectInputStream(in);
	    userHash = (TreeMap)s.readObject();
	} catch (IOException ioe) { 
	    throw ioe; 
	} catch (Exception e) { 
	    throw new RuntimeException("Can not load FunctionCatalog. Please check file:"+nameOnDisk, e);
	}
    }

    public void storeAll(String nameOnDisk)  throws IOException { 
	try {
	    FileOutputStream out = new FileOutputStream(nameOnDisk); 
	    ObjectOutputStream s = new ObjectOutputStream(out);
	    s.writeObject(userHash);
	    s.flush();
	} catch (IOException ioe) { 
	    throw ioe; 
	} catch (Exception e) { 
	    throw new RuntimeException("Can not save FunctionCatalog. Please check file:"+nameOnDisk, e);
	}
    }

    public void remove(String nameId) {
	if (hash.containsKey(nameId)) hash.remove(nameId);
	else
	    throw new IllegalArgumentException("Catalog does not contain function \""+nameId+"\"");
    }

    public FunctionCreator getFunctionCreator() {
	return creator;
    }

    public String toString() {
	String str = "";
	str += "\nFunctionCatalog:            prefix = "+prefix+"\n";
	str +=   "------------------\n";

	if (!hash.isEmpty()) {
	    str += "\n\tDefault Functions:\n"; 
	    Object[] keys = (hash.keySet()).toArray();
	    for (int i=0; i<keys.length; i++) {
		str += "\t\t" + ((String) keys[i]) + "\t" + ((String) hash.get(keys[i])) +"\n";
	    }
	}

	if (!userHash.isEmpty()) {
	    str += "\n\tAdditional Functions:\n"; 
	    Object[] keys = (userHash.keySet()).toArray();
	    for (int i=0; i<keys.length; i++) {
		str += "\t\t" + ((String) keys[i]) + "\t" + ((String) userHash.get(keys[i])) +"\n";
	    }
	}
	return str;
    }


    public static void main(String[] args)  throws ClassNotFoundException, FileNotFoundException, IOException {
	
	test1();
	test2();


    }

    public static void test1()  throws ClassNotFoundException, FileNotFoundException, IOException {
        IAnalysisFactory af = IAnalysisFactory.create();     //jas.aida.gui.JASGUIAnalysisFactory.create(); 
        ITree tree = af.createTreeFactory().create();
        IFunctionFactory ff = af.createFunctionFactory( tree );
	IModelFunction f1 = (IModelFunction) ff.createFunctionFromScript("name1", 1, "a*(1+c*sin(x[0]-d))", "a,c,d", "f1");
	IModelFunction f2 = (IModelFunction) ff.createFunctionByName("name-2", "P5");


	IFunctionCatalog cat = ff.catalog();

	System.out.println(cat+"\n");

	cat.add("script1", f1);
	cat.add("poly-5",  f2);
	String[] names = cat.list();
	for(int i=0; i<names.length; i++) System.out.println(i+"\t "+names[i]);


	System.out.println(cat+"\n");
	String codelet = "codelet:hep.aida.ref.TestUserFunction:file://afs/slac.stanford.edu/u/ey/serbo/java-tests/freehep-hep.jar";

	cat.add("Victor-test", codelet);
	System.out.println(cat+"\n");

	cat.storeAll("Cat");
    }

    public static void test2()  throws ClassNotFoundException, FileNotFoundException, IOException {
        IAnalysisFactory af = IAnalysisFactory.create();     //jas.aida.gui.JASGUIAnalysisFactory.create(); 
        ITree tree = af.createTreeFactory().create();
        IFunctionFactory ff = af.createFunctionFactory( tree );


	IFunctionCatalog cat = ff.catalog();
	cat.loadAll("Cat");

	System.out.println(cat+"\n");

	IFunction f = ff.createFunctionByName("TreeName", "Victor-test");
	System.out.println(FunctionCreator.toString(f));
    }

}
