/*******************************************************************************
 * Copyright (c) 2011, 2014 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 v1.0 and Eclipse Distribution License v. 1.0
 * which accompanies this distribution.
 * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html
 * and the Eclipse Distribution License is available at
 * http://www.eclipse.org/org/documents/edl-v10.php.
 *
 * Contributors:
 *     Oracle - initial API and implementation
 *
 ******************************************************************************/
package org.eclipse.persistence.jpa.jpql.tools.model.query;

import org.eclipse.persistence.jpa.jpql.Assert;
import org.eclipse.persistence.jpa.jpql.parser.ComparisonExpression;
import org.eclipse.persistence.jpa.jpql.parser.ComparisonExpressionBNF;
import static org.eclipse.persistence.jpa.jpql.parser.Expression.*;

/**
 * Only the values of like types are permitted to be compared. A type is like another type if they
 * correspond to the same Java language type, or if one is a primitive Java language type and the
 * other is the wrapped Java class type equivalent (e.g., int and Integer are like types in this
 * sense).
 * <p>
 * There is one exception to this rule: it is valid to compare numeric values for which the rules of
 * numeric promotion apply. Conditional expressions attempting to compare non-like type values are
 * disallowed except for this numeric case.
 * <p>
 * Note that the arithmetic operators and comparison operators are permitted to be applied to
 * state-fields and input parameters of the wrapped Java class equivalents to the primitive numeric
 * Java types. Two entities of the same abstract schema type are equal if and only if they have the
 * same primary key value. Only equality/inequality comparisons over enumeration constants are
 * required to be supported.
 * <p>
 * <b>JPA 1.0 - BNF:</b>
 * <pre><code>comparison_expression ::= string_expression comparison_operator {string_expression | all_or_any_expression} |
 *                          boolean_expression {=|{@literal <>}} {boolean_expression | all_or_any_expression} |
 *                          enum_expression {=|{@literal <>}} {enum_expression | all_or_any_expression} |
 *                          datetime_expression comparison_operator {datetime_expression | all_or_any_expression} |
 *                          entity_expression {=|{@literal <>}} {entity_expression | all_or_any_expression} |
 *                          arithmetic_expression comparison_operator {arithmetic_expression | all_or_any_expression}</code></pre>
 *
 * <b>JPA 2.0 - BNF:</b>
 * <pre><code>comparison_expression ::= string_expression comparison_operator {string_expression | all_or_any_expression} |
 *                          boolean_expression {=|{@literal <>}} {boolean_expression | all_or_any_expression} |
 *                          enum_expression {=|{@literal <>}} {enum_expression | all_or_any_expression} |
 *                          datetime_expression comparison_operator {datetime_expression | all_or_any_expression} |
 *                          entity_expression {=|{@literal <>}} {entity_expression | all_or_any_expression} |
 *                          arithmetic_expression comparison_operator {arithmetic_expression | all_or_any_expression} |
 *                          <b>entity_type_expression {=|{@literal <>}} entity_type_expression}</b></code></pre>
 *
 * @see ComparisonExpression
 *
 * @version 2.4
 * @since 2.4
 * @author Pascal Filion
 */
@SuppressWarnings("nls")
public class ComparisonExpressionStateObject extends CompoundExpressionStateObject {

	/**
	 * The comparison identifier, which is either {@literal <, <=, =, >=, <>}.
	 */
	private String identifier;

	/**
	 * Notifies the identifier property has changed.
	 */
	public static final String IDENTIFIER_PROPERTY = "identifier";

	/**
	 * Creates a new <code>ComparisonExpressionStateObject</code>.
	 *
	 * @param parent The parent of this state object, which cannot be <code>null</code>
	 * @param leftStateObject The {@link StateObject} representing the left expression
	 * @param identifier The comparison identifier, either {@literal <, <=, =, >=, <>}
	 * @param rightStateObject The {@link StateObject} representing the right expression
	 * @exception NullPointerException The given parent cannot be <code>null</code>
	 */
	public ComparisonExpressionStateObject(StateObject parent,
	                                       StateObject leftStateObject,
	                                       String identifier,
	                                       StateObject rightStateObject) {

		super(parent, leftStateObject, rightStateObject);
		validateIdentifier(identifier);
		this.identifier = identifier;
	}

	/**
	 * Creates a new <code>ComparisonExpressionStateObject</code>.
	 *
	 * @param parent The parent of this state object, which cannot be <code>null</code>
	 * @param identifier The comparison identifier, either {@literal <, <=, =, >=, <>}
	 * @exception NullPointerException The given parent cannot be <code>null</code>
	 */
	public ComparisonExpressionStateObject(StateObject parent, String identifier) {
		super(parent);
		validateIdentifier(identifier);
		this.identifier = identifier;
	}

	/**
	 * Creates a new <code>ComparisonExpressionStateObject</code>.
	 *
	 * @param parent The parent of this state object, which cannot be <code>null</code>
	 * @param leftJpqlFragment The string representation of the left expression to parse and to
	 * convert into a {@link StateObject}
	 * @param identifier The comparison identifier, either {@literal <, <=, =, >=, <>}
	 * @param rightJpqlFragment The string representation of the right expression to parse and to
	 * convert into a {@link StateObject}
	 * @exception NullPointerException The given parent cannot be <code>null</code>
	 */
	public ComparisonExpressionStateObject(StateObject parent,
	                                       String leftJpqlFragment,
	                                       String identifier,
	                                       String rightJpqlFragment) {

		super(parent, leftJpqlFragment, rightJpqlFragment);
		validateIdentifier(identifier);
		this.identifier = identifier;
	}

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

	/**
	 * {@inheritDoc}
	 */
	@Override
	public ComparisonExpression getExpression() {
		return (ComparisonExpression) super.getExpression();
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public String getIdentifier() {
		return identifier;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	protected String getLeftQueryBNFId() {
		return ComparisonExpressionBNF.ID;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	protected String getRightQueryBNFId() {
		return ComparisonExpressionBNF.ID;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public boolean isEquivalent(StateObject stateObject) {

		if (super.isEquivalent(stateObject)) {
			ComparisonExpressionStateObject comparison = (ComparisonExpressionStateObject) stateObject;
			return identifier == comparison.identifier;
		}

		return false;
	}

	/**
	 * Keeps a reference of the {@link ComparisonExpression parsed object} object, which should only
	 * be done when this object is instantiated during the conversion of a parsed JPQL query into
	 * {@link StateObject StateObjects}.
	 *
	 * @param expression The {@link ComparisonExpression parsed object} representing a comparison
	 * expression
	 */
	public void setExpression(ComparisonExpression expression) {
		super.setExpression(expression);
	}

	/**
	 * Sets the comparison identifier to one of the following: {@literal <, <=, =, >=, <>}.
	 *
	 * @param identifier The new comparison identifier, either {@literal <, <=, =, >=, <>}
	 */
	public void setIdentifier(String identifier) {
		validateIdentifier(identifier);
		String oldIdentifier = this.identifier;
		this.identifier = identifier;
		firePropertyChanged(IDENTIFIER_PROPERTY, oldIdentifier, identifier);
	}

	protected void validateIdentifier(String identifier) {
		Assert.isValid(
			identifier,
			"The comparison identifier must be either <, <=, =, >=, <>.",
			LOWER_THAN, LOWER_THAN_OR_EQUAL, EQUAL, GREATER_THAN, GREATER_THAN_OR_EQUAL, DIFFERENT
		);
	}
}