/*******************************************************************************
 * 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.WordParser;

/**
 * <div><b>BNF:</b> <code>general_case_expression ::= CASE when_clause {when_clause}* ELSE scalar_expression END</code></div>
 * or
 * <div><b>BNF:</b> <code>simple_case_expression ::= CASE case_operand simple_when_clause {simple_when_clause}* ELSE scalar_expression END</code><p></div>
 *
 * @version 2.5
 * @since 2.3
 * @author Pascal Filion
 */
public final class CaseExpression extends AbstractExpression {

	/**
	 * The actual <b>CASE</b> identifier found in the string representation of the JPQL query.
	 */
	private String caseIdentifier;

	/**
	 * The {@link Expression} representing the case operand.
	 */
	private AbstractExpression caseOperand;

	/**
	 * The {@link Expression} representing the <b>ELSE</b> expression.
	 */
	private AbstractExpression elseExpression;

	/**
	 * The actual <b>ELSE</b> identifier found in the string representation of the JPQL query.
	 */
	private String elseIdentifier;

	/**
	 * The actual <b>END</b> identifier found in the string representation of the JPQL query.
	 */
	private String endIdentifier;

	/**
	 * Determines whether a whitespace was parsed after <b>CASE</b>.
	 */
	private boolean hasSpaceAfterCase;

	/**
	 * Determines whether a whitespace was parsed after the case operand.
	 */
	private boolean hasSpaceAfterCaseOperand;

	/**
	 * Determines whether a whitespace was parsed after <b>ELSE</b>.
	 */
	private boolean hasSpaceAfterElse;

	/**
	 * Determines whether a whitespace was parsed after the <b>BETWEEN</b> expression.
	 */
	private boolean hasSpaceAfterElseExpression;

	/**
	 * Determines whether a whitespace was parsed after <b>WHEN</b> clause.
	 */
	private boolean hasSpaceAfterWhenClauses;

	/**
	 * Used to determine how to check if the parsing is complete.
	 */
	private ParsingType parsingType;

	/**
	 * The {@link Expression} representing the <b>WHEN</b> clauses.
	 */
	private AbstractExpression whenClauses;

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

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

	/**
	 * {@inheritDoc}
	 */
	public void acceptChildren(ExpressionVisitor visitor) {
		getCaseOperand().accept(visitor);
		getWhenClauses().accept(visitor);
		getElseExpression().accept(visitor);
	}

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

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

		// 'CASE'
		children.add(buildStringExpression(CASE));

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

		// Case operand
		if (caseOperand != null) {
			children.add(caseOperand);
		}

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

		// WHEN clauses
		if (whenClauses != null) {
			children.add(whenClauses);
		}

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

		// 'ELSE'
		if (elseIdentifier != null) {
			children.add(buildStringExpression(ELSE));
		}

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

		// Else expression
		if (elseExpression != null) {
			children.add(elseExpression);
		}

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

		// 'END'
		if (endIdentifier != null) {
			children.add(buildStringExpression(END));
		}
	}

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

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

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

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

		return super.findQueryBNF(expression);
	}

	/**
	 * Returns the actual <b>CASE</b> identifier found in the string representation of the JPQL
	 * query, which has the actual case that was used.
	 *
	 * @return The <b>CASE</b> identifier that was actually parsed
	 */
	public String getActualCaseIdentifier() {
		return caseIdentifier;
	}

	/**
	 * Returns the actual <b>ELSE</b> identifier found in the string representation of the JPQL
	 * query, which has the actual case that was used.
	 *
	 * @return The <b>ELSE</b> identifier that was actually parsed, or an empty string if it was not
	 * parsed
	 */
	public String getActualElseIdentifier() {
		return (elseIdentifier != null) ? elseIdentifier : ExpressionTools.EMPTY_STRING;
	}

	/**
	 * Returns the actual <b>END</b> identifier found in the string representation of the JPQL query,
	 * which has the actual case that was used.
	 *
	 * @return The <b>END</b> identifier that was actually parsed, or an empty string if it was not
	 * parsed
	 */
	public String getActualEndIdentifier() {
		return (endIdentifier != null) ? endIdentifier : ExpressionTools.EMPTY_STRING;
	}

	/**
	 * Returns the {@link Expression} that represents the <b>CASE</b> operand.
	 *
	 * @return The expression that was parsed representing the <b>CASE</b> operand
	 */
	public Expression getCaseOperand() {
		if (caseOperand == null) {
			caseOperand = buildNullExpression();
		}
		return caseOperand;
	}

	/**
	 * Returns the {@link Expression} that represents the <b>ELSE</b> operand.
	 *
	 * @return The expression that was parsed representing the <b>ELSE</b> operand
	 */
	public AbstractExpression getElseExpression() {
		if (elseExpression == null) {
			elseExpression = buildNullExpression();
		}
		return elseExpression;
	}

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

	/**
	 * Returns the {@link Expression} that represents the <b>WHEN</b> clauses.
	 *
	 * @return The expression that was parsed representing the <b>WHEN</b> clauses
	 */
	public AbstractExpression getWhenClauses() {
		if (whenClauses == null) {
			whenClauses = buildNullExpression();
		}
		return whenClauses;
	}

	/**
	 * Determines whether the <b>CASE</b> operand was parsed.
	 *
	 * @return <code>true</code> if the <b>CASE</b> operand was parsed; <code>false</code> otherwise
	 */
	public boolean hasCaseOperand() {
		return caseOperand != null &&
		      !caseOperand.isNull();
	}

	/**
	 * Determines whether the identifier <b>ELSE</b> was parsed.
	 *
	 * @return <code>true</code> if the identifier <b>ELSE</b> was parsed; <code>false</code>
	 * otherwise
	 */
	public boolean hasElse() {
		return elseIdentifier != null;
	}

	/**
	 * Determines whether the <b>ELSE</b> expression was parsed.
	 *
	 * @return <code>true</code> if the <b>ELSE</b> expression was parsed; <code>false</code>
	 * otherwise
	 */
	public boolean hasElseExpression() {
		return elseExpression != null &&
		      !elseExpression.isNull();
	}

	/**
	 * Determines whether the identifier <b>END</b> was parsed.
	 *
	 * @return <code>true</code> if the identifier <b>END</b> was parsed; <code>false</code> otherwise
	 */
	public boolean hasEnd() {
		return endIdentifier != null;
	}

	/**
	 * Determines whether a whitespace was parsed after <b>CASE</b>.
	 *
	 * @return <code>true</code> if there was a whitespace after <b>CASE</b>; <code>false</code>
	 * otherwise
	 */
	public boolean hasSpaceAfterCase() {
		return hasSpaceAfterCase;
	}

	/**
	 * Determines whether a whitespace was parsed after the case operand.
	 *
	 * @return <code>true</code> if there was a whitespace after the case operand; <code>false</code>
	 * otherwise
	 */
	public boolean hasSpaceAfterCaseOperand() {
		return hasSpaceAfterCaseOperand;
	}

	/**
	 * Determines whether a whitespace was parsed after <b>ELSE</b>.
	 *
	 * @return <code>true</code> if there was a whitespace after <b>ELSE</b>; <code>false</code>
	 * otherwise
	 */
	public boolean hasSpaceAfterElse() {
		return hasSpaceAfterElse;
	}

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

	/**
	 * Determines whether a whitespace was parsed after the <b>WHEN</b> clause.
	 *
	 * @return <code>true</code> if there was a whitespace after the <b>WHEN</b>
	 * clause; <code>false</code> otherwise
	 */
	public boolean hasSpaceAfterWhenClauses() {
		return hasSpaceAfterWhenClauses;
	}

	/**
	 * Determines whether the <b>WHEN</b> clauses were parsed.
	 *
	 * @return <code>true</code> if the <b>WHEN</b> clauses were parsed;
	 * <code>false</code> otherwise
	 */
	public boolean hasWhenClauses() {
		return whenClauses != null &&
		      !whenClauses.isNull();
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	protected boolean isParsingComplete(WordParser wordParser, String word, Expression expression) {

		// 'CASE'
		if (parsingType == ParsingType.CASE) {
			return word.equalsIgnoreCase(WHEN) ||
			       word.equalsIgnoreCase(ELSE) ||
			       word.equalsIgnoreCase(END)  ||
			       super.isParsingComplete(wordParser, word, expression);
		}

		// 'WHEN'
		if (parsingType == ParsingType.WHEN) {
			return word.equalsIgnoreCase(ELSE) ||
			       word.equalsIgnoreCase(END)  ||
			       super.isParsingComplete(wordParser, word, expression);
		}

		// 'ELSE'
		return word.equalsIgnoreCase(END) ||
		       super.isParsingComplete(wordParser, word, expression);
	}

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

		// Parse 'CASE'
		caseIdentifier = wordParser.moveForward(CASE);
		hasSpaceAfterCase = wordParser.skipLeadingWhitespace() > 0;

		// Parse case operand
		parsingType = ParsingType.CASE;

		if (!wordParser.startsWithIdentifier(WHEN)) {
			caseOperand = parse(wordParser, CaseOperandBNF.ID, tolerant);
			hasSpaceAfterCaseOperand = wordParser.skipLeadingWhitespace() > 0;
		}

		// Parse the WHEN clauses
		parsingType = ParsingType.WHEN;
		whenClauses = parse(wordParser, WhenClauseBNF.ID, tolerant);
		hasSpaceAfterWhenClauses = wordParser.skipLeadingWhitespace() > 0;

		// Parse 'ELSE'
		if (!tolerant || wordParser.startsWithIdentifier(ELSE)) {
			elseIdentifier = wordParser.moveForward(ELSE);
			hasSpaceAfterElse = wordParser.skipLeadingWhitespace() > 0;
		}

		// Parse the ELSE expression
		parsingType = ParsingType.ELSE;
		elseExpression = parse(wordParser, ElseExpressionBNF.ID, tolerant);
		hasSpaceAfterElseExpression = wordParser.skipLeadingWhitespace() > 0;

		// Parse 'END'
		if (!tolerant || wordParser.startsWithIdentifier(END)) {
			endIdentifier = wordParser.moveForward(END);
		}
	}

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

		// 'CASE'
		writer.append(actual ? caseIdentifier : CASE);

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

		// Case Operand
		if (caseOperand != null) {
			caseOperand.toParsedText(writer, actual);
		}

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

		// When clauses
		if (whenClauses != null) {
			whenClauses.toParsedText(writer, actual);
		}

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

		// 'ELSE'
		if (elseIdentifier != null) {
			writer.append(actual ? elseIdentifier : ELSE);
		}

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

		// Else expression
		if (elseExpression != null) {
			elseExpression.toParsedText(writer, actual);
		}

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

		// 'END'
		if (endIdentifier != null) {
			writer.append(actual ? endIdentifier : END);
		}
	}

	/**
	 * A enumeration used to determine how {@link CaseExpression#isParsingComplete(WordParser, String)}
	 * should behaves.
	 */
	private enum ParsingType {
		CASE,
		ELSE,
		WHEN
	}
}