/*******************************************************************************
 * 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 java.io.IOException;
import java.util.List;
import org.eclipse.persistence.jpa.jpql.parser.AbstractDoubleEncapsulatedExpression;
import static org.eclipse.persistence.jpa.jpql.parser.AbstractExpression.*;

/**
 * This {@link StateObject} represents a JPQL expression that has a JPQL identifier followed by
 * two an encapsulated expression with parenthesis, the two expression are separated by a comma.
 *
 * <div><b>BNF:</b> <code>expression ::= &lt;identifier&gt;(first_expression, second_expression)</code><p></div>
 *
 * @see ModExpressionStateObject
 * @see NullIfExpressionStateObject
 *
 * @see AbstractDoubleEncapsulatedExpression
 *
 * @version 2.4
 * @since 2.4
 * @author Pascal Filion
 */
@SuppressWarnings("nls")
public abstract class AbstractDoubleEncapsulatedExpressionStateObject extends AbstractEncapsulatedExpressionStateObject {

	/**
	 * The {@link StateObject} representing the first expression.
	 */
	private StateObject firstStateObject;

	/**
	 * The {@link StateObject} representing the second expression.
	 */
	private StateObject secondStateObject;

	/**
	 * Notifies the first {@link StateObject} property has changed.
	 */
	public static final String FIRST_STATE_OBJECT_PROPERTY = "firstStateObject";

	/**
	 * Notifies the second {@link StateObject} property has changed.
	 */
	public static final String SECOND_STATE_OBJECT_PROPERTY = "secondStateObject";

	/**
	 * Creates a new <code>AbstractDoubleEncapsulatedExpressionStateObject</code>.
	 *
	 * @param parent The parent of this state object, which cannot be <code>null</code>
	 * @exception NullPointerException The given parent cannot be <code>null</code>
	 */
	protected AbstractDoubleEncapsulatedExpressionStateObject(StateObject parent) {
		super(parent);
	}

	/**
	 * Creates a new <code>AbstractDoubleEncapsulatedExpressionStateObject</code>.
	 *
	 * @param parent The parent of this state object, which cannot be <code>null</code>
	 * @param firstStateObject The {@link StateObject} representing the first expression
	 * @param secondStateObject The {@link StateObject} representing the second expression
	 * @exception NullPointerException The given parent cannot be <code>null</code>
	 */
	protected AbstractDoubleEncapsulatedExpressionStateObject(StateObject parent,
	                                                          StateObject firstStateObject,
	                                                          StateObject secondStateObject) {

		super(parent);
		this.firstStateObject  = parent(firstStateObject);
		this.secondStateObject = parent(secondStateObject);
	}

	/**
	 * Creates a new <code>AbstractDoubleEncapsulatedExpressionStateObject</code>.
	 *
	 * @param parent The parent of this state object, which cannot be <code>null</code>
	 * @param firstJpqlFragment The string representation of the first encapsulated expression to
	 * parse and to convert into a {@link StateObject} representation
	 * @param secondJpqlFragment The string representation of the second encapsulated expression to
	 * parse and to convert into a {@link StateObject} representation
	 * @exception NullPointerException The given parent cannot be <code>null</code>
	 */
	public AbstractDoubleEncapsulatedExpressionStateObject(StateObject parent,
	                                                       String firstJpqlFragment,
	                                                       String secondJpqlFragment) {

		super(parent);
		parseFirst(firstJpqlFragment);
		parseSecond(secondJpqlFragment);
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	protected void addChildren(List<StateObject> children) {

		super.addChildren(children);

		if (firstStateObject != null) {
			children.add(firstStateObject);
		}

		if (secondStateObject != null) {
			children.add(secondStateObject);
		}
	}

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

	/**
	 * Returns the {@link StateObject} representing the first expression.
	 *
	 * @return The first encapsulated {@link StateObject} or <code>null</code> if none exists
	 */
	public StateObject getFirst() {
		return firstStateObject;
	}

	/**
	 * Returns the unique identifier of the BNF that will be used to parse a JPQL fragment as the
	 * first encapsulated expression.
	 *
	 * @return The query BNF ID for the first encapsulated expression
	 */
	protected abstract String getFirstQueryBNFId();

	/**
	 * Returns the {@link StateObject} representing the second expression.
	 *
	 * @return The second encapsulated {@link StateObject} or <code>null</code> if none exists
	 */
	public StateObject getSecond() {
		return secondStateObject;
	}

	/**
	 * Returns the unique identifier of the BNF that will be used to parse a JPQL fragment as the
	 * second encapsulated expression.
	 *
	 * @return The query BNF ID for the second encapsulated expression
	 */
	protected abstract String getSecondQueryBNFId();

	/**
	 * Determines whether the {@link StateObject} representing the first encapsulated expression is
	 * present or not.
	 *
	 * @return <code>true</code> if the first {@link StateObject} is not <code>null</code>;
	 * <code>false</code> otherwise
	 */
	public boolean hasFirst() {
		return firstStateObject != null;
	}

	/**
	 * Determines whether the {@link StateObject} representing the second encapsulated expression is
	 * present or not.
	 *
	 * @return <code>true</code> if the second {@link StateObject} is not <code>null</code>;
	 * <code>false</code> otherwise
	 */
	public boolean hasSecond() {
		return secondStateObject != null;
	}

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

		if (super.isEquivalent(stateObject)) {
			AbstractDoubleEncapsulatedExpressionStateObject encapsulated = (AbstractDoubleEncapsulatedExpressionStateObject) stateObject;
			return areEquivalent(firstStateObject,  encapsulated.firstStateObject) &&
			       areEquivalent(secondStateObject, encapsulated.secondStateObject);
		}

		return false;
	}

	/**
	 * Parses the given JPQL fragment, which will represent the first encapsulated expression.
	 *
	 * @param jpqlFragment The string representation of the first encapsulated expression to parse and
	 * to convert into a {@link StateObject} representation
	 */
	public void parseFirst(String jpqlFragment) {
		StateObject stateObject = buildStateObject(jpqlFragment, getFirstQueryBNFId());
		setFirst(stateObject);
	}

	/**
	 * Parses the given JPQL fragment, which will represent the second encapsulated expression.
	 *
	 * @param jpqlFragment The string representation of the second encapsulated expression to parse and
	 * to convert into a {@link StateObject} representation
	 */
	public void parseSecond(String jpqlFragment) {
		StateObject stateObject = buildStateObject(jpqlFragment, getSecondQueryBNFId());
		setSecond(stateObject);
	}

	/**
	 * Sets the given {@link StateObject} to represent the first encapsulated expression.
	 *
	 * @param firstStateObject The new encapsulated {@link StateObject} representing the first
	 * expression
	 */
	public void setFirst(StateObject firstStateObject) {
		StateObject oldFirstStateObject = this.firstStateObject;
		this.firstStateObject = parent(firstStateObject);
		firePropertyChanged(FIRST_STATE_OBJECT_PROPERTY, oldFirstStateObject, firstStateObject);
	}

	/**
	 * Sets the given {@link StateObject} to represent the second encapsulated expression.
	 *
	 * @param secondStateObject The new encapsulated {@link StateObject} representing the second
	 * expression
	 */
	public void setSecond(StateObject secondStateObject) {
		StateObject oldSecondStateObject = this.firstStateObject;
		this.secondStateObject = parent(secondStateObject);
		firePropertyChanged(SECOND_STATE_OBJECT_PROPERTY, oldSecondStateObject, secondStateObject);
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	protected void toTextEncapsulatedExpression(Appendable writer) throws IOException {

		if (firstStateObject != null) {
			firstStateObject.toString(writer);
			writer.append(SPACE);
		}

		writer.append(COMMA);

		if (secondStateObject != null) {
			writer.append(SPACE);
			secondStateObject.toString(writer);
		}
	}
}