/*
 *    GeoTools - The Open Source Java GIS Toolkit
 *    http://geotools.org
 * 
 *    (C) 2004-2010, Open Source Geospatial Foundation (OSGeo)
 *    
 *    This library is free software; you can redistribute it and/or
 *    modify it under the terms of the GNU Lesser General Public
 *    License as published by the Free Software Foundation;
 *    version 2.1 of the License.
 *
 *    This library is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *    Lesser General Public License for more details.
 *
 *    reated on October 27, 2004, 11:27 AM
 */
package org.geotools.filter.function;

import java.lang.reflect.Constructor;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.geotools.factory.CommonFactoryFinder;
import org.geotools.filter.FunctionExpression;
import org.geotools.filter.FunctionFactory;
import org.geotools.filter.FunctionImpl;
import org.geotools.util.logging.Logging;
import org.opengis.filter.FilterFactory;
import org.opengis.filter.capability.FunctionName;
import org.opengis.filter.expression.Expression;
import org.opengis.filter.expression.Function;
import org.opengis.filter.expression.Literal;

/**
 * Filter function facotry that uses the spi lookup mechanism to create functions.
 * 
 * @author Justin Deoliveira, OpenGeo
 *
 *
 * @source $URL: http://svn.osgeo.org/geotools/branches/2.7.x/build/maven/javadoc/../../../modules/library/main/src/main/java/org/geotools/filter/function/DefaultFunctionFactory.java $
 */
public class DefaultFunctionFactory implements FunctionFactory {
    private static final Logger LOGGER = Logging.getLogger("org.geotools.filter");
    private FilterFactory filterFactory = CommonFactoryFinder.getFilterFactory(null);
    
    private Map<String,FunctionDescriptor> functionCache;

    public List<FunctionName> getFunctionNames() {
        ArrayList list = new ArrayList(functionCache().size());
        for (FunctionDescriptor fd : functionCache().values()) {
            list.add(fd.name);
        }
        return list;
    }
    
    public Function function(String name, List<Expression> parameters, Literal fallback) {
        
        // cache lookup
        FunctionDescriptor fd = functionCache().get(functionName(name));
        if (fd == null) {
            //no such function
            return null;
        }
        
        try {
            return fd.newFunction(parameters, fallback);
        }
        catch(Exception e){
            LOGGER.log( Level.FINER, "Unable to create function " + name + "Function", e);
            //just continue on to return null
        }
        
        return null;
    }
    
    private Map<String,FunctionDescriptor> functionCache() {
        if (functionCache == null) {
            synchronized(this) {
                if (functionCache == null) {
                    loadFunctions();
                }
            }
        }
        
        return functionCache;
    }
    
    private void loadFunctions() {
        functionCache = new HashMap();
        
        // Get all the GeoTools FunctionExpression implementations
        // and store in functionExpressionCache
        // (these are implementations of the legacy GeoTools FunctionExpression
        // interface)
        Set functions = CommonFactoryFinder.getFunctionExpressions(null);
        for (Iterator it = functions.iterator(); it.hasNext();) {
            FunctionExpression function = (FunctionExpression) it.next();
            FunctionDescriptor fd = new FunctionDescriptor(
                filterFactory.functionName(function.getName(), function.getArgCount()), 
                function.getClass()); 
            
            functionCache.put(functionName(function.getName()), fd);
        }
        
        // Get all the GeoAPI Function implementations
        functions = CommonFactoryFinder.getFunctions(null);
        for (Iterator i = functions.iterator(); i.hasNext();) {
            Function function = (Function) i.next();
            int argc = function instanceof FunctionExpression ? 
                ((FunctionExpression)function).getArgCount() : function.getParameters().size();
            
            FunctionDescriptor fd = new FunctionDescriptor(
                filterFactory.functionName(function.getName(), argc), function.getClass());
            functionCache.put(functionName(function.getName()), fd);
        }
    }
    
    private String functionName(String name) {
        
        //strip off "Function" prefix
        int index = -1;

        if ((index = name.indexOf("Function")) != -1) {
            name = name.substring(0, index);
        }

        //convert to lower case
        name = name.toLowerCase().trim();
        
        //JD: not sure why the first character is converted back to upper case, disabling this and
        // just keeping everything lower case, as this is only used internally to store functions
        // it should affect any existing clients
        //char c = name.charAt(0);
        //name = name.replaceFirst("" + c, "" + Character.toUpperCase(c));

        return name;
    }

    static class FunctionDescriptor {
        FunctionName name;
        Class clazz;
        
        FunctionDescriptor(FunctionName name, Class clazz) {
            this.name = name;
            this.clazz = clazz;
        }
        
        Function newFunction(List<Expression> parameters, Literal fallback) throws Exception {
            // cache lookup
            if (FunctionExpression.class.isAssignableFrom(clazz)) {
                FunctionExpression function = (FunctionExpression) clazz.newInstance();
                if(parameters != null) {
                    function.setParameters(parameters);
                }
                
                return function;
            }

            if(FunctionImpl.class.isAssignableFrom(clazz)) {
                FunctionImpl function = (FunctionImpl) clazz.newInstance();
                if(parameters != null){
                    function.setParameters( parameters );
                }
                if(fallback != null)
                        function.setFallbackValue( fallback );
                return function;
            }
            
            //Function function = (Function) functionClass.newInstance();
            Constructor<Function> constructor = clazz.getConstructor( new Class[]{ List.class, Literal.class} );
            return constructor.newInstance( parameters, fallback );
        }
    }
}
