
// ANTLR runtime includes
#include "antlr3commontoken.h"
#include "antlr3string.h"
#include "antlr3input.h"
#include "antlr3lexer.h"

#include "OraclePLSQLLexer.h"
#include "OraclePLSQLParser.h"

#include "core/tsqlparse.h"

namespace SQLParser
{
class OraclePLSQLToken: public Token
{
public:
    OraclePLSQLToken (Token *parent, const Position &pos, const QString &str, unsigned tokentype, const char* tokentypenamestring, unsigned usagetype = T_UNKNOWN)
        : Token(parent, pos, str)
    {
        _mTokenATypeName = tokentypenamestring;

        TokenType &_mTokenTypeRef = const_cast<Token::TokenType&>(_mTokenType);
        switch(tokentype)
        {
        case T_RESERVED:
            _mTokenTypeRef = L_RESERVED;
            break;
        case T_BINDVAR_NAME:
            _mTokenTypeRef = L_BINDVARNAME;
            break;
        case T_VARIABLE_NAME:
            _mTokenTypeRef = L_VARIABLE;
            break;
        case T_DATATYPE_NAME:
            _mTokenTypeRef = L_DATATYPE;
            break;
        case T_SCHEMA_NAME:
            _mTokenTypeRef = L_SCHEMANAME;
            break;
        case T_FUNCTION_NAME:
            _mTokenTypeRef = L_FUNCTIONNAME;
            break;
        case T_PROCEDURE_NAME:
            _mTokenTypeRef = L_PROCEDURENAME;
            break;
        case T_CURSOR_NAME:
            _mTokenTypeRef = L_CURSORNAME;
            break;
        case T_EXCEPTION_NAME:
            _mTokenTypeRef = L_EXCEPTIONNAME;
            break;
        case T_PACKAGE_NAME:
            _mTokenTypeRef = L_PACKAGENAME;
            break;
        case T_PARAMETER_NAME:
            _mTokenTypeRef = L_PARAMETER;
            break;
        case T_UNKNOWN:
            _mTokenTypeRef = X_UNASSIGNED;
            break;
        } // switch(tokentype)

        UsageType &_mUsageTypeRef = const_cast<Token::UsageType&>(_mUsageType);
        switch(usagetype)
        {
        case 0:
        case T_UNKNOWN:
            _mUsageTypeRef = Unknown;
            break;
        case T_USE:
            _mUsageTypeRef = Usage;
            break;
        case T_USEL:
            _mUsageTypeRef = UsageL;
            break;
        case T_DECL:
            _mUsageTypeRef = Declaration;
            break;
        }
    };
}; // class OraclePLSQLToken: public Token

class OraclePLSQLStatement: public Statement
{
public:
    OraclePLSQLStatement(const QString &statement, const QString &name);
    virtual ~OraclePLSQLStatement() {};

private:
    void parse ();
    /* Recursive walk through ANTLR3_BASE_TREE */
    void treeWalk(pOraclePLSQLParser psr, QPointer<Token> root,  ANTLR3_BASE_TREE *tree, ANTLR3_UINT32 &lastindex);
    /* Walk through Token tree and look for table names, table aliases, ... */
    virtual void scanTree();
    void findDeclarations();
    pANTLR3_VECTOR lexerTokenVector;
};

OraclePLSQLStatement::OraclePLSQLStatement(const QString &statement, const QString &name) : Statement(statement, name)
{
    _mStatementType = S_SELECT;
    parse();
};

void OraclePLSQLStatement::parse ()
{
    pANTLR3_INPUT_STREAM input;
    pOraclePLSQLLexer lxr;
    pANTLR3_COMMON_TOKEN_STREAM tstream;
    pOraclePLSQLParser psr;
    OraclePLSQLParser_start_rule_return langAST;
    //pANTLR3_COMMON_TREE_NODE_STREAM   nodes;

    QByteArray QBAinput(_mStatement.toUtf8());
    QByteArray QBAname(_mname.toUtf8());

    input = antlr3StringStreamNew( (pANTLR3_UINT8) QBAinput.data(), ANTLR3_ENC_8BIT, (ANTLR3_UINT64) QBAinput.size(), (pANTLR3_UINT8)QBAname.data());
    //input = antlr3NewAsciiStringInPlaceStream( (uint8_t*) QBAinput.data(), (ANTLR3_UINT64) QBAinput.size(), NULL);

    input->setUcaseLA(input, ANTLR3_TRUE); // ignore case

    if (input == NULL)
    {
        // TODO throw here
        throw ParseException();
        exit(ANTLR3_ERR_NOMEM);
    }

    // Our input stream is now open and all set to go, so we can create a new instance of our
    // lexer and set the lexer input to our input stream:
    //  (file | memory | ?) --> inputstream -> lexer --> tokenstream --> parser ( --> treeparser )?
    //
    lxr     = OraclePLSQLLexerNew(input);       // CLexerNew is generated by ANTLR
    lxr->pLexer->input->charPositionInLine = 0; // fix off-by-one error for getCharPositionInLine for the 1st row

    if ( lxr == NULL )
    {
        // TODO throw here
        throw ParseException();
        exit(ANTLR3_ERR_NOMEM);
    }

    tstream = antlr3CommonTokenStreamSourceNew(ANTLR3_SIZE_HINT, TOKENSOURCE(lxr));

    if (tstream == NULL)
    {
        // TODO throw here
        _mState = P_ERROR;
        throw ParseException();
        exit(ANTLR3_ERR_NOMEM);
    }
    this->lexerTokenVector = tstream->getTokens(tstream);
    _mState = P_LEXER;

    // Finally, now that we have our lexer constructed, we can create the parser
    //
    psr     = OraclePLSQLParserNew(tstream);  // CParserNew is generated by ANTLR3

    if (psr == NULL)
    {
        // TODO throw here
        throw ParseException();
        exit(ANTLR3_ERR_NOMEM);
    }

    langAST = psr->start_rule(psr);

    if (psr->pParser->rec->state->errorCount > 0)
    {
        // TODO throw reasonable expeption here
        // fprintf(stderr, "The parser returned %d errors, tree walking aborted.\n", psr->pParser->rec->state->errorCount);
        throw ParseException();
    }

    //pANTLR3_COMMON_TOKEN root_token = langAST.tree->getToken(langAST.tree);
    _mAST = new Token( NULL
                       , Position(0, 0)
                       , ""
                       , Token::X_ROOT
                     );
    _mAST->setTokenATypeName("ROOT");
    ANTLR3_UINT32 lastIndex = 0;

    treeWalk(psr, _mAST, langAST.tree, lastIndex);

    findDeclarations();

    psr ->free (psr);
    psr       = NULL;
    tstream ->free (tstream);
    tstream   = NULL;
    this->lexerTokenVector = NULL;
    lxr ->free (lxr);
    lxr       = NULL;
    input ->close (input);
    input    = NULL;
};

/* recursively copy an AST tree into */
void OraclePLSQLStatement::treeWalk(pOraclePLSQLParser psr, QPointer<Token> root,  ANTLR3_BASE_TREE *tree, ANTLR3_UINT32 &lastindex)
{
    pANTLR3_BASE_TREE pRootNode = (ANTLR3_BASE_TREE*)tree;

    // if  (pRootNode->isNilNode(pRootNode) == ANTLR3_TRUE) {
    //  printf("// nil-node\n");
    //  //return;
    //  //TODO throw here
    // }

    ANTLR3_UINT32 uChildCount = pRootNode->getChildCount(pRootNode);
    for  (ANTLR3_UINT32 i = 0; i < uChildCount; ++i)
    {
        pANTLR3_BASE_TREE pChildNode = (pANTLR3_BASE_TREE) pRootNode->getChild(pRootNode, i);
        ANTLR3_UINT32 uChildLexemeType = pChildNode->getType(pChildNode);
        pANTLR3_COMMON_TOKEN pChildLexeme = pChildNode->getToken(pChildNode);

        // if child is not a leaf node - recurse
        if ( pChildNode->getChildCount(pChildNode) != 0)
        {
            // pANTLR3_COMMON_TOKEN parser_token = child->getToken(child);
            // printf("Non-Leaf node \'%s\'(%d)\n",
            //        (const char*)parser_token->getText(parser_token)->chars,
            //        parser_token->user1 //tree->u
            //        );

            Token *childToken = new OraclePLSQLToken ( root
                    , Position(pChildLexeme->getLine(pChildLexeme), pChildLexeme->getCharPositionInLine(pChildLexeme))
                    , (const char*)pChildLexeme->getText(pChildLexeme)->chars
                    , pChildLexeme->getType(pChildLexeme)
                    , (const char*) psr->pParser->rec->state->tokenNames[ pChildNode->getType(pChildNode) ]
                    , (ANTLR3_UINT64)(pChildNode->u)
                                                     );
            root->appendChild(childToken);
            //childToken->setTokenATypeName( (const char*) psr->pParser->rec->state->tokenNames[ pChildNode->getType(pChildNode) ]);
            treeWalk(psr, childToken, pChildNode, lastindex);
        }
        else     // if child is a leaf node
        {
            /* this is a leaf node */
            ANTLR3_MARKER uChildLexemeStart = pChildLexeme->start;
            ANTLR3_UINT32 lexemeTotal = lexerTokenVector->count;
            pANTLR3_COMMON_TOKEN pLocalLexeme;

            /* loop over lexer's tokens until leaf if found */
            while(lastindex < lexemeTotal)
            {
                pLocalLexeme = (pANTLR3_COMMON_TOKEN) lexerTokenVector->get(lexerTokenVector, lastindex);
                ANTLR3_MARKER uLocalLexemeStart = pLocalLexeme->start;
                if(uLocalLexemeStart == uChildLexemeStart)
                    break;
                lastindex++;
            }

            // TODO check for EOF in lexer stream */
            // printf("Leaf node \'%s\'(%d)\n",
            //        (const char*)parser_token->getText(parser_token)->chars,
            //        parser_token->user1
            //        );

            Token *childToken = new OraclePLSQLToken ( root
                    , Position(pChildLexeme->getLine(pChildLexeme), pChildLexeme->getCharPositionInLine(pChildLexeme))
                    , (const char*)pChildLexeme->getText(pChildLexeme)->chars
                    // Leaf node can be either a reserved (key)word or identifier. Also some keywords can be identifiers.
                    // if the attribute user1 is set then the token is considered to be an identifier
                    // user2 represents either alias declaration or usage
                    , pChildLexeme->user1 ? pChildLexeme->user1 : pChildLexeme->getType(pChildLexeme)
                    , (
                        pChildNode->getType(pChildNode) != (ANTLR3_UINT32)0xffffffff
                        ? ((const char*) psr->pParser->rec->state->tokenNames[ pChildNode->getType(pChildNode) ])
                        : ((const char*) "Build-in ANTLR token")
                    )
                    , pChildLexeme->user2 ? pChildLexeme->user2 : T_UNKNOWN
                                                     );
            root->appendChild(childToken);
            //childToken->setTokenTypeName( (const char*) psr->pParser->rec->state->tokenNames[ pChildNode->getType(pChildNode) ]);
            // Process spaces and comments after parser_token
            while(++lastindex < lexemeTotal)
            {
                pANTLR3_COMMON_TOKEN pSpacerLexeme = (pANTLR3_COMMON_TOKEN) lexerTokenVector->get(lexerTokenVector, lastindex);
                ANTLR3_UINT32 SpacerLexemeChannel = pSpacerLexeme->getChannel(pSpacerLexeme);

                if(SpacerLexemeChannel != HIDDEN )
                    break;

                Token *spacerToken =  new Token( root
                                                 , Position(pSpacerLexeme->getLine(pSpacerLexeme), pSpacerLexeme->getCharPositionInLine(pSpacerLexeme))
                                                 , (const char*)pSpacerLexeme->getText(pSpacerLexeme)->chars
                                                 , Token::X_COMMENT
                                               );
                childToken->appendSpacer(spacerToken);
            }
        } // else for child is a leaf node
    } // for each child
};

/* virtual */
void OraclePLSQLStatement::scanTree() {};

void OraclePLSQLStatement::findDeclarations()
{
    for(SQLParser::Statement::token_const_iterator i = begin(); i != end(); ++i)
    {
        Token const &node = *i;
        if(node.getTokenUsageType() == Token::Declaration)
        {
            switch(node.getTokenType())
            {
            case Token::L_BINDVARNAME:
            case Token::L_VARIABLE:
            case Token::L_DATATYPE:
            case Token::L_SCHEMANAME:
            case Token::L_FUNCTIONNAME:
            case Token::L_PROCEDURENAME:
            case Token::L_CURSORNAME:
            case Token::L_EXCEPTIONNAME:
                _mDeclarations.insertMulti(node.toString(), &node);
                break;
            }
        }

        switch(node.getTokenType())
        {
        case Token::L_TABLENAME:
            break;
        }
        // if( node.getTokenUsageType() == Token::Declaration )
        //  _mAliasesSet.insert( node.toString().toUpper());
        // break;
    }
};

}; // namespace SQLParser

Util::RegisterInFactory<SQLParser::OraclePLSQLStatement, StatementFactTwoParmSing> regOraclePLSQLStatement("OraclePLSQL");
