/*******************************************************************************
 * Copyright (c) 2006, 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.parser;

import java.util.Collection;
import java.util.List;
import org.eclipse.persistence.jpa.jpql.ExpressionTools;
import org.eclipse.persistence.jpa.jpql.JPAVersion;
import org.eclipse.persistence.jpa.jpql.WordParser;

/**
 * A <code>JPQLExpression</code> is the root of the parsed tree representation of a JPQL query. The
 * query is parsed based on what was registered in the {@link JPQLGrammar}'s {@link ExpressionRegistry}.
 * <p>
 * A JPQL statement may be either a <b>SELECT</b> statement, an <b>UPDATE</b> statement, or a
 * <b>DELETE FROM</b> statement.
 *
 * <div><b>BNF:</b> <code>QL_statement ::= {@link SelectStatement select_statement} |
 *                                                {@link UpdateStatement update_statement} |
 *                                                {@link DeleteStatement delete_statement}</code></div>
 * <p>
 * It is possible to parse a portion of a JPQL query. The ID of the {@link JPQLQueryBNF} is used to
 * parse that portion and {@link #getQueryStatement()} then returns only the parsed tree representation
 * of that JPQL fragment.
 *
 * @version 2.5
 * @since 2.3
 * @author Pascal Filion
 */
@SuppressWarnings("nls")
public final class JPQLExpression extends AbstractExpression {

	/**
	 * The JPQL grammar that defines how to parse a JPQL query.
	 */
	private JPQLGrammar jpqlGrammar;

	/**
	 * By default, this is {@link JPQLStatementBNF.ID} but it can be any other unique identifier of
	 * a {@link JPQLQueryBNF} when a portion of a JPQL query needs to be parsed.
	 */
	private String queryBNFId;

	/**
	 * The tree representation of the query.
	 */
	private AbstractExpression queryStatement;

	/**
	 * Determines if the parsing system should be tolerant, meaning if it should try to parse invalid
	 * or incomplete queries.
	 */
	private boolean tolerant;

	/**
	 * If the expression could not be fully parsed, meaning some unknown text is trailing the query,
	 * this will contain it.
	 */
	private AbstractExpression unknownEndingStatement;

	/**
	 * Creates a new <code>JPQLExpression</code>, which is the root of the JPQL parsed tree.
	 *
	 * @param query The string representation of the JPQL query to parse
	 * @param jpqlGrammar The JPQL grammar that defines how to parse a JPQL query
	 */
	public JPQLExpression(CharSequence query, JPQLGrammar jpqlGrammar) {
		this(query, jpqlGrammar, false);
	}

	/**
	 * Creates a new <code>JPQLExpression</code>, which is the root of the JPQL parsed tree.
	 *
	 * @param query The string representation of the JPQL query to parse
	 * @param jpqlGrammar The JPQL grammar that defines how to parse a JPQL query
	 * @param tolerant Determines if the parsing system should be tolerant, meaning if it should try
	 * to parse invalid or incomplete queries
	 */
	public JPQLExpression(CharSequence query, JPQLGrammar jpqlGrammar, boolean tolerant) {
		this(query, jpqlGrammar, JPQLStatementBNF.ID, tolerant);
	}

	/**
	 * Creates a new <code>JPQLExpression</code> that will parse the given fragment of a JPQL query.
	 * This means {@link #getQueryStatement()} will not return a query statement (select, delete or
	 * update) but only the parsed tree representation of the fragment if the query BNF can pare it.
	 * If the fragment of the JPQL query could not be parsed using the given {@link JPQLQueryBNF},
	 * then {@link #getUnknownEndingStatement()} will contain the non-parsable fragment.
	 *
	 * @param jpqlFragment A fragment of a JPQL query, which is a portion of a complete JPQL query
	 * @param jpqlGrammar The JPQL grammar that defines how to parse a JPQL query
	 * @param queryBNFId The unique identifier of the {@link org.eclipse.persistence.jpa.jpql.parser.JPQLQueryBNF JPQLQueryBNF}
	 * @param tolerant Determines if the parsing system should be tolerant, meaning if it should try
	 * to parse invalid or incomplete queries
	 * @since 2.4
	 */
	public JPQLExpression(CharSequence jpqlFragment,
	                      JPQLGrammar jpqlGrammar,
	                      String queryBNFId,
	                      boolean tolerant) {

		this(jpqlGrammar, queryBNFId, tolerant);
		parse(new WordParser(jpqlFragment), tolerant);
	}

	/**
	 * Creates a new <code>JPQLExpression</code>, which is the root of the JPQL parsed tree.
	 *
	 * @param jpqlGrammar The JPQL grammar that defines how to parse a JPQL query
	 * @param tolerant Determines if the parsing system should be tolerant, meaning if it should try
	 * to parse invalid or incomplete queries
	 */
	private JPQLExpression(JPQLGrammar jpqlGrammar, String queryBNFId, boolean tolerant) {
		super(null);
		this.queryBNFId  = queryBNFId;
		this.tolerant    = tolerant;
		this.jpqlGrammar = jpqlGrammar;
	}

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

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

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

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

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

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

	/**
	 * Creates a map of the position of the cursor within each {@link Expression} of the parsed tree.
	 *
	 * @param actualQuery The actual query is a string representation of the query that may contain
	 * extra whitespace
	 * @param position The position of the cursor in the actual query, which is used to retrieve the
	 * deepest {@link Expression}. The position will be adjusted to fit into the beautified version
	 * of the query
	 * @return A new {@link QueryPosition}
	 */
	public QueryPosition buildPosition(String actualQuery, int position) {

		// Adjust the position by not counting extra whitespace
		position = ExpressionTools.repositionCursor(actualQuery, position, toActualText());

		QueryPosition queryPosition = new QueryPosition(position);
		populatePosition(queryPosition, position);
		return queryPosition;
	}

	/**
	 * Returns the deepest {@link Expression} for the given position.
	 *
	 * @param actualQuery The actual query is the text version of the query that may contain extra
	 * whitespace and different formatting than the trim down version generated by the parsed tree
	 * @param position The position in the actual query used to retrieve the {@link Expression}
	 * @return The {@link Expression} located at the given position in the given query
	 */
	public Expression getExpression(String actualQuery, int position) {
		QueryPosition queryPosition = buildPosition(actualQuery, position);
		return queryPosition.getExpression();
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public JPQLGrammar getGrammar() {
		return jpqlGrammar;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public JPAVersion getJPAVersion() {
		return jpqlGrammar.getJPAVersion();
	}

	/**
	 * {@inheritDoc}
	 */
	public JPQLQueryBNF getQueryBNF() {
		return getQueryBNF(queryBNFId);
	}

	/**
	 * Returns the {@link Expression} representing the query, which is either a <b>SELECT</b>, a
	 * <b>DELETE</b> or an <b>UPDATE</b> clause.
	 *
	 * @return The expression representing the Java Persistence query
	 */
	public Expression getQueryStatement() {
		if (queryStatement == null) {
			queryStatement = buildNullExpression();
		}
		return queryStatement;
	}

	/**
	 * Returns the {@link Expression} that may contain a portion of the query that could not be
	 * parsed, this happens when the query is either incomplete or malformed.
	 *
	 * @return The expression used when the ending of the query is unknown or malformed
	 */
	public Expression getUnknownEndingStatement() {
		if (unknownEndingStatement == null) {
			unknownEndingStatement = buildNullExpression();
		}
		return unknownEndingStatement;
	}

	/**
	 * Determines whether a query was parsed. The query may be incomplete but it started with one of
	 * the three clauses (<b>SELECT</b>, <b>DELETE FROM</b>, or <b>UPDATE</b>).
	 *
	 * @return <code>true</code> the query was parsed; <code>false</code> otherwise
	 */
	public boolean hasQueryStatement() {
		return queryStatement != null &&
		      !queryStatement.isNull();
	}

	/**
	 * Determines whether the query that got parsed had some malformed or unknown information.
	 *
	 * @return <code>true</code> if the query could not be parsed correctly
	 * because it is either incomplete or malformed
	 */
	public boolean hasUnknownEndingStatement() {
		return unknownEndingStatement != null &&
		      !unknownEndingStatement.isNull();
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	protected boolean isTolerant() {
		return tolerant;
	}

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

		// Skip leading whitespace
		wordParser.skipLeadingWhitespace();

		// Parse the query, which can be invalid/incomplete or complete and valid
		// Make sure to use this statement if it's a JPQL fragment as well
		if (tolerant || (queryBNFId != JPQLStatementBNF.ID)) {

			// If the query BNF is not the "root" BNF, then we need to parse
			// it with a broader check when parsing
			if (queryBNFId == JPQLStatementBNF.ID) {
				queryStatement = parseUsingExpressionFactory(wordParser, queryBNFId, tolerant);
			}
			else {
				queryStatement = parse(wordParser, queryBNFId, tolerant);
			}

			int count = wordParser.skipLeadingWhitespace();

			// The JPQL query is invalid or incomplete, the remaining will be added
			// to the unknown ending statement
			if ((queryStatement == null) || !wordParser.isTail()) {
				wordParser.moveBackward(count);
				unknownEndingStatement = buildUnknownExpression(wordParser.substring());
			}
			// The JPQL query has some ending whitespace, keep one (used by content assist)
			else if (!wordParser.isTail() || (tolerant && (count > 0))) {
				unknownEndingStatement = buildUnknownExpression(" ");
			}
			// The JPQL query or fragment is invalid
			else if (queryStatement.isUnknown()) {
				unknownEndingStatement = buildUnknownExpression(queryStatement.toParsedText());
				queryStatement = null;
			}
		}
		// Quickly parse the valid query
		else {

			switch (wordParser.character()) {
				case 'd': case 'D': queryStatement = new DeleteStatement(this); break;
				case 'u': case 'U': queryStatement = new UpdateStatement(this); break;
				case 's': case 'S': queryStatement = new SelectStatement(this); break;
			}

			if (queryStatement != null) {
				queryStatement.parse(wordParser, tolerant);
			}
			else {
				queryStatement = parse(wordParser, queryBNFId, tolerant);
			}
		}
	}

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

		if (queryStatement != null) {
			queryStatement.toParsedText(writer, actual);
		}

		if (unknownEndingStatement != null) {
			unknownEndingStatement.toParsedText(writer, actual);
		}
	}
}