/*
 * Copyright (C) 2007-2023 Xagasoft, All rights reserved.
 *
 * This file is part of the libbu++ library and is released under the
 * terms of the license contained in the file LICENSE.
 */

#include "bu/parser.h"
#include "bu/lexer.h"

#include "bu/sio.h"
using namespace Bu;

Bu::Parser::Parser()
{
}

Bu::Parser::~Parser()
{
}

void Bu::Parser::pushLexer( Lexer *pLex )
{
    sLexer.push( pLex );
}

void Bu::Parser::popLexer()
{
    delete sLexer.peekPop();
}

Lexer::Token *Bu::Parser::popToken()
{
    return sToken.peekPop();
}

void Bu::Parser::pushToken( Lexer::Token *pTok )
{
    sToken.push( pTok );
}

void Bu::Parser::parse()
{
    int iCurNt = iRootNonTerminal;
    Lexer::Token *ptCur = sLexer.peek()->nextToken();
    sio << "Token(a): " << sLexer.peek()->tokenToString( *ptCur ) << sio.nl;
    selectProduction( iCurNt, ptCur );
    
    while( !sState.isEmpty() )
    {
        switch( (*sState.peek()).eType )
        {
            case State::typeTerminal:
                sio << "terminal: " << ptCur->iToken << " == "
                    << (*sState.peek()).iIndex << sio.nl;
                if( ptCur->iToken == (*sState.peek()).iIndex )
                {
                    advanceState();
                    delete ptCur;
                    ptCur = sLexer.peek()->nextToken();
                    sio << "Token(b): " << sLexer.peek()->tokenToString( *ptCur ) << sio.nl;
                }
                else
                {
                    throw Bu::ExceptionBase("Error parsing code.");
                }
                break;

            case State::typeTerminalPush:
                sio << "terminalpush: " << ptCur->iToken << " == "
                    << (*sState.peek()).iIndex << sio.nl;
                if( ptCur->iToken == (*sState.peek()).iIndex )
                {
                    advanceState();
                    sToken.push( ptCur );

                    ptCur = sLexer.peek()->nextToken();
                    sio << "Token(c): " << sLexer.peek()->tokenToString( *ptCur ) << sio.nl;
                }
                else
                {
                    throw Bu::ExceptionBase("Error parsing code.");
                }
                break;

            case State::typeNonTerminal:
                sio << "nonterminal: " << ptCur->iToken << " --> "
                    << (*sState.peek()).iIndex << sio.nl;
                {
                    int iNt = (*sState.peek()).iIndex;
                    sio << "Current state: " << *sState.peek() << sio.nl;
                    if( !selectProduction( iNt, ptCur ) )
                    {
                        throw Bu::ExceptionBase("Error parsing code.");
                    }
                }
                break;

            case State::typeReduction:
                sio << "reduction" << sio.nl;
                aReduction[(*sState.peek()).iIndex]( *this );
                advanceState();
                break;
        }
    }   
}

bool Bu::Parser::selectProduction( int iNt, Lexer::Token *ptCur )
{
    NonTerminal &nt = aNonTerminal[iNt];
    int j = 0;
    for( NonTerminal::ProductionList::iterator i = nt.lProduction.begin();
         i; i++,j++ )
    {
        if( (*i).isEmpty() )
            continue;
        sio << "-->(Attempting production " << iNt << ":" << j << ": "
            << (*i).first() << ")" << sio.nl;
        if( (*i).first().eType == State::typeTerminal ||
            (*i).first().eType == State::typeTerminalPush )
        {
            if( (*i).first().iIndex == ptCur->iToken )
            {
                sState.push( (*i).begin() );
                sio.incIndent();
                sio << "Pushing production " << j << " from nt " << iNt
                    << sio.nl;
                return true;
            }
        }
        else if( (*i).first().eType == State::typeNonTerminal )
        {
            sState.push( (*i).begin() );
            sio.incIndent();
            sio << "Pushing production " << j << " from nt " << iNt
                << " as test." << sio.nl;
            if( !selectProduction( (*i).first().iIndex, ptCur ) )
            {
                sio.decIndent();
                sState.pop();
                sio << "Production " << j << " from nt " << iNt
                    << " didn't work out." << sio.nl;
            }
            else
            {
                return true;
            }
        }
    }
    if( nt.bCanSkip )
    {
        sio << "Nothing matches, skipping non-terminal." << sio.nl;
        advanceState();
        return true;
    }
    sio << "-->(Found nothing)" << sio.nl;
    return false;
}

void Bu::Parser::advanceState()
{
    if( sState.isEmpty() )
        return;

    sState.peek()++;
    if( !sState.peek() )
    {
        sio.decIndent();
        sState.pop();
        sio << "State advanced, End of production." << sio.nl;
        advanceState();
        return;
    }
    sio << "State advanced, now: " << *(sState.peek()) << sio.nl;
}

void Bu::Parser::setRootNonTerminal( int iRoot )
{
    iRootNonTerminal = iRoot;
}

void Bu::Parser::setRootNonTerminal( const Bu::String &sRoot )
{
    setRootNonTerminal( hNonTerminalName.get( sRoot ) );
}

int Bu::Parser::addNonTerminal( const Bu::String &sName, NonTerminal &nt )
{
    int iId = aNonTerminal.getSize();
    aNonTerminal.append( nt );
    hNonTerminalName.insert( sName, iId );
    sio << "nt '" << sName << "' = " << iId << sio.nl;
    return iId;
}

int Bu::Parser::addNonTerminal( const Bu::String &sName )
{
    int iId = aNonTerminal.getSize();
    aNonTerminal.append( NonTerminal() );
    hNonTerminalName.insert( sName, iId );
    sio << "nt '" << sName << "' = " << iId << sio.nl;
    return iId;
}

void Bu::Parser::setNonTerminal( const Bu::String &sName, NonTerminal &nt )
{
    aNonTerminal[hNonTerminalName.get(sName)] = nt;
}

int Bu::Parser::getNonTerminalId( const Bu::String &sName )
{
    return hNonTerminalName.get( sName );
}

bool Bu::Parser::hasNonTerminal( const Bu::String &sName )
{
    return hNonTerminalName.has( sName );
}

int Bu::Parser::addReduction( const Bu::String &sName, const Reduction &r )
{
    int iId = aReduction.getSize();
    aReduction.append( r );
    hReductionName.insert( sName, iId );
    return iId;
}

int Bu::Parser::addReduction( const Bu::String &sName )
{
    int iId = aReduction.getSize();
    aReduction.append( Reduction() );
    hReductionName.insert( sName, iId );
    return iId;
}

void Bu::Parser::setReduction( const Bu::String &sName, const Reduction &r )
{
    aReduction[hReductionName.get(sName)] = r;
}

int Bu::Parser::getReductionId( const Bu::String &sName )
{
    return hReductionName.get( sName );
}

bool Bu::Parser::hasReduction( const Bu::String &sName )
{
    return hReductionName.has( sName );
}

//
// Bu::Parser::State
//

Bu::Parser::State::State( Bu::Parser::State::Type eType, int iIndex ) :
    eType( eType ),
    iIndex( iIndex )
{
}

Bu::Parser::State::~State()
{
}

//
// Bu::Parser::NonTerminal
//

Bu::Parser::NonTerminal::NonTerminal() :
    bCanSkip( false )
{
}

Bu::Parser::NonTerminal::~NonTerminal()
{
}

void Bu::Parser::NonTerminal::addProduction( Production p )
{
    lProduction.append( p );
}

void Bu::Parser::NonTerminal::setCanSkip()
{
    bCanSkip = true;
}

Bu::Formatter &Bu::operator<<( Bu::Formatter &f, Bu::Parser::State::Type t )
{
    switch( t )
    {
        case Bu::Parser::State::typeTerminal:
            return f << "typeTerminal";

        case Bu::Parser::State::typeTerminalPush:
            return f << "typeTerminalPush";

        case Bu::Parser::State::typeNonTerminal:
            return f << "typeNonTerminal";

        case Bu::Parser::State::typeReduction:
            return f << "typeReduction";
    }
    return f << "***error***";
}

Bu::Formatter &Bu::operator<<( Bu::Formatter &f, const Bu::Parser::State &s )
{
    return f << "{" << s.eType << ": " << s.iIndex << "}";
}

