#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()
{
    consumeNewLines();

    auto tok = m_tokProvider->consume();

    if( m_lookAheadCache.empty() )
    {
        if( !tok )
            return nullopt;
        return resolve( *tok );
    }

    auto t = move( m_lookAheadCache.front() );
    m_lookAheadCache.pop_front();
    return t;
}

optional< Term > Resolver::consumeUnresolved()
{
    consumeNewLines();

    if( !m_lookAheadCache.empty() )
        m_lookAheadCache.pop_front();

    return m_tokProvider->consume();
}

optional< Term > Resolver::consumeRaw()
{
    auto tok = m_tokProvider->consume();

    // If we already cached a resolved value, discard it
    // unless the value we just consumed is a newline,
    // in which case there is no corresponding entry in the cache.
    const auto* delim = get_if< Delimiter >( &tok.value() );
    if( ( !delim || *delim != Delimiter::Newline )
        && !m_lookAheadCache.empty() )
    {
        m_lookAheadCache.pop_front();
    }

    return tok;
}

optional< Term > Resolver::lookAhead( size_t distance )
{
    size_t i = 0;

    while( m_lookAheadCache.size() < ( distance + 1 ) )
    {
        auto tok = m_tokProvider->lookAhead( i++ );
        if( !tok )
            return nullopt;

        // Skip newlines
        const auto* delim = get_if< Delimiter >( &tok.value() );
        if( delim && *delim == Delimiter::Newline )
            continue;

        m_lookAheadCache.emplace_back( resolve( *tok ) );
    }

    return m_lookAheadCache[distance];
}

optional< Term > Resolver::lookAheadUnresolved( size_t distance )
{
    size_t i = distance;

    while( auto tok = m_tokProvider->lookAhead( i++ ) )
    {
        const auto* delim = get_if< Delimiter >( &tok.value() );
        if( !delim || *delim != Delimiter::Newline )
            return tok;
    }

    return nullopt;
}

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

void Resolver::clearLookAheadCache()
{
    m_lookAheadCache.clear();
}

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.identity(), result ) )
    {
        case sema::Env::Status::NoMatch:
            return t;

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

    return result;
}

void Resolver::consumeNewLines()
{
    while( auto tok = lookAheadRaw() )
    {
        const auto* delim = get_if< Delimiter >( &tok.value() );
        if( !delim )
            return;

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

        m_tokProvider->consume();
    }
}

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

    const auto* delim = get_if< Delimiter >( &tok.value() );
    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 << "mismatched ')'\n";
            break;

        case Delimiter::CloseBrace:
            cout << "mismatched '}'\n";
            break;

        case Delimiter::CloseBracket:
            cout << "mismatched ']'\n";
            break;

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

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

                    case Delimiter::CloseBrace:
                        cout << "mismatched '}'\n";
                        co_return;

                    case Delimiter::CloseBracket:
                        cout << "mismatched ']'\n";
                        co_return;
                }
            }
        }

        co_yield consumeUnit();
    }
}
