#include "builtins/builtins.h"
#include "precedence.h"
#include "helpers.h"
#include "tuple.h"

using namespace goose;
using namespace goose::eir;
using namespace goose::cir;
using namespace goose::parse;

namespace goose::builtins
{
    Generator< Value > GenerateRHSValuesFromTupleRef( const ptr< OverloadSet >& assOp, const Context& c, const Value& tup )
    {
        auto cfg = GetCFG( c );
        if( !cfg )
            co_yield Value( PoisonValue() );

        auto tupRefType = *FromValue< ReferenceType >( *EIRToValue( tup.type() ) );

        // Store the source tuple as a temporary, otherwise we're going to recompute rhs
        // for each member assigned (which is bad)
        // TODO_SSA rewrite this (since CreateTemporary makes no sense anymore)
        co_return;
    /*    auto index = util::GenerateNewUID();
        cfg->currentBB()->append( tup, CreateTemporary( index, false, tup.locationId() ) );
        auto srcTup = BuildComputedValue( tupRefType.type(), GetTemporary( tupRefType.type(), index, tup.locationId() ) );

        auto anonVarDecl = ToValue( TNamedDecl( ValueToEIR( ToValue( TVar( "_"_sid ) ) ), ""_sid ) );
        co_yield GenerateValuesFromComputedTuple( srcTup );*/
    };

    // Assignment of a compile time tuple or tuple reference to another compile time tuple, directly. Most tuple assignment will go
    // through one of the other overload that takes a mutref to a tuple on the lhs, but sometimes we have compilation time tuples of
    // objects that can directly be assigned such as decls or refs.
    //
    // So this overload is for all these cases, which encompass initializing multiple comma separated decls from a tuple, as
    // well as variable swapping/shuffling. To make sure that the later works properly, we store all the rhs values into
    // temporary variables first, before assigning them to the lhs values.
    Value NonRefTupleAssignment( const ptr< OverloadSet >& assOp, Context& c, const Value& lhs, const Value& rhs )
    {
        auto tupSize = TupleSize( lhs );

        // Load the values from the rhs tuple into temporary vars.
        vector< Value > tempVars;
        tempVars.reserve( tupSize );

        Generator< Value > gen;
        if( rhs.isConstant() )
            gen = GenerateValuesFromConstantTuple( rhs );
        else
            gen = GenerateRHSValuesFromTupleRef( assOp, c, rhs );

        for( auto&& srcVal : gen )
        {
            // We go at very high level to construct the temporary variables by resolving an invocation
            // of the assignment of the rhs value to a TNamedDecl with an empty name.
            // The reason we need to work at such high level is so that the initializer value gets
            // resolved exactly like if it was a regular assignment. In particular, if the value in question
            // is a locvar, we want it to go through the type checking process to be replaced with its content.
            // This is the simplest and most robust way to achieve this, which should honor every relevant
            // extension point.
            auto tmpVar = DeclareLocalVarWithTypeInference( c,
                ValueToEIR( ToValue( TVar( "_"_sid ) ) ), ""_sid, srcVal, srcVal.locationId() );

            if( tmpVar.isPoison() )
                return PoisonValue();

            tmpVar.setLocationId( srcVal.locationId() );
            tempVars.emplace_back( move( tmpVar ) );
        }

        if( tupSize != tempVars.size() )
        {
            DiagnosticsManager::GetInstance().emitErrorMessage( 0, "incompatible tuple sizes." );
            return PoisonValue();
        }

        auto result = EmptyOpenTuple();

        size_t i = 0;
        bool success = true;
        ForEachInTuple( lhs, [&]( auto&& destVal )
        {
            auto r = InvokeOverloadSet( c,
                assOp, MakeClosedTuple( destVal, tempVars[i++] ) );

            if( r.isPoison() )
            {
                success = false;
                return false;
            }

            result = AppendToTuple( result, move( r ) );
            return true;
        } );

        if( !success )
            return PoisonValue();

        return result;
    };

    Value MutRefTupleAssignment( const ptr< OverloadSet >& assOp, Context& c, const Value& lhs, const Value& rhs )
    {
        auto lhsRefType = *FromValue< ReferenceType >( *EIRToValue( lhs.type() ) );
        auto lhsTupType = *EIRToValue( lhsRefType.type() );

        auto rhsTupTypeEIR = rhs.type();
        if( !rhs.isConstant() )
        {
            auto rhsRefType = *FromValue< ReferenceType >( *EIRToValue( lhs.type() ) );
            rhsTupTypeEIR = rhsRefType.type();
        }

        auto rhsTupType = *EIRToValue( rhsTupTypeEIR );

        if( IsTrivialTupleAssignment( c, lhsTupType, rhsTupType ) )
        {
            auto cfg = GetCFG( c );
            if( !cfg )
            {
                DiagnosticsManager::GetInstance().emitSyntaxErrorMessage( 0, "assignments are not allowed here." );
                return PoisonValue();
            }
            else if( auto bb = cfg->currentBB() )
            {
                bb->append( rhs );
                if( !rhs.isConstant() )
                    bb->append( Load( rhsTupTypeEIR, rhs.locationId() ) );
                bb->append( lhs, Store( lhsRefType.type(), rhs.locationId(), lhs.locationId() ) );
            }
            return lhs;
        }

        auto result = EmptyOpenTuple();

        Generator< Value > gen;
        if( rhs.isConstant() )
            gen = GenerateValuesFromConstantTuple( rhs );
        else
            gen = GenerateRHSValuesFromTupleRef( assOp, c, rhs );

        uint32_t index = 0;
        bool success = true;
        ForEachInTupleType( lhsTupType, [&]( auto&& t )
        {
            if( gen.finished() )
            {
                DiagnosticsManager::GetInstance().emitErrorMessage( 0, "incompatible tuple sizes." );
                success = false;
                return false;
            }

            auto srcVal = gen.consume();
            auto rt = ValueToEIR( ToValue( ReferenceType( t, MutAccessSpecifier() ) ) );
            auto destVal = BuildComputedValue( rt, lhs, cir::Select( index++, lhs.locationId() ) );

            auto r = InvokeOverloadSet( c,
                assOp, MakeClosedTuple( destVal, srcVal ) );

            success = !r.isPoison();
            return success;
        } );

        if( !success )
            return PoisonValue();

        if( !gen.finished() )
        {
            DiagnosticsManager::GetInstance().emitErrorMessage( 0, "incompatible tuple sizes." );
            return PoisonValue();
        }

        return lhs;
    };
}
