package adql.parser.grammar;

/*
 * This file is part of ADQLLibrary.
 *
 * ADQLLibrary is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * ADQLLibrary is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with ADQLLibrary.  If not, see <http://www.gnu.org/licenses/>.
 *
 * Copyright 2019-2020 - UDS/Centre de Données astronomiques de Strasbourg (CDS)
 */

import java.io.InputStream;

import adql.parser.ADQLParser;
import adql.parser.ADQLParser.ADQLVersion;
import adql.parser.ADQLQueryFactory;
import adql.query.ADQLOrder;
import adql.query.ADQLSet;
import adql.query.ClauseADQL;
import adql.query.ClauseConstraints;
import adql.query.ClauseSelect;
import adql.query.from.FromContent;
import adql.query.operand.ADQLOperand;
import adql.query.operand.StringConstant;

/**
 * API of a specific ADQL grammar's parser.
 *
 * <p>
 * 	A concrete implementation of this interface lets parsing a full or partial
 * 	ADQL query. It MUST follow ONLY ONE VERSION of an ADQL standard grammar.
 * </p>
 *
 * <h3>Usage</h3>
 *
 * <p>Here is how to proceed in order to parse an ADQL expression:</p>
 * <ol>
 * 	<li>(Re-)Set the parser with the expression to parse,</li>
 * 	<li>Call the appropriate parsing in function of what should be parsed
 * 		(e.g. {@link #Query()} for a full ADQL query, {@link #Where()} for a
 * 		WHERE clause),</li>
 * 	<li>The corresponding class representation is returned.</li>
 * </ol>
 *
 * <i>
 * <p><b>Example 1:</b> Parse a full ADQL query</p>
 * <pre> // 1. (Re-)Set the parser with the expression to parse:
 * grammarParser.<b>reset(</b>new ByteArrayInputStream("SELECT foo FROM bar WHERE stuff = 1 ORDER BY 1 DESC LIMIT 10".getBytes()));
 *
 * // 2. Call the appropriate parsing:
 * grammarParser.<b>Query()</b>;
 *
 * // 3. Get the result:
 * System.out.println("RESULT: `" + <b>grammarParser.getQuery()</b>.toADQL() + "`")</pre>
 *
 * <p><b>Example 2:</b> Parse just a WHERE clause</p>
 * <pre> // 1. (Re-)Set the parser with the expression to parse:
 * grammarParser.<b>reset(</b>new ByteArrayInputStream("WHERE foo = 'bar' AND stuff = 1".getBytes()));
 *
 * // 2. Call the appropriate parsing:
 * grammarParser.<b>Where()</b>;
 *
 * // 3. Get the result:
 * System.out.println("RESULT: `" + <b>grammarParser.getQuery().getWhere()</b>.toADQL() + "`")</pre>
 * </i>
 *
 * <h3>{@link ADQLGrammar} VS {@link ADQLParser}</h3>
 *
 * <p>
 * 	Implementations of {@link ADQLGrammar} should not be used directly. These
 * 	classes are generally generated by another tool from a grammar (e.g. JavaCC,
 * 	PEG) which makes them quite difficult to use without knowledges on the used
 * 	tool. Thus, this interface aims to simplify use of these grammar parsers.
 * </p>
 *
 * <p>
 * 	ADQL-Lib users should use {@link ADQLParser} instead of direct use of
 * 	{@link ADQLGrammar} implementations. {@link ADQLParser} wraps the
 * 	appropriate {@link ADQLGrammar} implementation in function of the specified
 * 	ADQL Grammar version. It also includes additional tests (e.g. optional
 * 	language features, UDFs, types) as well as some useful tool functions
 * 	(e.g. tokenization, quick fix).
 * </p>
 *
 * @see ADQLParser
 *
 * @author Gr&eacute;gory Mantelet (CDS)
 * @version 2.0 (11/2020)
 * @since 2.0
 */
public interface ADQLGrammar {

	/* **********************************************************************
	   *                         GETTERS/SETTERS                            *
	   ********************************************************************** */

	/**
	 * Get the version of the ADQL Grammar implemented by this parser.
	 *
	 * @return	Implemented ADQL Grammar version. <i>Never NULL</i>
	 */
	public ADQLVersion getVersion();

	/**
	 * Get the {@link ADQLQueryFactory} used by this Grammar Parser to create
	 * ADQL object representations (e.g. SELECT, column, string, constraint)
	 * while building the ADQL query tree
	 *
	 * @return	The used {@link ADQLQueryFactory}. <i>Never NULL</i>
	 */
	public ADQLQueryFactory getQueryFactory();

	/**
	 * Set the {@link ADQLQueryFactory} to use in order to create ADQL object
	 * representations (e.g. SELECT, column, string, constraint) while building
	 * the ADQL query tree.
	 *
	 * @param factory	The {@link ADQLQueryFactory} to use.
	 *
	 * @throws NullPointerException	If the given factory is NULL.
	 */
	public void setQueryFactory(final ADQLQueryFactory factory) throws NullPointerException;

	/* **********************************************************************
	   *                      PARSER INITIALIZATION                         *
	   ********************************************************************** */

	/**
	 * (Re-)Set the ADQL expression to parse.
	 *
	 * <p><i><b>Important note:</b>
	 * 	This function MUST always be called BEFORE calling any parsing function.
	 * </i></p>
	 *
	 * @param inputADQLExpression	ADQL expression to parse.
	 *
	 * @throws NullPointerException	If the given stream is NULL.
	 * @throws Exception			If any error occurs while initializing the
	 *                  			parser.
	 */
	public void reset(final InputStream inputADQLExpression) throws Exception;

	/* **********************************************************************
	   *                        PARSING FUNCTIONS                           *
	   ********************************************************************** */

	/**
	 * Parse the ADQL expression as a single full ADQL query.
	 *
	 * <p><i><b>Important note:</b>
	 * 	This function MUST always be called AFTER {@link #reset(InputStream)}
	 * 	with the ADQL expression to parse.
	 * </i></p>
	 *
	 * @return	The corresponding ADQL tree.
	 *
	 * @throws ParseException	If the parsing failed.
	 */
	public ADQLSet Query() throws ParseException;

	/**
	 * Parse the ADQL expression as a single <code>SELECT</code> clause.
	 *
	 * <p><i><b>Important note:</b>
	 * 	This function MUST always be called AFTER {@link #reset(InputStream)}
	 * 	with the ADQL expression to parse.
	 * </i></p>
	 *
	 * @throws ParseException	If the parsing failed.
	 */
	public ClauseSelect Select() throws ParseException;

	/**
	 * Parse the ADQL expression as a single <code>FROM</code> clause.
	 *
	 * <p><i><b>Important note:</b>
	 * 	This function MUST always be called AFTER {@link #reset(InputStream)}
	 * 	with the ADQL expression to parse.
	 * </i></p>
	 *
	 * @throws ParseException	If the parsing failed.
	 */
	public FromContent From() throws ParseException;

	/**
	 * Parse the ADQL expression as a single <code>WHERE</code> clause.
	 *
	 * <p><i><b>Important note:</b>
	 * 	This function MUST always be called AFTER {@link #reset(InputStream)}
	 * 	with the ADQL expression to parse.
	 * </i></p>
	 *
	 * @throws ParseException	If the parsing failed.
	 */
	public ClauseConstraints Where() throws ParseException;

	/**
	 * Parse the ADQL expression as a single <code>ORDER BY</code> clause.
	 *
	 * <p><i><b>Important note:</b>
	 * 	This function MUST always be called AFTER {@link #reset(InputStream)}
	 * 	with the ADQL expression to parse.
	 * </i></p>
	 *
	 * @throws ParseException	If the parsing failed.
	 */
	public ClauseADQL<ADQLOrder> OrderBy() throws ParseException;

	/**
	 * Parse the ADQL expression as a single <code>GROUP BY</code> clause.
	 *
	 * <p><i><b>Important note:</b>
	 * 	This function MUST always be called AFTER {@link #reset(InputStream)}
	 * 	with the ADQL expression to parse.
	 * </i></p>
	 *
	 * @throws ParseException	If the parsing failed.
	 */
	public ClauseADQL<ADQLOperand> GroupBy() throws ParseException;

	/**
	 * Parse the ADQL expression as a single string constant.
	 *
	 * <p><i><b>Important note:</b>
	 * 	This function MUST always be called AFTER {@link #reset(InputStream)}
	 * 	with the ADQL expression to parse.
	 * </i></p>
	 *
	 * <p><i><b>Implementation note:</b>
	 * 	This function is available here just for Unitary Test purpose.
	 * </i></p>
	 *
	 * @return	The parsed {@link StringConstant}.
	 *
	 * @throws ParseException	If the parsing failed.
	 */
	public ADQLOperand StringExpression() throws ParseException;

	/* **********************************************************************
	   *                         TOKEN KIND TESTS                           *
	   ********************************************************************** */

	public boolean isEOF(final Token token);

	public boolean isEOQ(final Token token);

	public boolean isRegularIdentifierCandidate(final Token token);

	public boolean isSQLReservedWord(final Token token);

	public boolean isLeftPar(final Token token);

	/**
	* Tell whether the given token represents the end of an ADQL query.
	*
	* <p><i><b>Implementation note:</b>
	* 	NULL is considered as a last token of a set. So, by extension it should
	* 	also be considered as the end of an ADQL query. But this method should
	* 	rely on the ADQL grammar. Consequently, at least the token for the
	* 	semi-colon (;) and the token for the End-Of-File (EOF) should be
	* 	considered as the correct one to use to indicate the end of an ADQL
	* 	query.
	* </i></p>
	*
	* @param token	Token to analyze. <i>Might be NULL.</i>
	*
	* @return	<code>true</code> if the given token represents a query end,
	*        	<code>false</code> otherwise.
	*/
	public boolean isEnd(final Token token);

	/* **********************************************************************
	   *                        TOKENIZATION HELPER                         *
	   ********************************************************************** */

	/**
	 * Get an API giving access to the result of the tokenization of the given
	 * ADQL expression.
	 *
	 * @param expr	The ADQL expression to tokenize.
	 *
	 * @return	A {@link Tokenizer} object helping to iterate over the tokens of
	 *        	the given ADQL expression.
	 *
	 * @throws NullPointerException	If the given ADQL expression is NULL.
	 */
	public Tokenizer getTokenizer(final String expr) throws NullPointerException;

	/**
	 * API helping to iterate over a set of {@link Token}s.
	 *
	 * @author Gr&eacute;gory Mantelet (CDS)
	 * @version 2.0 (08/2019)
	 * @since 2.0
	 */
	public static interface Tokenizer {
		/**
		 * Get the next available token.
		 *
		 * <p>
		 * 	The last available {@link Token} should
		 *
		 * @return	The next token.
		 */
		public Token nextToken();
	}

	/* **********************************************************************
	   *                     REGULAR IDENTIFIER TEST                        *
	   ********************************************************************** */

	/**
	 * Tell whether the given string is a valid ADQL regular identifier.
	 *
	 * <p>
	 * 	According to the ADQL-2.0's BNF, a regular identifier (i.e. not
	 * 	delimited ; not between double quotes) must be a letter followed by a
	 * 	letter, digit or underscore. So, the following regular expression:
	 * </p>
	 *
	 * <pre>[a-zA-Z]+[a-zA-Z0-9_]*</pre>
	 *
	 * <p>
	 * 	This is what this function tests on the given string.
	 * </p>
	 *
	 * <p><i><b>Warning:</b>
	 * 	This function may return a different result for different versions of
	 * 	the ADQL grammar.
	 * </i></p>
	 *
	 * @param idCandidate	The string to test.
	 *
	 * @return	<code>true</code> if the given string is a valid regular
	 *        	identifier,
	 *        	<code>false</code> otherwise.
	 *
	 * @see #testRegularIdentifier(Token)
	 */
	public boolean isRegularIdentifier(final String idCandidate);

	/**
	 * Test the given token as an ADQL's regular identifier.
	 *
	 * <p><i><b>Implementation note:</b>
	 * 	This function uses {@link #isRegularIdentifier(String)} to test the
	 * 	given token's image. If the test fails, a {@link ParseException} is
	 * 	thrown.
	 * </i></p>
	 *
	 * @param token	The token to test.
	 *
	 * @throws ParseException	If the given token is not a valid ADQL regular
	 *                       	identifier.
	 *
	 * @see #isRegularIdentifier(String)
	 */
	public void testRegularIdentifier(final Token token) throws ParseException;

	/* **********************************************************************
	   *                     DEBUG & ERRORS MANAGEMENT                      *
	   ********************************************************************** */

	/**
	 * Generate a {@link ParseException} instance representing the given
	 * {@link Exception}.
	 *
	 * <p><i><b>Implementation note:</b>
	 * 	If the given {@link Exception} is already a {@link ParseException} this
	 * 	function should do nothing else than returning it as such.
	 * </i></p>
	 *
	 * @param ex	The exception to represent as a {@link ParseException}.
	 *
	 * @return	The corresponding ParseException.
	 */
	public ParseException generateParseException(final Exception ex);

	/**
	 * Enable the deep tracing of the Grammar Parser.
	 */
	public void enable_tracing();

	/**
	 * Disable the deep tracing of the Grammar Parser.
	 */
	public void disable_tracing();

}
