/*
 * Copyright (c) 2012, 2018 Oracle and/or its affiliates. All rights reserved.
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License v. 2.0 which is available at
 * http://www.eclipse.org/legal/epl-2.0,
 * or the Eclipse Distribution License v. 1.0 which is available at
 * http://www.eclipse.org/org/documents/edl-v10.php.
 *
 * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
 */

// Contributors:
//     Oracle - initial API and implementation
//
package org.eclipse.persistence.jpa.jpql.parser;

import org.eclipse.persistence.jpa.jpql.Assert;
import org.eclipse.persistence.jpa.jpql.ExpressionTools;
import org.eclipse.persistence.jpa.jpql.WordParser;

/**
 * This {@link FunctionExpressionFactory} creates a new {@link FunctionExpression} when the portion
 * of the query to parse starts with an identifier related to a SQL function.
 *
 * @see FunctionExpression
 *
 * @version 2.5
 * @since 2.4
 * @author James
 */
@SuppressWarnings("nls")
public final class FunctionExpressionFactory extends ExpressionFactory {

    /**
     * The number of {@link ParameterCount parameters} a {@link FunctionExpression} can have.
     */
    private ParameterCount parameterCount;

    /**
     * The unique identifier of the {@link JPQLQueryBNF} that will be used to parse the arguments of
     * the function expression.
     */
    private String parameterQueryBNFId;

    /**
     * The unique identifier for this {@link FunctionExpressionFactory}.
     */
    public static final String ID = Expression.FUNCTION;

    /**
     * Creates a new <code>FunctionExpressionFactory</code>.
     *
     * @param id The unique identifier of this factory
     * @param parameterCount The number of {@link ParameterCount parameters} a {@link
     * FunctionExpression} can have
     * @param parameterQueryBNFId The unique identifier of the {@link JPQLQueryBNF} that will be used
     * to parse the arguments of the function expression
     * @param identifiers The JPQL identifiers handled by this factory
     */
    public FunctionExpressionFactory(String id,
                                     ParameterCount parameterCount,
                                     String parameterQueryBNFId,
                                     String... identifiers) {

        super(id, identifiers);
        setParameterCount(parameterCount);
        setParameterQueryBNFId(parameterQueryBNFId);
    }

    /**
     * Creates a new <code>FunctionExpressionFactory</code>.
     *
     * @param id The unique identifier of this factory
     * @param identifiers The JPQL identifiers handled by this factory
     */
    public FunctionExpressionFactory(String id, String... identifiers) {
        this(id, ParameterCount.ZERO_OR_MANY, FunctionItemBNF.ID, identifiers);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected AbstractExpression buildExpression(AbstractExpression parent,
                                                 WordParser wordParser,
                                                 String word,
                                                 JPQLQueryBNF queryBNF,
                                                 AbstractExpression expression,
                                                 boolean tolerant) {

        // Search for the constant that is registered with this factory
        String identifier = null;

        for (String possibleIdentifier : identifiers()) {
            if (possibleIdentifier.equalsIgnoreCase(word)) {
                identifier = possibleIdentifier;
                break;
            }
        }

        // No constant was found
        if (identifier == null) {
            return null;
        }

        // Make sure the new function is a real expression and not a variable name.
        // Example: SELECT column FROM Column column
        if (ExpressionTools.isFunctionExpression(wordParser, identifier)) {
            expression = new FunctionExpression(parent, identifier, parameterCount, parameterQueryBNFId);
            expression.parse(wordParser, tolerant);
            return expression;
        }

        return null;
    }

    /**
     * Sets the number of parameters a {@link FunctionExpression} can have, which will be during
     * validation.
     *
     * @param parameterCount The number of parameters
     */
    public void setParameterCount(ParameterCount parameterCount) {
        Assert.isNotNull(parameterCount, "The ParameterCount cannot be null");
        this.parameterCount = parameterCount;
    }

    /**
     * Sets the BNF that will be used when parsing the function's arguments.
     *
     * @param parameterQueryBNFId The unique identifier of the {@link JPQLQueryBNF} that will be used
     * to parse the arguments of the function expression
     */
    public void setParameterQueryBNFId(String parameterQueryBNFId) {
        Assert.isNotNull(parameterQueryBNFId, "The JPQLQueryBNF for the parameters cannot be null");
        this.parameterQueryBNFId = parameterQueryBNFId;
    }

    /**
     * The number of parameters a {@link FunctionExpression} can have.
     */
    public enum ParameterCount {

        /**
         * Only one parameter is allowed.
         */
        ONE,

        /**
         * [1, n] are allowed.
         */
        ONE_OR_MANY,

        /**
         * No parameters are allowed.
         */
        ZERO,

        /**
         * [0, n] are allowed.
         */
        ZERO_OR_MANY,

        /**
         * [0, 1] are allowed.
         */
        ZERO_OR_ONE
    }
}
