/*
 * Copyright (c) 2006, 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 java.util.Collection;
import java.util.List;
import org.eclipse.persistence.jpa.jpql.WordParser;

/**
 * The <code>new_value</code> specified for an update operation must be compatible in type with the
 * field to which it is assigned.
 *
 * <div><b>BNF:</b> <code>update_item ::= [identification_variable.]{state_field | single_valued_association_field} = new_value</code><p></div>
 *
 * @see UpdateClause
 *
 * @version 2.5
 * @since 2.3
 * @author Pascal Filion
 */
public final class UpdateItem extends AbstractExpression {

    /**
     * Determines whether the equal sign was parsed or not.
     */
    private boolean hasEqualSign;

    /**
     * Determines whether a whitespace was parsed after the equal sign or not.
     */
    private boolean hasSpaceAfterEqualSign;

    /**
     * Determines whether a whitespace was parsed before the equal sign or not
     */
    private boolean hasSpaceAfterStateFieldPathExpression;

    /**
     * The expression representing the new value.
     */
    private AbstractExpression newValue;

    /**
     * The expression representing the state field to have its value updated.
     */
    private AbstractExpression stateFieldExpression;

    /**
     * Creates a new <code>UpdateItem</code>.
     *
     * @param parent The parent of this expression
     */
    public UpdateItem(AbstractExpression parent) {
        super(parent);
    }

    /**
     * {@inheritDoc}
     */
    public void accept(ExpressionVisitor visitor) {
        visitor.visit(this);
    }

    /**
     * {@inheritDoc}
     */
    public void acceptChildren(ExpressionVisitor visitor) {
        getStateFieldPathExpression().accept(visitor);
        getNewValue().accept(visitor);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected void addChildrenTo(Collection<Expression> children) {
        children.add(getStateFieldPathExpression());
        children.add(getNewValue());
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected void addOrderedChildrenTo(List<Expression> children) {

        // State field expression
        if (stateFieldExpression != null) {
            children.add(stateFieldExpression);
        }

        if (hasSpaceAfterStateFieldPathExpression) {
            children.add(buildStringExpression(SPACE));
        }

        // '='
        if (hasEqualSign) {
            children.add(buildStringExpression(EQUAL));
        }

        if (hasSpaceAfterEqualSign) {
            children.add(buildStringExpression(SPACE));
        }

        // New value
        if (newValue != null) {
            children.add(newValue);
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public JPQLQueryBNF findQueryBNF(Expression expression) {

        if ((stateFieldExpression != null) && stateFieldExpression.isAncestor(expression)) {
            return getQueryBNF(UpdateItemStateFieldPathExpressionBNF.ID);
        }

        if ((newValue != null) && newValue.isAncestor(expression)) {
            return getQueryBNF(NewValueBNF.ID);
        }

        return super.findQueryBNF(expression);
    }

    /**
     * Returns the {@link Expression} representing the new value, which is the new value of the property.
     *
     * @return The expression for the new value
     */
    public Expression getNewValue() {
        if (newValue == null) {
            newValue = buildNullExpression();
        }
        return newValue;
    }

    /**
     * {@inheritDoc}
     */
    public JPQLQueryBNF getQueryBNF() {
        return getQueryBNF(UpdateItemBNF.ID);
    }

    /**
     * Returns the {@link Expression} representing the state field path expression, which is the
     * property that should get updated.
     *
     * @return The expression for the state field path expression
     */
    public Expression getStateFieldPathExpression() {
        if (stateFieldExpression == null) {
            stateFieldExpression = buildNullExpression();
        }
        return stateFieldExpression;
    }

    /**
     * Determines whether the equal sign was parsed or not.
     *
     * @return <code>true</code> if the equal sign was parsed; <code>false</code> otherwise
     */
    public boolean hasEqualSign() {
        return hasEqualSign;
    }

    /**
     * Determines whether the new value section of the query was parsed.
     *
     * @return <code>true</code> the new value was parsed; <code>false</code> if nothing was parsed
     */
    public boolean hasNewValue() {
        return newValue != null &&
              !newValue.isNull();
    }

    /**
     * Determines whether a whitespace was parsed after the equal sign or not.
     *
     * @return <code>true</code> if there was a whitespace after the equal sign; <code>false</code> otherwise
     */
    public boolean hasSpaceAfterEqualSign() {
        return hasSpaceAfterEqualSign;
    }

    /**
     * Determines whether a whitespace was parsed after the state field path expression not.
     *
     * @return <code>true</code> if there was a whitespace after the state field path expression;
     * <code>false</code> otherwise
     */
    public boolean hasSpaceAfterStateFieldPathExpression() {
        return hasSpaceAfterStateFieldPathExpression;
    }

    /**
     * Determines whether the state field was parsed.
     *
     * @return <code>true</code> the state field was parsed; <code>false</code> otherwise
     */
    public boolean hasStateFieldPathExpression() {
        return stateFieldExpression != null &&
              !stateFieldExpression.isNull();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected boolean isParsingComplete(WordParser wordParser, String word, Expression expression) {
        return word.equals(EQUAL) ||
               super.isParsingComplete(wordParser, word, expression);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected void parse(WordParser wordParser, boolean tolerant) {

        // Parse state field
        if (tolerant) {
            stateFieldExpression = parse(wordParser, UpdateItemStateFieldPathExpressionBNF.ID, tolerant);
        }
        else {
            stateFieldExpression = new StateFieldPathExpression(this, wordParser.word());
            stateFieldExpression.parse(wordParser, tolerant);
        }

        hasSpaceAfterStateFieldPathExpression = wordParser.skipLeadingWhitespace() > 0;

        // Parse '='
        hasEqualSign = wordParser.startsWith(EQUAL);

        if (hasEqualSign) {
            wordParser.moveForward(1);
            hasSpaceAfterEqualSign = wordParser.skipLeadingWhitespace() > 0;
            if (stateFieldExpression != null) {
                hasSpaceAfterStateFieldPathExpression = true;
            }
        }

        // Parse new value
        newValue = parse(wordParser, NewValueBNF.ID, tolerant);

        if (!hasSpaceAfterEqualSign && (newValue != null)) {
            hasSpaceAfterEqualSign = true;
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected void toParsedText(StringBuilder writer, boolean actual) {

        // State field expression
        if (stateFieldExpression != null) {
            stateFieldExpression.toParsedText(writer, actual);
        }

        if (hasSpaceAfterStateFieldPathExpression) {
            writer.append(SPACE);
        }

        // '='
        if (hasEqualSign) {
            writer.append(EQUAL);
        }

        if (hasSpaceAfterEqualSign) {
            writer.append(SPACE);
        }

        // New value
        if (newValue != null) {
            newValue.toParsedText(writer, actual);
        }
    }
}
