#include "parse.h"

using namespace goose;
using namespace goose::parse;

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

optional< TermLoc > Resolver::consume()
{
    clearCacheIfNeeded();
    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< TermLoc > Resolver::consumeUnresolved()
{
    clearCacheIfNeeded();
    consumeNewLines();

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

    return m_tokProvider->consume();
}

optional< TermLoc > Resolver::consumeRaw()
{
    clearCacheIfNeeded();
    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->first );
    if( ( !delim || *delim != Delimiter::Newline )
        && !m_lookAheadCache.empty() )
    {
        m_lookAheadCache.pop_front();
    }

    return tok;
}

optional< TermLoc > Resolver::lookAhead( size_t distance )
{
    clearCacheIfNeeded();
    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->first );
        if( delim && *delim == Delimiter::Newline )
            continue;

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

    return m_lookAheadCache[distance];
}

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

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

    return nullopt;
}

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

void Resolver::clearLookAheadCache() const
{
    m_lookAheadCache.clear();
    m_valueStoreVersion = m_context.env()->valueStoreVersion();
}

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

    Term result;

    DiagnosticsContext dc( t.second, "Referenced here." );

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

        case sema::Env::Status::AmbiguousMatch:
            DiagnosticsManager::GetInstance().emitSyntaxErrorMessage(
                t.second, "ambiguous match for symbol." );
            return t;

        default:
            break;
    }

    return TermLoc( result, t.second );
}

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

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

        m_tokProvider->consume();
    }
}

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

    const auto* delim = get_if< Delimiter >( &tok->first );
    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:
            DiagnosticsManager::GetInstance().emitSyntaxErrorMessage(
                tok->second, "mismatched ')'.", 0 );
            break;

        case Delimiter::CloseBrace:
            DiagnosticsManager::GetInstance().emitSyntaxErrorMessage(
                tok->second, "mismatched '}'.", 0 );
            break;

        case Delimiter::CloseBracket:
            DiagnosticsManager::GetInstance().emitSyntaxErrorMessage(
                tok->second, "mismatched ']'.", 0 );
            break;

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

Generator< TermLoc > Resolver::consumeBlock( Delimiter end )
{
    while( const auto& tok = lookAheadRaw() )
    {
        const auto* delim = get_if< Delimiter >( &tok->first );
        if( delim )
        {
            if( *delim == end )
            {
                co_yield *consumeRaw();
                co_return;
            }
            else
            {
                switch( *delim )
                {
                    case Delimiter::CloseParen:
                        DiagnosticsManager::GetInstance().emitSyntaxErrorMessage(
                            tok->second, "mismatched ')'.", 0 );
                        co_return;

                    case Delimiter::CloseBrace:
                        DiagnosticsManager::GetInstance().emitSyntaxErrorMessage(
                            tok->second, "mismatched '}'.", 0 );
                        co_return;

                    case Delimiter::CloseBracket:
                        DiagnosticsManager::GetInstance().emitSyntaxErrorMessage(
                            tok->second, "mismatched ']'.", 0 );
                        co_return;

                    default:
                        break;
                }
            }
        }

        co_yield consumeUnit();
    }

    switch( end )
    {
        case Delimiter::CloseParen:
            DiagnosticsManager::GetInstance().emitLexerErrorMessage( currentLocation(), "missing ')'." );
            break;

        case Delimiter::CloseBrace:
            DiagnosticsManager::GetInstance().emitLexerErrorMessage( currentLocation(), "missing '}'." );
            break;

        case Delimiter::CloseBracket:
            DiagnosticsManager::GetInstance().emitLexerErrorMessage( currentLocation(), "missing ']'." );
            break;

        default:
            break;
    }
}
