/*
 * Copyright (c) 1998, 2018 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 v. 2.0 which is available at
 * http://www.eclipse.org/legal/epl-2.0,
 * or the Eclipse Distribution License v. 1.0 which is available at
 * http://www.eclipse.org/org/documents/edl-v10.php.
 *
 * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
 */

// Contributors:
//     Oracle - initial API and implementation from Oracle TopLink
package org.eclipse.persistence.internal.jpa.parsing.jpql;

import java.util.ArrayList;
import java.util.List;

//toplink imports
import org.eclipse.persistence.exceptions.JPQLException;
import org.eclipse.persistence.internal.jpa.parsing.JPQLParseTree;
import org.eclipse.persistence.internal.jpa.parsing.NodeFactory;
import org.eclipse.persistence.internal.jpa.parsing.NodeFactoryImpl;
import org.eclipse.persistence.internal.jpa.parsing.jpql.antlr.JPQLParserBuilder;
// Third party (ANLTR) stuff
import org.eclipse.persistence.internal.libraries.antlr.runtime.MismatchedTokenException;
import org.eclipse.persistence.internal.libraries.antlr.runtime.NoViableAltException;
import org.eclipse.persistence.internal.libraries.antlr.runtime.RecognitionException;
import org.eclipse.persistence.internal.libraries.antlr.runtime.RecognizerSharedState;
import org.eclipse.persistence.internal.libraries.antlr.runtime.Token;
import org.eclipse.persistence.internal.libraries.antlr.runtime.TokenStream;

/**
 * EJBQLParser is the superclass of the ANTLR generated parser.
 */
public abstract class JPQLParser extends org.eclipse.persistence.internal.libraries.antlr.runtime.Parser {

    /** List of errors. */
    private List errors = new ArrayList();

    /** The name of the query being compiled.
     *  The variable is null for dynamic queries.
     */
    private String queryName = null;

    /** The text of the query being compiled. */
    private String queryText = null;

    /** The factory to create parse tree nodes. */
    protected NodeFactory factory;

    protected JPQLParser(TokenStream stream) {
        super(stream);
    }

    public JPQLParser(TokenStream input, RecognizerSharedState state) {
        super(input, state);
    }

    /**
     * INTERNAL
     * Returns the ANTLR version currently used.
     */
    public static String ANTLRVersion() throws Exception {
        return "3.5.2";
    }

    /**
     * INTERNAL
     * Builds a parser, parses the specified query string and returns the
     * parse tree. Any error in the query text results in an JPQLException.
     * This method is used for dynamic queries.
     */
    public static JPQLParseTree buildParseTree(String queryText)
        throws JPQLException {
        return buildParseTree(null, queryText);
    }

    /**
     * INTERNAL
     * Builds a parser, parses the specified query string and returns the
     * parse tree. Any error in the query text results in an JPQLException.
     */
    public static JPQLParseTree buildParseTree(String queryName, String queryText)
        throws JPQLException {
        JPQLParser parser = buildParserFor(queryName, queryText);
        return parser.parse();
    }

    /**
     * INTERNAL
     * Creates a parser for the specified query string. The query string is
     * not parsed (see method parse).
     * This method is used for dynamic queries.
     */
    public static JPQLParser buildParserFor(String queryText)
        throws JPQLException {
        return buildParserFor(null, queryText);
    }

    /**
     * INTERNAL
     * Creates a parser for the specified query string. The query string is
     * not parsed (see method parse).
     */
    public static JPQLParser buildParserFor(String queryName, String queryText)
        throws JPQLException {
        try {
            JPQLParser parser = JPQLParserBuilder.buildParser(queryText);
            parser.setQueryName(queryName);
            parser.setQueryText(queryText);
            parser.setNodeFactory(new NodeFactoryImpl(parser.getQueryInfo()));
            return parser;
        } catch (Exception ex) {
            throw JPQLException.generalParsingException(queryText, ex);
        }
    }

    /**
     * INTERNAL
     * Parse the query string that was specified on parser creation.
     */
    public JPQLParseTree parse()
        throws JPQLException {
        try {
            document();
        } catch (Exception e) {
            addError(e);
        }

        // Handle any errors generated by the Parser
        if (hasErrors()) {
            throw generateException();
        }

        // return the parser tree
        return getParseTree();
    }

    /**
     * INTERNAL
     * Returns the parse tree created by a successful run of the parse
     * method.
     */
    public JPQLParseTree getParseTree() {
        return (JPQLParseTree)getRootNode();
    }

    /**
     * INTERNAL
     * Return the text of the current query being compiled.
     */
    public String getQueryText() {
        return queryText;
    }

    /**
     * INTERNAL
     * Set the text of the current query being compiled.
     * Please note, setting the query text using this method is for error
     * handling and debugging purposes.
     */
    public void setQueryText(String queryText) {
        this.queryText = queryText;
    }

    /**
     * INTERNAL
     * Return the name of the current query being compiled. This method returns
     * <code>null</code> if the current query is a dynamic query and not a named
     * query.
     */
    public String getQueryName() {
        return queryText;
    }

    /**
     * INTERNAL
     * Set the name of the current query being compiled.
     * Please note, setting the query name using this method is for error
     * handling and debugging purposes.
     */
    public void setQueryName(String queryName) {
        this.queryName = queryName;
    }

    /**
     * INTERNAL
     * Return the the query text prefixed by the query name in case of a
     * named query. The method returns just the query text in case of a dynamic
     * query.
     */
    public String getQueryInfo() {
        return (queryName == null) ? queryText :
            queryName + ": " + queryText;
    }

    /**
     * INTERNAL
     * Set the factory used by the parser to create a parse tree and parse
     * tree nodes.
     */
    public void setNodeFactory(NodeFactory factory) {
        this.factory = factory;
    }

    /**
     * INTERNAL
     * Returns the factory used by the parser to create a parse tree and parse
     * tree nodes.
     */
    public NodeFactory getNodeFactory() {
        return factory;
    }

    /**
     * INTERNAL
     * Returns the list of errors found during the parsing process.
     */
    public List getErrors() {
        return errors;
    }

    /**
     * INTERNAL
     * Returns true if there were errors during the parsing process.
     */
    public boolean hasErrors() {
        return !getErrors().isEmpty();
    }

    /**
     * INTERNAL
     * Add the exception to the list of errors.
     */
    public void addError(Exception e) {
        if (e instanceof RecognitionException) {
            e = handleRecognitionException((RecognitionException)e);
        } else if (!(e instanceof JPQLException)) {
            e = JPQLException.generalParsingException(getQueryInfo(), e);
        }
        errors.add(e);
    }

    /**
     * INTERNAL
     * Generate an exception which encapsulates all the exceptions generated
     * by this parser. Special case where the first exception is an
     * JPQLException.
     */
    protected JPQLException generateException() {
        //Handle exceptions we expect (such as expressionSotSupported)
        Exception firstException = (Exception)getErrors().get(0);
        if (firstException instanceof JPQLException) {
            return (JPQLException)firstException;
        }

        //Handle general exceptions, such as NPE
        JPQLException exception =
            JPQLException.generalParsingException(getQueryInfo());
        exception.setInternalExceptions(getErrors());
        return exception;
    }

    /**
     * INTERNAL
     * Map an exception thrown by the ANTLR generated code to an
     * JPQLException.
     */
    //gf1166 Wrap ANTLRException inside JPQLException
    protected JPQLException handleRecognitionException(RecognitionException ex) {
        JPQLException result = null;
        // TODO: figure out the equivalent
       /* if (ex instanceof MismatchedCharException) {
            MismatchedCharException mismatched = (MismatchedCharException)ex;
            if (mismatched.foundChar == EOF_CHAR) {
                result = JPQLException.unexpectedEOF(getQueryInfo(),
                    mismatched.getLine(), mismatched.getColumn(), ex);
            } else if (mismatched.mismatchType == MismatchedCharException.CHAR) {
                result = JPQLException.expectedCharFound(getQueryInfo(),
                    mismatched.getLine(), mismatched.getColumn(),
                    String.valueOf((char)mismatched.expecting),
                    String.valueOf((char)mismatched.foundChar),
                    ex);
            }
        }
        else*/
        if (ex instanceof MismatchedTokenException) {
            MismatchedTokenException mismatched = (MismatchedTokenException)ex;
            Token token = mismatched.token;
            if (token != null) {
                if (token.getType() == Token.EOF) {
                    result = JPQLException.unexpectedEOF(getQueryInfo(),
                        mismatched.line, mismatched.charPositionInLine, ex);
                }
                else {
                    result = JPQLException.syntaxErrorAt(getQueryInfo(),
                        mismatched.line, mismatched.charPositionInLine,
                        token.getText(), ex);
                }
            }
        }
        else if (ex instanceof NoViableAltException) {
            NoViableAltException noviable = (NoViableAltException)ex;
            Token token = noviable.token;
            if (token != null) {
                if (token.getType() == Token.EOF) {
                    result = JPQLException.unexpectedEOF(getQueryInfo(),
                        noviable.line, noviable.charPositionInLine, ex);
                }
                else {
                    result = JPQLException.unexpectedToken(getQueryInfo(),
                        noviable.line, noviable.charPositionInLine,
                        token.getText(), ex);
                }
            }
        } else if (ex instanceof InvalidIdentifierException){
            InvalidIdentifierException invalid = (InvalidIdentifierException)ex;
            Token token = invalid.getToken();
            if (token != null) {
                if (token.getType() == Token.EOF) {
                    result = JPQLException.unexpectedEOF(getQueryInfo(),
                            token.getLine(), token.getCharPositionInLine(), ex);
                }
                else {
                    result = JPQLException.unexpectedToken(getQueryInfo(),
                            token.getLine(), token.getCharPositionInLine(),
                        token.getText(), ex);
                }
            }
        } else if (ex instanceof InvalidIdentifierStartException) {
            InvalidIdentifierStartException invalid = (InvalidIdentifierStartException)ex;
            result = JPQLException.unexpectedChar(getQueryInfo(),
                    invalid.line, invalid.charPositionInLine,
                String.valueOf((char)invalid.c), ex);
        }
        /*
        else if (ex instanceof TokenStreamRecognitionException) {
            result = handleANTLRException(((TokenStreamRecognitionException)ex).recog);
        }*/


        if (result == null) {
            // no special handling from aboves matches the exception if this
            // line is reached => make it a syntax error
            result = JPQLException.syntaxError(getQueryInfo(), ex);
        }
        return result;
    }

    /**
     * Method called by the ANTLR generated code in case of an error.
     */
    @Override
    public void reportError(RecognitionException ex) {
        addError(ex);
    }

    /**
     * This is the parser start method. It will be implemented by the ANTLR
     * generated subclass.
     */
    public abstract void document() throws RecognitionException;

    /**
     * Returns the root node after representing the parse tree for the current
     * query string. It will be implemented by the ANTLR generated subclass.
     */
    public abstract Object getRootNode();

}
