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

using namespace goose::builtins;
using namespace goose::cir;
using namespace goose::parse;

namespace goose::builtins
{
    const Term& LocalVar::PatternAny::GetPattern()
    {
        static auto pattern = GetValueType< LocalVar >( HOLE( "_"_sid ) );
        return pattern;
    }

    const Term& LocalVar::PatternAnyOfTypeT::GetPattern()
    {
        static auto pattern = GetValueType< LocalVar >( HOLE( "T"_sid ) );
        return pattern;
    }

    Value DeclareLocalVar( const Context& c, const Term& type, StringId name,
        const optional< Value >& initializer, LocationId locId )
    {
        auto cfg = GetCFG( c );
        if( !cfg )
            return PoisonValue();

        auto bb = cfg->currentBB();
        if( !bb )
            return PoisonValue();

        Value typeVal = *EIRToValue( type );
        if( !typeVal.isType() )
            typeVal = ToType( c, typeVal );

        if( !ParseTypePredicates( c, typeVal ) )
            return PoisonValue();

        auto index = cfg->nextValueIndex();
        LocalVar lv( name, ValueToEIR( typeVal ), index );
        bb->append( AllocVar( lv.type(), index, locId ) );

        Value initResult;

        {
            DiagnosticsContext dc( 0, "When invoking _Initialize." );

            if( initializer )
            {
                initResult = InvokeOverloadSet( c, c.env()->extInitialize(),
                    MakeClosedTuple( ToValue( lv ).setLocationId( locId ), *initializer ), locId );
            }
            else
            {
                initResult = InvokeOverloadSet( c, c.env()->extInitialize(),
                    MakeClosedTuple( ToValue( lv ).setLocationId( locId ) ), locId );
            }
        }

        if( !initResult.isPoison() )
        {
            DiagnosticsContext dc2( initResult.locationId(), "When invoking _DropValue." );
            InvokeOverloadSet( c, c.env()->extDropValue(),
                MakeClosedTuple( c.builder(), move( initResult ) ), initResult.locationId() );
        }

        auto locVar = ToValue( lv );
        auto identity = AppendToVectorTerm( c.identity(), name );

        c.env()->storeValue( identity, ANYTERM( _ ), ValueToEIR( locVar ) );

        DeclareValue( c, locVar, lv.index() );
        return locVar;
    }

    Value DeclareLocalVarWithTypeInference( const Context& c, const Term& typeTExpr, StringId name,
        const Value& initVal, LocationId locId )
    {
        auto cfg = GetCFG( c );
        if( !cfg )
            return PoisonValue();

        auto bb = cfg->currentBB();
        if( !bb )
            return PoisonValue();

        auto type = InferTypeFromTExprAndInitializer( c, typeTExpr, initVal );
        if( type.isPoison() )
            return PoisonValue();

        auto index = cfg->nextValueIndex();

        // Retrieve the texpr's location and set it on the inferred type. This way if an
        // error occurs later with it, for instance when calling LowerTypeForRuntime on it during
        // codegen, it will have a meaningful location for the error message to attach itself on.
        LocalVar lv( name, ValueToEIR( type ), index );
        bb->append( AllocVar( lv.type(), index, locId ) );

        DiagnosticsContext dc( 0, "When invoking _Initialize." );

        auto initResult = InvokeOverloadSet( c, c.env()->extInitialize(),
            MakeClosedTuple( ToValue( lv ).setLocationId( locId ), initVal ) );

        if( !initResult.isPoison() )
        {
            DiagnosticsContext dc2( initResult.locationId(), "When invoking _DropValue." );
            InvokeOverloadSet(
                c, c.env()->extDropValue(), MakeClosedTuple( c.builder(), move( initResult ) ) );
        }

        auto locVar = ToValue( lv );

        if( name != ""_sid )
        {
            auto identity = AppendToVectorTerm( c.identity(), name );
            c.env()->storeValue( identity, ANYTERM( _ ), ValueToEIR( locVar ) );
        }

        DeclareValue( c, locVar, lv.index() );
        return locVar;
    }
} // namespace goose::builtins

namespace goose::eir
{
    const Term& Bridge< LocalVarType >::Type()
    {
        return TypeType();
    }

    Value Bridge< LocalVarType >::ToValue( const LocalVarType& t )
    {
        return Value( Type(), TVEC( TSID( ct_type ), TSID( local_var ), t.type() ) );
    }

    Value Bridge< LocalVarType >::ToValue( const Term& type )
    {
        return Value( Type(), TVEC( TSID( ct_type ), TSID( local_var ), type ) );
    }

    optional< LocalVarType > Bridge< LocalVarType >::FromValue( const Value& v )
    {
        auto result =
            Decompose( v.val(), Vec( Lit( "ct_type"_sid ), Lit( "local_var"_sid ), SubTerm() ) );

        if( !result )
            return nullopt;

        auto&& [type] = *result;
        return LocalVarType( move( type ) );
    }

    Term Bridge< LocalVar >::Type( const Term& type )
    {
        return ValueToEIR( ToValue< LocalVarType >( type ) );
    }

    Value Bridge< LocalVar >::ToValue( const LocalVar& lv )
    {
        return Value( Type( lv.type() ), VEC( TERM( lv.name() ), TERM( lv.index() ) ) );
    }

    optional< LocalVar > Bridge< LocalVar >::FromValue( const Value& v )
    {
        auto t = FromValue< LocalVarType >( *EIRToValue( v.type() ) );
        if( !t )
            return nullopt;

        if( !v.isConstant() )
        {
            // This is an abstract local variable value, this may happen when
            // typechecking abstract arguments while resolving function typed parameters
            return LocalVar( move( t->type() ) );
        }

        auto result = Decompose( v.val(), Vec( Val< StringId >(), Val< uint64_t >() ) );

        if( !result )
            return nullopt;

        auto&& [name, index] = *result;
        return LocalVar( name, move( t->type() ), index );
    }
} // namespace goose::eir
