#include "builtins/builtins.h"
#include "parse/parse.h"
#include "precedence.h"

using namespace empathy;
using namespace empathy::ir;
using namespace empathy::parse;

namespace empathy::builtins
{
    void SetupUsingStmt( Env& e )
    {
        auto handleUsing = []( Parser& p, uint32_t locationId, uint32_t prec )
        {
            auto& dm = DiagnosticsManager::GetInstance();

            auto nameTerm = p.resolver()->consume();
            if( !nameTerm )
            {
                dm.emitSyntaxErrorMessage( p.resolver()->getCurrentLocation(), "expected an identifier.", 0 );
                return false;
            }

            const auto* name = get_if< StringId >( &nameTerm->first );
            if( !name )
            {
                dm.emitSyntaxErrorMessage( nameTerm->second, "expected an identifier.", 0 );
                return false;
            }

            auto eqTerm = p.resolver()->consumeUnresolved();
            if( !eqTerm )
            {
                dm.emitSyntaxErrorMessage( p.resolver()->getCurrentLocation(), "expected '='.", 0 );
                return false;
            }

            const auto* eq = get_if< StringId >( &eqTerm->first );
            if( !eq || *eq != "="_sid )
            {
                dm.emitSyntaxErrorMessage( eqTerm->second, "expected '='.", 0 );
                return false;
            }

            p.resolver()->consumeNewLines();

            // Store all the units following the equal sign until the next semicolon or newline.
            vector< TermLoc > toks;
            while( !p.resolver()->eos() )
            {
                const auto& tok = p.resolver()->lookAheadRaw();
                if( !tok )
                    break;

                const auto* delim = get_if< Delimiter >( &tok->first );
                if( delim && *delim == Delimiter::Newline )
                {
                    p.resolver()->consumeRaw();
                    break;
                }

                const auto* id = get_if< StringId >( &tok->first );
                if( id && *id == ";"_sid )
                    break;

                auto g = p.resolver()->consumeUnit();
                move( g.begin(), g.end(), back_inserter( toks ) );
            }

            if( toks.empty() )
            {
                // TODO maybe use the current location after consuming the equal sign as the location here
                dm.emitSyntaxErrorMessage( eqTerm->second, "expected an expression.", 0 );
                return false;
            }

            const auto& context = p.resolver()->context();

            // Create a local identity for the constant, from which the current identity will be visible.
            // This is to avoid anything declared from within the using expression to leak outside,
            // and also to make sure that whenever it will be parsed, it will have access to the current
            // context in which it has been declared.
            auto localIdentity = AppendToVectorTerm( InjectDomainIntoIdentity( context.identity(), ANYTERM( _ ) ), nameTerm->first );
            context.env()->addVisibilityRule( InjectDomainIntoIdentity( context.identity(), ANYTERM( _ ) ), localIdentity );

            auto nameSid = *name;
            auto nameLoc = nameTerm->second;

            bool bInUse = false;

            auto loc = Location::CreateSpanningLocation( locationId, nameLoc );

            auto UsingValProvider = [toks, localIdentity, bInUse, nameSid, loc]( Env& e, const Term& identity, const Term& contextId, Term& result ) mutable
            {
                auto& dm = DiagnosticsManager::GetInstance();

                // In case of errors, we poison the result so that code invoking
                // this using expression will not emit cascading errors.
                if( bInUse )
                {

                    dm.emitErrorMessage( loc, "recursive using expression." );
                    // We only poison the result here, not "content", since the token
                    // vector that it contains is currently in use. It will get poisoned
                    // down the line once the parent recursion level of this using
                    // expression finishes.
                    result = ValueToIRExpr( PoisonValue() );
                    return Env::Status::Success;
                }

                bInUse = true;

                auto wantedDomain = ExtractDomainFromIdentity( identity );
                auto wantedIdentity = InjectDomainIntoIdentity( localIdentity, wantedDomain );

                Context localContext( e.shared_from_this(), InjectDomainIntoIdentity( localIdentity, wantedDomain ) );
                auto tokProvider = lex::MakeVectorAdapter( toks );
                auto r = make_shared< parse::Resolver >( tokProvider, localContext );
                Parser p( r );

                if( !p.parseExpression() )
                {
                    dm.emitErrorMessage( loc, "invalid expression." );
                    result = ValueToIRExpr( PoisonValue() );
                    bInUse = false;
                    return Env::Status::Success;
                }

                auto val = p.popValue();
                if( !val )
                {
                    dm.emitErrorMessage( loc, "invalid expression." );
                    val = PoisonValue();
                }

                if( !val->isPoison() && !val->isConstant() )
                {
                    dm.emitErrorMessage( loc, "using expression doesn't evaluate to a constant." );
                    val = PoisonValue();
                }

                result = ValueToIRExpr( *val );
                e.storeValue( wantedIdentity, ANYTERM( _ ), result );

                bInUse = false;
                return Env::Status::Success;
            };

            context.env()->storeValue( localIdentity, ANYTERM( _ ),
                make_shared< Env::ValueProvider >( move( UsingValProvider ) ) );

            return true;
        };

        Rule r( handleUsing );
        auto ruleVal = ToValue( move( r ) );
        auto ruleTerm = ValueToIRExpr( ruleVal );
        e.storeValue( AppendToVectorTerm( RootIdentity(), TSID( using ) ), ANYTERM( _ ), ruleTerm );
    }
}
