/**
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.activemq.filter;

import java.util.HashMap;
import java.util.List;
import org.apache.activemq.filter.function.FilterFunction;

/**
 * Function call expression for use in selector expressions.  Includes an extensible interface to allow custom
 * functions to be added without changes to the core.
 * <p/>
 * Use registerFunction() to register new function implementations for use in selectors.
 */

public class FunctionCallExpression implements Expression {
    protected static final HashMap<String, functionRegistration> functionRegistry = new HashMap();

    protected String functionName;
    protected java.util.ArrayList arguments;
    protected FilterFunction filterFunc;

    static {
        // Register the built-in functions.  It would be nice to just have each function class register
        //  itself, but that only works when the classes are loaded, which may be never.

        org.apache.activemq.filter.function.BuiltinFunctionRegistry.register();
    }


    /**
     * Register the function with the specified name.
     *
     * @param    name - the function name, as used in selector expressions.  Case Sensitive.
     * @param    impl - class which implements the function interface, including parse-time and evaluation-time
     * operations.
     * @return true - if the function is successfully registered; false - if a function with the same name is
     * already registered.
     */

    public static boolean registerFunction(String name, FilterFunction impl) {
        boolean result;

        result = true;

        synchronized (functionRegistry) {
            if (functionRegistry.containsKey(name))
                result = false;
            else
                functionRegistry.put(name, new functionRegistration(impl));
        }

        return result;
    }

    /**
     * Remove the registration of the function with the specified name.
     * <p/>
     * Note that parsed expressions using this function will still access its implementation after this call.
     *
     * @param    name - name of the function to remove.
     */

    public static void deregisterFunction(String name) {
        synchronized (functionRegistry) {
            functionRegistry.remove(name);
        }
    }


    /**
     * Constructs a function call expression with the named function and argument list.
     * <p/>
     * Use createFunctionCall() to create instances.
     *
     * @exception invalidFunctionExpressionException - if the function name is not valid.
     */

    protected FunctionCallExpression(String func_name, List<Expression> args)
            throws invalidFunctionExpressionException {
        functionRegistration func_reg;

        synchronized (functionRegistry) {
            func_reg = functionRegistry.get(func_name);
        }

        if (func_reg != null) {
            this.arguments = new java.util.ArrayList();
            this.arguments.addAll(args);
            this.functionName = func_name;
            this.filterFunc = func_reg.getFilterFunction();
        } else {
            throw new invalidFunctionExpressionException("invalid function name, \"" + func_name + "\"");
        }
    }


    /**
     * Create a function call expression for the named function and argument list, returning a Boolean function
     * call expression if the function returns a boolean value so that it may be used in boolean contexts.
     * Used by the parser when a function call is identified.  Note that the function call is created after all
     * argument expressions so that the function call can properly detect whether it evaluates to a Boolean value.
     *
     * @param    func_name - name of the function, as used in selectors.
     * @param    args - list of argument expressions passed to the function.
     * @return an instance of a BooleanFunctionCallExpr if the function returns a boolean value in this call,
     * or a FunctionCallExpression otherwise.
     * @exception invalidFunctionExpression - if the function name is not valid, or the given argument list is
     * not valid for the function.
     */

    public static FunctionCallExpression createFunctionCall(String func_name, List<Expression> args)
            throws invalidFunctionExpressionException {
        FunctionCallExpression result;

        //
        // Create a function call expression by default to use with validating the function call
        //  expression and checking whether it returns a boolean result.
        //

        result = new FunctionCallExpression(func_name, args);


        //
        // Check wether the function accepts this expression.  I.E. are the arguments valid?
        //

        if (result.filterFunc.isValid(result)) {
            //
            // If the result of the call is known to alwyas return a boolean value, wrap this
            //  expression as a valid BooleanExpression so it will be accepted as a boolean result
            //  by the selector grammar.
            //

            if (result.filterFunc.returnsBoolean(result))
                result = new BooleanFunctionCallExpr(func_name, args);
        } else {
            //
            // Function does not like this expression.
            //

            throw new invalidFunctionExpressionException("invalid call of function " + func_name);
        }

        return result;
    }


    /**
     * Retrieve the number of arguments for the function call defined in this expression.
     *
     * @return the number of arguments being passed to the function.
     */

    public int getNumArguments() {
        return arguments.size();
    }


    /**
     * Retrieve the argument at the specified index; the first argument is index 0.  Used by implementations of
     * FilterFunction objects to check arguments and evaluate them, as needed.
     *
     * @param    which - number of the argument to retrieve; the first is 0.
     */

    public Expression getArgument(int which) {
        return (Expression) arguments.get(which);
    }


    /**
     * Evaluate the function call expression in the context given.
     *
     * @see    Expression#evaluate
     */

    public Object evaluate(MessageEvaluationContext message_ctx)
            throws jakarta.jms.JMSException {
        return this.filterFunc.evaluate(this, message_ctx);
    }


    /**
     * Translate the expression back into text in a form similar to the input to the selector parser.
     */

    @Override
    public String toString() {
        StringBuilder result;
        boolean first_f;

        result = new StringBuilder();

        result.append(functionName);
        result.append("(");
        first_f = true;

        for (Object arg : arguments) {
            if (first_f)
                first_f = false;
            else
                result.append(", ");

            result.append(arg.toString());
        }

        result.append(")");

        return result.toString();
    }


    ////                         ////
    ////  FUNCTION REGISTRATION  ////
    ////                         ////

    /**
     * Maintain a single function registration.
     */

    protected static class functionRegistration {
        protected FilterFunction filterFunction;

        /**
         * Constructs a function registration for the given function implementation.
         */

        public functionRegistration(FilterFunction func) {
            this.filterFunction = func;
        }


        /**
         * Retrieve the filter function implementation.
         */

        public FilterFunction getFilterFunction() {
            return filterFunction;
        }


        /**
         * Set the filter function implementation for this registration.
         */

        public void setFilterFunction(FilterFunction func) {
            filterFunction = func;
        }
    }


    /**
     * Exception indicating that an invalid function call expression was created, usually by the selector parser.
     * Conditions include invalid function names and invalid function arguments.
     */

    public static class invalidFunctionExpressionException extends java.lang.Exception {
        public invalidFunctionExpressionException(String msg) {
            super(msg);
        }

        public invalidFunctionExpressionException(String msg, java.lang.Throwable cause) {
            super(msg, cause);
        }
    }
}
