#include "parse.h"

using namespace empathy;
using namespace empathy::parse;

bool Resolver::eos() const
{
    return m_tokProvider->eos() && m_lookAheadCache.empty();
}

optional< Term > Resolver::consume()
{
    if( m_lookAheadCache.empty() )
    {
        auto tok = m_tokProvider->consume();
        if( !tok )
            return nullopt;
        return resolve( *tok );
    }

    m_tokProvider->consume();
    auto t = move( m_lookAheadCache.front() );
    m_lookAheadCache.pop_front();
    return t;
}

optional< Term > Resolver::consumeUnresolved()
{
    if( !m_lookAheadCache.empty() )
        m_lookAheadCache.pop_front();

    return m_tokProvider->consume();
}

optional< Term > Resolver::lookAhead( size_t distance )
{
    while( m_lookAheadCache.size() < ( distance + 1 ) )
    {
        auto tok = m_tokProvider->lookAhead( m_lookAheadCache.size() );
        if( !tok )
            return nullopt;
        m_lookAheadCache.push_back( resolve( *tok ) );
    }

    return m_lookAheadCache[distance];
}

optional< Term > Resolver::lookAheadUnresolved( size_t distance )
{
    return m_tokProvider->lookAhead( distance );
}

Term Resolver::resolve( const Term& t ) const
{
    auto identifier = Decompose( t, Val< StringId >() );
    if( !identifier )
        return t;

    Term result;

    auto valId = AppendToVectorTerm( m_context.identity(), t );
    switch( m_context.env()->retrieveValue( valId, m_context, result ) )
    {
        case sema::Env::Status::NoMatch:
            return t;

        case sema::Env::Status::AmbiguousMatch:
            cout << t.location() << ": ambiguous match for symbol '" << t << "'.\n";
            result = t;
            result.setFaulty();
            return t;
    }

    return result;
}

void Resolver::consumeNewLines()
{
    while( auto tok = lookAheadUnresolved() )
    {
        const auto* delim = get_if< Delimiter >( &tok->content() );
        if( !delim )
            return;

        if( *delim != Delimiter::Newline )
            return;

        consumeUnresolved();
    }
}

Generator< Term > Resolver::consumeUnit()
{
    auto tok = consumeUnresolved();
    if( !tok )
        co_return;

    const auto* delim = get_if< Delimiter >( &tok->content() );
    if( !delim )
    {
        co_yield move( *tok );
        co_return;
    }

    switch( *delim )
    {
        case Delimiter::OpenParen:
            co_yield move( *tok );
            co_yield consumeBlock( Delimiter::CloseParen );
            break;

        case Delimiter::OpenBrace:
            co_yield move( *tok );
            co_yield consumeBlock( Delimiter::CloseBrace );
            break;

        case Delimiter::OpenBracket:
            co_yield move( *tok );
            co_yield consumeBlock( Delimiter::CloseBracket );
            break;

        case Delimiter::CloseParen:
            cout << tok->location() << ": mismatched ')'\n";
            break;

        case Delimiter::CloseBrace:
            cout << tok->location() << ": mismatched '}'\n";
            break;

        case Delimiter::CloseBracket:
            cout << tok->location() << ": mismatched ']'\n";
            break;

        default:
            co_yield move( *tok );
            break;
    }
}

Generator< Term > Resolver::consumeBlock( Delimiter end )
{
    while( const auto& tok = lookAheadUnresolved() )
    {
        const auto* delim = get_if< Delimiter >( &tok->content() );
        if( delim )
        {
            if( *delim == end )
            {
                co_yield *consumeUnresolved();
                co_return;
            }
            else
            {
                switch( *delim )
                {
                    case Delimiter::CloseParen:
                        cout << tok->location() << ": mismatched ')'\n";
                        co_return;

                    case Delimiter::CloseBrace:
                        cout << tok->location() << ": mismatched '}'\n";
                        co_return;

                    case Delimiter::CloseBracket:
                        cout << tok->location() << ": mismatched ']'\n";
                        co_return;
                }
            }
        }

        co_yield consumeUnit();
    }
}
