#include "cir/cir.h"
#include "builtins/builtins.h"

using namespace goose::builtins;

namespace goose::cir
{
    bool Call::canBeExecuted() const
    {
        if( !IsValueConstantOrExecutable( m_func ) )
            return false;

        if( IsExternalFunc( m_func ) )
            return false;

        bool argsCanBeExecuted = true;
        ForEachInVectorTerm( m_args, [&]( auto&& arg )
        {
            if( !IsValueConstantOrExecutable( *EIRToValue( arg ) ) )
            {
                argsCanBeExecuted = false;
                return false;
            }

            return true;
        } );

        if( !argsCanBeExecuted )
            return false;

        if( IsBuiltinFunc( m_func ) )
            return true;

        const auto* pFunc = GetFuncCIR( m_func );

        // If the func is passed as a value, we might not have been able to evaluate
        // it yet, so we assume it's going to be executable. If it isn't, we'll fail
        // at execution time, which isn't a big deal - we should be able to avoid trying
        // executing it in the first place in most cases.
        return !pFunc || pFunc->canBeExecuted();
    }

    bool Call::canBeEagerlyEvaluated() const
    {
        // Functions with void return type are assumed to have side effects
        // and therefore that they should never be eagerly evaluated,
        // It's not like they would need to be anyway.
        // An exception is builtin functions that have been explicitely marked to be eagerly evaluated.
        if( GetFuncRType( m_func ) == GetValueType< void >() )
            return IsEagerBuiltinFunc( m_func );

        if( IsNonEagerBuiltinFunc( m_func ) )
            return false;

        if( IsExternalFunc( m_func ) )
            return false;

        bool argsAreConstant = true;
        ForEachInVectorTerm( m_args, [&]( auto&& arg )
        {
            if( !CanValueBeEagerlyEvaluated( *EIRToValue( arg ) ) )
            {
                argsAreConstant = false;
                return false;
            }

            return true;
        } );

        if( !argsAreConstant )
            return false;

        if( IsEagerBuiltinFunc( m_func ) )
            return true;

        const auto* pFuncCIR = GetFuncCIR( m_func );

        if( !pFuncCIR )
            return false;

        return pFuncCIR->canBeExecuted();
    }

    ostream& operator<<( ostream& out, const Call& ins )
    {
        return out << "CALL(" << ins.m_func << ", " << ins.m_args << ')';
    }
}
