/*
 * Copyright (c) 2011, 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.tools.model;

import java.util.Collections;
import java.util.List;
import org.eclipse.persistence.jpa.jpql.ExpressionTools;
import org.eclipse.persistence.jpa.jpql.parser.TrimExpression.Specification;
import org.eclipse.persistence.jpa.jpql.tools.model.query.AllOrAnyExpressionStateObject;
import org.eclipse.persistence.jpa.jpql.tools.model.query.AndExpressionStateObject;
import org.eclipse.persistence.jpa.jpql.tools.model.query.BetweenExpressionStateObject;
import org.eclipse.persistence.jpa.jpql.tools.model.query.CollectionMemberExpressionStateObject;
import org.eclipse.persistence.jpa.jpql.tools.model.query.ComparisonExpressionStateObject;
import org.eclipse.persistence.jpa.jpql.tools.model.query.EmptyCollectionComparisonExpressionStateObject;
import org.eclipse.persistence.jpa.jpql.tools.model.query.ExistsExpressionStateObject;
import org.eclipse.persistence.jpa.jpql.tools.model.query.InExpressionStateObject;
import org.eclipse.persistence.jpa.jpql.tools.model.query.KeywordExpressionStateObject;
import org.eclipse.persistence.jpa.jpql.tools.model.query.LikeExpressionStateObject;
import org.eclipse.persistence.jpa.jpql.tools.model.query.LowerExpressionStateObject;
import org.eclipse.persistence.jpa.jpql.tools.model.query.NullComparisonExpressionStateObject;
import org.eclipse.persistence.jpa.jpql.tools.model.query.OrExpressionStateObject;
import org.eclipse.persistence.jpa.jpql.tools.model.query.SimpleSelectStatementStateObject;
import org.eclipse.persistence.jpa.jpql.tools.model.query.StateObject;
import org.eclipse.persistence.jpa.jpql.tools.model.query.SubExpressionStateObject;
import org.eclipse.persistence.jpa.jpql.tools.model.query.SubstringExpressionStateObject;
import org.eclipse.persistence.jpa.jpql.tools.model.query.TrimExpressionStateObject;
import org.eclipse.persistence.jpa.jpql.tools.model.query.UpperExpressionStateObject;
import static org.eclipse.persistence.jpa.jpql.parser.Expression.*;

/**
 * The abstract implementation of {@link IConditionalExpressionStateObjectBuilder} that supports the
 * creation of the conditional expression based on the JPQL grammar defined in JPA 2.0.
 *
 * @version 2.4
 * @since 2.4
 * @author Pascal Filion
 */
@SuppressWarnings("unchecked")
public abstract class AbstractConditionalExpressionStateObjectBuilder<T extends IAbstractConditionalExpressionStateObjectBuilder<T>>
    extends AbstractScalarExpressionStateObjectBuilder<T>
   implements IAbstractConditionalExpressionStateObjectBuilder<T> {

    /**
     * Creates a new <code>AbstractStateObjectBuilder</code>.
     *
     * @param parent The parent of the expression to build, which is only required when a JPQL
     * fragment needs to be parsed
     */
    public AbstractConditionalExpressionStateObjectBuilder(StateObject parent) {
        super(parent);
    }

    /**
     * {@inheritDoc}
     */
    public T all(SimpleSelectStatementStateObject subquery) {
        allOrAny(ALL, subquery);
        return (T) this;
    }

    protected void allOrAny(String identifier, SimpleSelectStatementStateObject subquery) {
        StateObject stateObject = new AllOrAnyExpressionStateObject(getParent(), identifier, subquery);
        add(stateObject);
    }

    /**
     * {@inheritDoc}
     */
    public T and(T builder) {

        checkBuilder(builder);

        StateObject rightStateObject = pop();
        StateObject leftStateObject  = pop();

        StateObject stateObject = new AndExpressionStateObject(
            getParent(),
            leftStateObject,
            rightStateObject
        );

        add(stateObject);
        return (T) this;
    }

    /**
     * {@inheritDoc}
     */
    public T any(SimpleSelectStatementStateObject subquery) {
        allOrAny(ANY, subquery);
        return (T) this;
    }

    protected void between(boolean not) {

        StateObject upperBoundStateObject  = pop();
        StateObject lowerBoundStateObject  = pop();
        StateObject firstStateObject       = pop();

        StateObject stateObject = new BetweenExpressionStateObject(
            getParent(),
            firstStateObject,
            not,
            lowerBoundStateObject,
            upperBoundStateObject
        );

        add(stateObject);
    }

    /**
     * {@inheritDoc}
     */
    public T between(T lowerBoundExpression, T upperBoundExpression) {
        checkBuilders(lowerBoundExpression, upperBoundExpression);
        between(false);
        return (T) this;
    }

    /**
     * {@inheritDoc}
     */
    public T collectionPath(String path) {
        StateObject stateObject = buildCollectionPath(path);
        add(stateObject);
        return (T) this;
    }

    protected void comparison(String identifier) {
        comparison(identifier, pop());
    }

    protected void comparison(String identifier, StateObject rightStateObject) {

        StateObject leftStateObject = pop();

        StateObject stateObject = new ComparisonExpressionStateObject(
            getParent(),
            leftStateObject,
            identifier,
            rightStateObject
        );

        add(stateObject);
    }

    /**
     * {@inheritDoc}
     */
    public T different(Number number) {
        comparison(DIFFERENT, buildNumeric(number));
        return (T) this;
    }

    /**
     * {@inheritDoc}
     */
    public T different(String literal) {
        comparison(DIFFERENT, literal(literal));
        return (T) this;
    }

    /**
     * {@inheritDoc}
     */
    public T different(T builder) {
        checkBuilder(builder);
        comparison(DIFFERENT);
        return (T) this;
    }

    /**
     * {@inheritDoc}
     */
    public T equal(Number number) {
        comparison(EQUAL, buildNumeric(number));
        return (T) this;
    }

    /**
     * {@inheritDoc}
     */
    public T equal(String literal) {
        comparison(EQUAL, literal(literal));
        return (T) this;
    }

    /**
     * {@inheritDoc}
     */
    public T equal(T builder) {
        checkBuilder(builder);
        comparison(EQUAL);
        return (T) this;
    }

    protected void exists(boolean not, SimpleSelectStatementStateObject subquery) {
        StateObject stateObject = new ExistsExpressionStateObject(getParent(), not, subquery);
        add(stateObject);
    }

    /**
     * {@inheritDoc}
     */
    public T exists(SimpleSelectStatementStateObject subquery) {
        exists(false, subquery);
        return (T) this;
    }

    /**
     * {@inheritDoc}
     */
    public T FALSE() {
        keyword(FALSE);
        return (T) this;
    }

    /**
     * {@inheritDoc}
     */
    public T greaterThan(Number number) {
        comparison(GREATER_THAN, buildNumeric(number));
        return (T) this;
    }

    /**
     * {@inheritDoc}
     */
    public T greaterThan(String literal) {
        comparison(GREATER_THAN, literal(literal));
        return (T) this;
    }

    /**
     * {@inheritDoc}
     */
    public T greaterThan(T builder) {
        checkBuilder(builder);
        comparison(GREATER_THAN);
        return (T) this;
    }

    /**
     * {@inheritDoc}
     */
    public T greaterThanOrEqual(Number number) {
        comparison(GREATER_THAN_OR_EQUAL, buildNumeric(number));
        return (T) this;
    }

    /**
     * {@inheritDoc}
     */
    public T greaterThanOrEqual(String literal) {
        comparison(GREATER_THAN_OR_EQUAL, literal(literal));
        return (T) this;
    }

    /**
     * {@inheritDoc}
     */
    public T greaterThanOrEqual(T builder) {
        checkBuilder(builder);
        comparison(GREATER_THAN_OR_EQUAL);
        return (T) this;
    }

    protected void in(boolean not, List<StateObject> inItems) {

        StateObject stateFieldPath = pop();

        StateObject stateObject = new InExpressionStateObject(
            getParent(),
            stateFieldPath,
            not,
            inItems
        );

        add(stateObject);
    }

    protected void in(boolean not, String... inItems) {
        in(not, literals(inItems));
    }

    protected void in(boolean not, T... inItems) {
        in(false, stateObjects(inItems));
    }

    /**
     * {@inheritDoc}
     */
    public T in(SimpleSelectStatementStateObject subquery) {
        in(false, Collections.<StateObject>singletonList(subquery));
        return (T) this;
    }

    /**
     * {@inheritDoc}
     */
    public T in(String... inItems) {
        in(false, inItems);
        return (T) this;
    }

    /**
     * {@inheritDoc}
     */
    public T in(T... inItems) {
        checkBuilders(inItems);
        in(false, inItems);
        return (T) this;
    }

    protected void isEmpty(boolean not, String path) {

        StateObject stateObject = new EmptyCollectionComparisonExpressionStateObject(
            getParent(),
            not,
            path
        );

        add(stateObject);
    }

    /**
     * {@inheritDoc}
     */
    public T isEmpty(String path) {
        isEmpty(false, path);
        return (T) this;
    }

    /**
     * {@inheritDoc}
     */
    public T isNotEmpty(String path) {
        isEmpty(true, path);
        return (T) this;
    }

    /**
     * {@inheritDoc}
     */
    public T isNotNull(String path) {
        isNull(true, path);
        return (T) this;
    }

    protected void isNull(boolean not, String path) {

        StateObject stateObject;

        if (ExpressionTools.isParameter(path.charAt(0))) {
            stateObject = buildInputParameter(path);
        }
        else {
            stateObject = buildStateFieldPath(path);
        }

        stateObject = new NullComparisonExpressionStateObject(
            getParent(),
            not,
            stateObject
        );

        add(stateObject);
    }

    /**
     * {@inheritDoc}
     */
    public T isNull(String path) {
        isNull(false, path);
        return (T) this;
    }

    protected void keyword(String identifier) {
        StateObject stateObject = new KeywordExpressionStateObject(getParent(), identifier);
        add(stateObject);
    }

    protected void like(boolean not, String escapeCharacter) {

        StateObject patternValue = pop();
        StateObject string       = pop();

        StateObject stateObject = new LikeExpressionStateObject(
            getParent(),
            string,
            not,
            patternValue,
            escapeCharacter
        );

        add(stateObject);
    }

    /**
     * {@inheritDoc}
     */
    public T like(String patternValue) {
        like(string(patternValue));
        return (T) this;
    }

    /**
     * {@inheritDoc}
     */
    public T like(T patternValue) {
        checkBuilder(patternValue);
        like(false, null);
        return (T) this;
    }

    /**
     * {@inheritDoc}
     */
    public T like(T patternValue, String escapeCharacter) {
        checkBuilder(patternValue);
        like(false, escapeCharacter);
        return (T) this;
    }

    /**
     * {@inheritDoc}
     */
    public T lower(T builder) {
        checkBuilder(builder);
        StateObject stateObject = new LowerExpressionStateObject(getParent(), pop());
        add(stateObject);
        return (T) this;
    }

    /**
     * {@inheritDoc}
     */
    public T lowerThan(Number number) {
        comparison(LOWER_THAN, buildNumeric(number));
        return (T) this;
    }

    /**
     * {@inheritDoc}
     */
    public T lowerThan(String literal) {
        comparison(LOWER_THAN, literal(literal));
        return (T) this;
    }

    /**
     * {@inheritDoc}
     */
    public T lowerThan(T builder) {
        checkBuilder(builder);
        comparison(LOWER_THAN);
        return (T) this;
    }

    /**
     * {@inheritDoc}
     */
    public T lowerThanOrEqual(Number number) {
        comparison(LOWER_THAN_OR_EQUAL, buildNumeric(number));
        return (T) this;
    }

    /**
     * {@inheritDoc}
     */
    public T lowerThanOrEqual(String literal) {
        comparison(LOWER_THAN_OR_EQUAL, literal(literal));
        return (T) this;
    }

    /**
     * {@inheritDoc}
     */
    public T lowerThanOrEqual(T builder) {
        checkBuilder(builder);
        comparison(LOWER_THAN_OR_EQUAL);
        return (T) this;
    }

    protected void member(boolean not, boolean of, String collectionValuedPathExpression) {

        StateObject entity = pop();

        StateObject stateObject = new CollectionMemberExpressionStateObject(
            getParent(),
            entity,
            not,
            of,
            collectionValuedPathExpression
        );

        add(stateObject);
    }

    /**
     * {@inheritDoc}
     */
    public T member(String path) {
        member(false, false, path);
        return (T) this;
    }

    /**
     * {@inheritDoc}
     */
    public T memberOf(String path) {
        member(false, true, path);
        return (T) this;
    }

    /**
     * {@inheritDoc}
     */
    public T notBetween(T lowerBoundExpression, T upperBoundExpression) {
        checkBuilders(lowerBoundExpression, upperBoundExpression);
        between(true);
        return (T) this;
    }

    /**
     * {@inheritDoc}
     */
    public T notExists(SimpleSelectStatementStateObject subquery) {
        exists(true, subquery);
        return (T) this;
    }

    /**
     * {@inheritDoc}
     */
    public T notIn(SimpleSelectStatementStateObject subquery) {
        in(true, Collections.<StateObject>singletonList(subquery));
        return (T) this;
    }

    /**
     * {@inheritDoc}
     */
    public T notIn(String... inItems) {
        in(true, inItems);
        return (T) this;
    }

    /**
     * {@inheritDoc}
     */
    public T notIn(T... inItems) {
        checkBuilders(inItems);
        in(true, inItems);
        return (T) this;
    }

    /**
     * {@inheritDoc}
     */
    public T notLike(String patternValue) {
        notLike(string(patternValue));
        return (T) this;
    }

    /**
     * {@inheritDoc}
     */
    public T notLike(T builder) {
        checkBuilder(builder);
        like(true, null);
        return (T) this;
    }

    /**
     * {@inheritDoc}
     */
    public T notLike(T builder, String escapeCharacter) {
        checkBuilder(builder);
        like(true, escapeCharacter);
        return (T) this;
    }

    /**
     * {@inheritDoc}
     */
    public T notMember(String path) {
        member(true, false, path);
        return (T) this;
    }

    /**
     * {@inheritDoc}
     */
    public T notMemberOf(String path) {
        member(true, true, path);
        return (T) this;
    }

    /**
     * {@inheritDoc}
     */
    public T NULL() {
        keyword(NULL);
        return (T) this;
    }

    /**
     * {@inheritDoc}
     */
    public T or(T builder) {

        checkBuilder(builder);

        StateObject rightStateObject = pop();
        StateObject leftStateObject  = pop();

        StateObject stateObject = new OrExpressionStateObject(
            getParent(),
            leftStateObject,
            rightStateObject
        );

        add(stateObject);
        return (T) this;
    }

    /**
     * {@inheritDoc}
     */
    public T some(SimpleSelectStatementStateObject subquery) {
        allOrAny(SOME, subquery);
        return (T) this;
    }

    /**
     * {@inheritDoc}
     */
    public T sub(StateObject stateObject) {
        stateObject = new SubExpressionStateObject(getParent(), stateObject);
        add(stateObject);
        return (T) this;
    }

    /**
     * {@inheritDoc}
     */
    public T substring(T parameter1, T parameter2, T parameter3) {

        checkBuilders(parameter1, parameter2, parameter3);

        StateObject thirdStateObject  = pop();
        StateObject secondStateObject = pop();
        StateObject firstStateObject  = pop();

        StateObject stateObject = new SubstringExpressionStateObject(
            getParent(),
            firstStateObject,
            secondStateObject,
            thirdStateObject
        );

        add(stateObject);
        return (T) this;
    }

    /**
     * {@inheritDoc}
     */
    public T trim(Specification specification, String trimCharacter, T builder) {

        checkBuilder(builder);
        StateObject stateObject = pop();

        stateObject = new TrimExpressionStateObject(
            getParent(),
            specification,
            ExpressionTools.stringIsNotEmpty(trimCharacter) ? literal(trimCharacter) : null,
            stateObject
        );

        add(stateObject);
        return (T) this;
    }

    /**
     * {@inheritDoc}
     */
    public T trim(Specification specification, T builder) {
        return trim(specification, null, builder);
    }

    /**
     * {@inheritDoc}
     */
    public T TRUE() {
        keyword(TRUE);
        return (T) this;
    }

    /**
     * {@inheritDoc}
     */
    public T upper(T builder) {
        checkBuilder(builder);
        StateObject stateObject = new UpperExpressionStateObject(getParent(), pop());
        add(stateObject);
        return (T) this;
    }

    /**
     * {@inheritDoc}
     */
    public T variable(String variable) {
        StateObject stateObject = buildIdentificationVariable(variable);
        add(stateObject);
        return (T) this;
    }
}
