/*******************************************************************************
 * 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 org.eclipse.persistence.jpa.jpql.WordParser;
import static org.eclipse.persistence.jpa.jpql.parser.Expression.*;

/**
 * This {@link JoinFactory} creates a new {@link Join} when the portion of the query to parse starts
 * with <b>JOIN</b> or <b>FETCH JOIN</b>, respectively.
 *
 * @see Join
 *
 * @version 2.5.2
 * @since 2.3
 * @author Pascal Filion
 */
@SuppressWarnings("nls")
public final class JoinFactory extends ExpressionFactory {

	/**
	 * The unique identifier of this {@link JoinFactory}.
	 */
	public static final String ID = JOIN;

	/**
	 * Creates a new <code>JoinFactory</code>.
	 */
	public JoinFactory() {
		super(ID, LEFT,
		          INNER,
		          JOIN,
		          OUTER,
		          FETCH);
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	protected AbstractExpression buildExpression(AbstractExpression parent,
	                                             WordParser wordParser,
	                                             String word,
	                                             JPQLQueryBNF queryBNF,
	                                             AbstractExpression expression,
	                                             boolean tolerant) {

		int index = wordParser.position();

		// TODO: There must be a better way to parse all the JOIN identifiers with a generic
		//       parsing behavior without parsing something like "LEFT JOIN OUTER" has a single
		//       expression but multiple join expressions

		// JOIN and JOIN FETCH
		if (wordParser.startsWithIdentifier(JOIN, index)) {
			index += 4;
			index += wordParser.whitespaceCount(index);

			// JOIN FETCH
			if (wordParser.startsWithIdentifier(FETCH, index)) {
				expression = new Join(parent, JOIN_FETCH);
			}
			// JOIN
			else {
				expression = new Join(parent, JOIN);
			}
		}
		// LEFT
		else if (wordParser.startsWithIdentifier(LEFT)) {
			index += 4;
			index += wordParser.whitespaceCount(index);

			// LEFT OUTER
			if (wordParser.startsWithIdentifier(OUTER, index)) {
				index += 5;
				index += wordParser.whitespaceCount(index);

				if (wordParser.startsWithIdentifier(JOIN, index)) {
					index += 4;
					index += wordParser.whitespaceCount(index);

					// LEFT OUTER JOIN FETCH
					if (wordParser.startsWithIdentifier(FETCH, index)) {
						expression = new Join(parent, LEFT_OUTER_JOIN_FETCH);
					}
					// LEFT OUTER JOIN
					else {
						expression = new Join(parent, LEFT_OUTER_JOIN);
					}
				}
				// LEFT OUTER INNER
				else if (wordParser.startsWithIdentifier(INNER, index)) {
					index += 5;
					index += wordParser.whitespaceCount(index);

					if (wordParser.startsWithIdentifier(JOIN, index)) {
						index += 4;
						index += wordParser.whitespaceCount(index);

						// LEFT OUTER INNER JOIN FETCH
						if (wordParser.startsWithIdentifier(FETCH, index)) {
							expression = new Join(parent, "LEFT OUTER INNER JOIN FETCH");
						}
						// LEFT OUTER INNER JOIN
						else {
							expression = new Join(parent, "LEFT OUTER INNER JOIN");
						}
					}
					else {
						expression = new Join(parent, "LEFT OUTER INNER");
					}
				}
				else {
					expression = new Join(parent, "LEFT OUTER");
				}
			}
			// LEFT JOIN
			else if (wordParser.startsWithIdentifier(JOIN, index)) {
				index += 4;
				index += wordParser.whitespaceCount(index);

				// LEFT JOIN FETCH
				if (wordParser.startsWithIdentifier(FETCH, index)) {
					expression = new Join(parent, LEFT_JOIN_FETCH);
				}
				// LEFT JOIN
				else {
					expression = new Join(parent, LEFT_JOIN);
				}
			}
			// LEFT INNER
			else if (wordParser.startsWithIdentifier(INNER, index)) {
				index += 5;
				index += wordParser.whitespaceCount(index);

				// LEFT INNER JOIN
				if (wordParser.startsWithIdentifier(JOIN, index)) {
					index += 5;
					index += wordParser.whitespaceCount(index);

					// LEFT INNER JOIN FETCH
					if (wordParser.startsWithIdentifier(FETCH, index)) {
						expression = new Join(parent, "LEFT INNER JOIN FETCH");
					}
					// LEFT INNER JOIN
					else {
						expression = new Join(parent, "LEFT INNER JOIN");
					}
				}
			}
			// LEFT
			else {
				expression = new Join(parent, LEFT);
			}
		}
		// INNER JOIN and INNER JOIN FETCH
		else if (wordParser.startsWithIdentifier(INNER, index)) {
			index += 5;
			index += wordParser.whitespaceCount(index);

			if (wordParser.startsWithIdentifier(JOIN, index)) {
				index += 4;
				index += wordParser.whitespaceCount(index);

				// INNER JOIN FETCH
				if (wordParser.startsWithIdentifier(FETCH, index)) {
					expression = new Join(parent, INNER_JOIN_FETCH);
				}
				// INNER JOIN
				else {
					expression = new Join(parent, INNER_JOIN);
				}
			}
			// INNER
			else {
				expression = new Join(parent, INNER);
			}
		}
		// OUTER JOIN and OUTER JOIN FETCH
		// OUTER INNER JOIN and OUTER INNER JOIN FETCH
		else if (wordParser.startsWithIdentifier(OUTER, index)) {
			index += 5;
			index += wordParser.whitespaceCount(index);

			// OUTER JOIN and OUTER JOIN FETCH
			if (wordParser.startsWithIdentifier(JOIN, index)) {
				index += 4;
				index += wordParser.whitespaceCount(index);

				// OUTER JOIN FETCH
				if (wordParser.startsWithIdentifier(FETCH, index)) {
					expression = new Join(parent, "OUTER JOIN FETCH");
				}
				// OUTER JOIN
				else {
					expression = new Join(parent, "OUTER JOIN");
				}
			}
			// OUTER INNER JOIN and OUTER INNER JOIN FETCH
			else if (wordParser.startsWithIdentifier(INNER, index)) {
				index += 5;
				index += wordParser.whitespaceCount(index);

				if (wordParser.startsWithIdentifier(JOIN, index)) {
					index += 4;
					index += wordParser.whitespaceCount(index);

					// OUTER INNER JOIN FETCH
					if (wordParser.startsWithIdentifier(FETCH, index)) {
						expression = new Join(parent, "OUTER INNER JOIN FETCH");
					}
					// INNER JOIN
					else {
						expression = new Join(parent, "OUTER INNER JOIN");
					}
				}
			}
			// OUTER
			else {
				expression = new Join(parent, OUTER);
			}
		}
		else {
			return null;
		}

		expression.parse(wordParser, tolerant);
		return expression;
	}
}