#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 )
    {
        const auto& cb = c.codeBuilder();
        assert( cb );
        const auto& cfg = cb->cfg();

        auto index = cfg->getNewTemporaryIndex();

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

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

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

        LocalVar lv( name, ValueToEIR( typeVal ), index );
        bb->emplace_back( AllocVar( typeVal, index ) );

        Value initResult;

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

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

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

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

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

        cb->pushLiveValue( locVar, lv.index() );
        return locVar;
    }

    Value DeclareLocalVarWithTypeInference( Context& c, const Term& typeTExpr, StringId name, const Value& initVal )
    {
        const auto& cb = c.codeBuilder();
        assert( cb );
        const auto& cfg = cb->cfg();

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

        // To infer the type and obtain a suitable initialization function, we typecheck
        //          ( $$T, _ ( MutRef[$$T], initValType ) initFunc )
        // against  ( pat, Initialize                              )
        //
        // Where:
        //   pat is the type pattern provided for the declaration,
        //   initValType is the initialization expression's type,
        //   Initialize is the Initialize overload set.
        //
        // The possible types that can be infered according to the initialization expression
        // are therefore defined by the Initialize overloads.

        // The best solution (if any) will give us the wanted type and
        // the function to invoke to initialize it.

        // Create the _ texpr.
        static auto TVarAny = ValueToEIR( ToValue( TVar( "_"_sid ) ) );

        // The $$T texpr.
        static auto TTVarT = ValueToEIR( ToValue( TTVar( "T"_sid ) ) );

        // Create the MutRef[$$T] param.
        static auto mutRefParam = ValueToEIR( ToValue( TNamedDecl(
            ValueToEIR( ToDeclValue( ReferenceType{ TTVarT, TSID( mut ) } ) ), "ref"_sid ) ) );

        auto initValParam = ValueToEIR( ToValue( Decl( initVal.type(), "initVal"_sid ) ) );

        // Create the _ ( MutRef[$$T], initType ) initFunc param.
        auto initFuncTFTParam = ValueToEIR( ToValue(
            TNamedDecl( ValueToEIR( ToValue( TFuncType( TVarAny, VEC( move( mutRefParam ), move( initValParam ) ),
            make_shared< vector< TermLoc > >(), make_shared< vector< TermLoc > >() ) ) ), "initFunc"_sid ) ) );

        // Create our parameter list.
        auto paramPat = VEC(
            *BuildTemplateSignature( c, TTVarT ),
            *BuildTemplateSignature( c, initFuncTFTParam )
        );

        // Create our arg list.
        auto args = VEC(
            *BuildTemplateArgPattern( c, typeTExpr ),
            ValueToEIR( ToValue( c.env()->extInitialize() ) )
        );

        auto us = FindBestTypingVec( paramPat, args, c );

        if( holds_alternative< NoUnification >( us ) )
        {
            // TODO display details
            DiagnosticsManager::GetInstance().emitErrorMessage( 0,
                "variable initialization type mismatch." );
            return PoisonValue();
        }

        if( holds_alternative< AmbiguousTypeCheck >( us ) )
        {
            // TODO display details
            DiagnosticsManager::GetInstance().emitErrorMessage( 0,
                "ambiguous variable type inference." );
            return PoisonValue();
        }

        auto&& [s,tcc] = get< TCSol >( us );

        // Perform the setup of the type template expression. This will create local bindings
        // for tvars ($whatever), if any, making them available for further use.

        // We need to create a temporary context and adjust the identity so that the tvar
        // bindings will be injected to our parent scope, rather than in our local statement scope.
        auto parentIdentity = TakeVectorTerm( c.identity(), VecSize( c.identity() ) - 2 );
        Context ctxt( c.env(), parentIdentity );

        // The TVar setup expects to find its name on the left hand side of the type checking,
        // But in our case we have put it on the rhs, so we need to flip the context around
        // before setup.
        TemplateSetup( ctxt, tcc.flip(), typeTExpr );

        auto callDecomp = Decompose( s,
            Vec(
                SubTerm(),  // locvar
                SubTerm()   // initializer
            )
        );

        auto&& [type, initializer] = *callDecomp;

        if( !ParseTypePredicates( c, c.identity(), *ValueFromEIR( type ) ) )
            return PoisonValue();

        auto index = cfg->getNewTemporaryIndex();

        // 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.
        uint32_t typeLoc = ValueFromEIR( typeTExpr )->locationId();
        LocalVar lv( name, type, index );

        bb->emplace_back( AllocVar( ValueFromEIR( lv.type() )->setLocationId( typeLoc ), index ) );

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

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

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

        auto locVar = ToValue( lv );

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

        cb->pushLiveValue( locVar, lv.index() );
        return locVar;
    }
}

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 >( *ValueFromEIR( 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< uint32_t >()
            )
        );

        if( !result )
            return nullopt;

        auto&& [name,index] = *result;

        return LocalVar( name, move( t->type() ), index );
    }
}
