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

#include "OracleSQLLexer.h"
#include "core/tsqllexer.h"
#include "core/tsqlparse.h"

namespace SQLLexer
{

class OracleLexer : public Lexer
{
public:
    OracleLexer(const QString &statement, const QString &name);
    virtual ~OracleLexer();
    virtual QString firstWord();
    virtual QString currentWord(unsigned line, unsigned column);
    virtual void setStatement(const char *s, unsigned len = -1);
    virtual void setStatement(const QString &s);

protected:
    virtual int size() const;
	virtual const Token& LA(int pos) const;

private:
    void init();
    void clean();
    QByteArray QBAinput;
    QByteArray QBAname;

    pANTLR3_INPUT_STREAM input;
    pOracleSQLLexer lxr;
    pANTLR3_COMMON_TOKEN_STREAM tstream;
    pANTLR3_VECTOR lexerTokenVector;

    unsigned lastLine, lastColumn, lastIndex;
};

OracleLexer::OracleLexer(const QString &statement, const QString &name)
    : Lexer(statement, name)
    , QBAinput(statement.toUtf8())
    , QBAname(name.toUtf8())
	, lastLine(1)
	, lastColumn(0)
	, lastIndex(0)
{
	init();
}

OracleLexer::~OracleLexer()
{
	clean();
}

void OracleLexer::init()
{
    input = antlr3StringStreamNew( (pANTLR3_UINT8) QBAinput.data(), ANTLR3_ENC_8BIT, (ANTLR3_UINT64) QBAinput.size(), (pANTLR3_UINT8)QBAname.data());
    input->setUcaseLA(input, ANTLR3_TRUE); // ignore case

    if (input == NULL)
    {
        // TODO throw here
        throw SQLParser::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     = OracleSQLLexerNew(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 SQLParser::ParseException();
        exit(ANTLR3_ERR_NOMEM);
    }

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

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

void OracleLexer::setStatement(const char *s, unsigned len)
{
	clean();
	QBAinput.clear();
	QBAinput.append(s, len);
	lastLine = 1;
	lastColumn = 0;
	lastIndex = 0;
	init();
}

void OracleLexer::setStatement(const QString &statement)
{
	clean();
	QBAinput.clear();
	QBAinput.append(statement.toUtf8());
	lastLine = 1;
	lastColumn = 0;
	lastIndex = 0;
	init();
}

void OracleLexer::clean()
{
	if( tstream)
		tstream->free(tstream);
	if( lxr)
		lxr->free(lxr);
	if( input)
		input->free(input);
}

int OracleLexer::size() const
{
	if(tstream)
		return this->lexerTokenVector->size(lexerTokenVector);
	else
		return 0;
}
const Token& OracleLexer::LA(int pos) const
{
	pANTLR3_COMMON_TOKEN token = (pANTLR3_COMMON_TOKEN)lexerTokenVector->get(lexerTokenVector, pos);
	if(token)
	{
		// ANTLR3 starts with 1st while QScintilla starts with 0th
		int line = token->getLine(token) - 1;
		int column = token->getCharPositionInLine(token);
		unsigned length = token->getStopIndex(token) - token->getStartIndex(token) + 1;
		int offset = token->getStartIndex(token);
		Token::TokenType type = Token::X_UNASSIGNED;

		int t = token->getType(token);
		if( t == SL_COMMENT )
		  type = Token::X_COMMENT;
		else if( t == ML_COMMENT)
		  type = Token::X_COMMENT_ML;
		else if( token->getChannel(token) == HIDDEN)
		  type = Token::X_WHITE;
		else
		  switch(token->getType(token))
		  {
		  case T__200:
		  case T_RESERVED: type = Token::L_RESERVED; break;
		  default: type = Token::X_UNASSIGNED;
		  }
		
		return Token(Position(line, column), length, type);
	} else
		throw SQLParser::ParseException();
}

QString OracleLexer::firstWord()
{
    ANTLR3_UINT32 size = this->lexerTokenVector->size(lexerTokenVector);
    ANTLR3_UINT32 i;
    for (i = 0; i < size; i++)
    {
        pANTLR3_COMMON_TOKEN token = (pANTLR3_COMMON_TOKEN)lexerTokenVector->get(lexerTokenVector, i);
        if( token->getChannel(token) != HIDDEN)
        {
            return QString((const char*)(token->getText(token)->chars));
        }
    }
    return QString();
}

QString OracleLexer::currentWord(unsigned line, unsigned column)
{
    line++; // ANTLR3 starts with 1st while QScintilla starts with 0th
    QString retval;
    ANTLR3_UINT32 i, startIndex;
    ANTLR3_UINT32 size = this->lexerTokenVector->size(lexerTokenVector);
    pANTLR3_COMMON_TOKEN token = NULL;

    if(size == 0)
        return retval;

    if( lastLine > line || (lastLine == line && lastColumn > column))
    {
    	pANTLR3_COMMON_TOKEN tokenZero = (pANTLR3_COMMON_TOKEN)lexerTokenVector->get(lexerTokenVector, 0);
    	retval = QString::fromUtf8((const char*)(tokenZero->getText(tokenZero)->chars));
    	startIndex = 1;
    } else {
    	pANTLR3_COMMON_TOKEN tokenZero = (pANTLR3_COMMON_TOKEN)lexerTokenVector->get(lexerTokenVector, lastIndex);
    	retval = QString::fromUtf8((const char*)(tokenZero->getText(tokenZero)->chars));
    	startIndex = lastIndex;
    }

    for (i = startIndex; i <= size; i++)
    {
        token = (pANTLR3_COMMON_TOKEN)lexerTokenVector->get(lexerTokenVector, i);
        if( token == NULL)
        	break;

        if ( token->getChannel(token) != HIDDEN)
        {
            lastIndex  = i;
            lastLine   = token->getLine(token);
            lastColumn = token->getCharPositionInLine(token);
        	retval = QString::fromUtf8((const char*)(token->getText(token)->chars));
        }

        if( token->getLine(token) > line
        		|| ( token->getLine(token) == line && token->getCharPositionInLine(token) > column ))
        	break;	
    }

    return retval;
}

};

Util::RegisterInFactory<SQLLexer::OracleLexer, LexerFactTwoParmSing> regOracleSQLLexStatement("OracleSQL");
