#include "builtins/builtins.h"

using namespace empathy;
using namespace empathy::ir;

namespace empathy::builtins
{
    void SetupRuntimeTypesUnification( Env& e )
    {
        auto rtIntTypePattern = Value( TypeType(), VEC( TSID( rt_type ),
            ANYTERM( _ ),
            TSID( integer ), ANYTERM( _ ), ANYTERM( _ ) ) );

        // ct_int type against a IntegerType type:
        // return the IntegerType type. We don't care if the
        // ct_int fits at this point, this will be dealt with by
        // the ct_int value unification rule below.
        e.unificationRuleSet()->addSymRule(
            ValueToIRExpr( rtIntTypePattern ),
            GetValueType< BigInt >(),
        []( const Term& lhs, const Term& rhs, UnificationContext& c ) -> UniGen
        {
            co_yield HalfUnify( lhs, c );
        } );

        // Reject the ct_int param and IntegerType arg pattern,
        // so that it doesn't fall into the rule above which would be incorrect
        // in that case.
        e.unificationRuleSet()->addAsymRule(

            ValueToIRExpr( ValuePattern(
                ANYTERM( _ ),
                GetValueType< BigInt >(),
                ANYTERM( _ ) ) ),

            ValueToIRExpr( ValuePattern(
                ANYTERM( _ ),
                ValueToIRExpr( rtIntTypePattern ),
                ANYTERM( _ ) ) ),

        []( const Term& lhs, const Term& rhs, UnificationContext& c ) -> UniGen
        {
            co_return;
        } );

        // ct_integer constant unification against a IntegerType:
        // Check if the IntegerType is big enough for the constant,
        // and emit a LoadConstantInt llr instruction if so.
        e.unificationRuleSet()->addAsymRule(

            ValueToIRExpr( ValuePattern(
                ANYTERM( _ ),
                ValueToIRExpr( rtIntTypePattern ),
                ANYTERM( _ ) ) ),

            ValueToIRExpr( ValuePattern(
                ANYTERM( _ ),
                GetValueType< BigInt >(),
                ANYTERM( _ ) ) ),

        []( const Term& lhs, const Term& rhs, UnificationContext& c ) -> UniGen
        {
            // The rhs might not be constant. It can happen even with ct_int if the rhs is a
            // fake argument pattern generated to unify a higher-order function param.
            // It means that we are unifying a function parameter against any ct_int value,
            // which we can't (because we have to know the value to safely convert it),
            // so we reject it.
            auto rhsVal = *ValueFromIRExpr( rhs );
            if( !rhsVal.isConstant() )
                co_return;

            // Don't bother during the first pass, the rt integer type pattern
            // is likely to contain unresolved holes.
            if( !c.secondPass() )
            {
                co_yield { lhs, c };
                co_return;
            }

            auto ct = FromValue< BigInt >( rhsVal );
            if( !ct )
                co_return;

            auto lhsVal = ValuePatternFromIRExpr( lhs );
            if( !lhsVal )
                co_return;

            auto rttypeVal = ValueFromIRExpr( lhsVal->type() );
            if( !rttypeVal )
                co_return;

            auto rttype = FromValue< IntegerType >( *rttypeVal );
            if( !rttype )
                co_return;

            APSInt valToLoad;

            if( rttype->m_signed )
            {
                if( ct->getMinSignedBits() > rttype->m_numBits )
                    co_return;

                valToLoad = ct->sext( rttype->m_numBits );
                valToLoad.setIsSigned( true );
            }
            else
            {
                if( ct->isNegative() )
                    co_return;

                if( ct->getActiveBits() > rttype->m_numBits )
                    co_return;

                valToLoad = ct->zext( rttype->m_numBits );
                valToLoad.setIsSigned( false );
            }

            auto* llvmType = static_cast< llvm::IntegerType* >( GetLLVMType( *rttypeVal ) );
            co_yield { ValueToIRExpr(
                BuildComputedValue( lhsVal->type(), llr::LoadConstInt( llvmType, move( valToLoad ) ) ) ), c };
        } );

        auto rtInt8TypePattern = Value( TypeType(), VEC( TSID( rt_type ),
            ANYTERM( _ ),
            TSID( integer ), TERM( 8U ), ANYTERM( _ ) ) );

        auto rtInt8PtrTypePattern = Value( TypeType(), VEC( TSID( rt_type ),
            ANYTERM( _ ),
            TSID( pointer ), ValueToIRExpr( rtInt8TypePattern ) ) );

        // ct_int type against a char*:
        // return the char* type.
        e.unificationRuleSet()->addSymRule(
            ValueToIRExpr( rtInt8PtrTypePattern ),
            GetValueType< string >(),
        []( const Term& lhs, const Term& rhs, UnificationContext& c ) -> UniGen
        {
            co_yield HalfUnify( lhs, c );
        } );

        // Reject the ct_string param and char* arg pattern,
        // so that it doesn't fall into the rule above which wouls be incorrect
        // in that case.
        e.unificationRuleSet()->addAsymRule(

            ValueToIRExpr( ValuePattern(
                ANYTERM( _ ),
                GetValueType< string >(),
                ANYTERM( _ ) ) ),

            ValueToIRExpr( ValuePattern(
                ANYTERM( _ ),
                ValueToIRExpr( rtInt8PtrTypePattern ),
                ANYTERM( _ ) ) ),

        []( const Term& lhs, const Term& rhs, UnificationContext& c ) -> UniGen
        {
            co_return;
        } );

        // ct_string constant unification against a pointer to a integer( 8 ):
        // Emit a LoadConstantStr llr instruction.
        e.unificationRuleSet()->addSymRule(

            ValueToIRExpr( Value(
                GetValueType< string >(),
                ANYTERM( _ ) ) ),

            ValueToIRExpr( ValuePattern(
                ANYTERM( _ ),
                ValueToIRExpr( rtInt8PtrTypePattern ),
                ANYTERM( _ ) ) ),

        []( const Term& lhs, const Term& rhs, UnificationContext& c ) -> UniGen
        {
            // Don't bother during the first pass.
            if( !c.secondPass() )
            {
                co_yield { lhs, c };
                co_return;
            }

            auto str = *FromValue< string >( *ValueFromIRExpr( lhs ) );
            auto rhsVal = *ValuePatternFromIRExpr( rhs );

            co_yield { ValueToIRExpr(
                BuildComputedValue( rhsVal.type(), llr::LoadConstStr( str ) ) ), c };
        } );
    }
}
