#include "builtins/builtins.h"

using namespace goose;
using namespace goose::eir;

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

        // Conversion rule for ct_int to runtime int:
        // we verify that the ct_int fits in the bitsize/signage
        // of the runtime int
        e.typeCheckingRuleSet()->addTypeCheckingRule( TCRINFOS,

            ValueToEIR( ValuePattern(
                ANYTERM( _ ),
                ValueToEIR( rtIntTypePattern ),
                ANYTERM( _ ) ) ),

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

        []( const Term& lhs, const Term& rhs, TypeCheckingContext tcc ) -> TCGen
        {
            // 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 = *EIRToValue( rhs );
            if( !rhsVal.isConstant() )
                co_return;

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

            auto s = HalfUnify( lhsVal->type(), tcc );
            if( !s )
                co_return;

            auto wrapped = WrapWithPostprocFunc( *s,
            [rhs]( const Term& t, TypeCheckingContext tcc ) -> optional< Term >
            {
                auto argVal = EIRToValue( rhs );
                assert( argVal );

                auto ct = *FromValue< BigInt >( *argVal );

                auto rttypeVal = EIRToValue( t );
                assert( rttypeVal );

                auto rttype = FromValue< IntegerType >( *rttypeVal );
                assert( rttype );

                APSInt valToLoad;

                if( rttype->m_signed )
                {
                    if( ct.getMinSignedBits() > rttype->m_numBits )
                        return nullopt;

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

                    if( ct.getActiveBits() > rttype->m_numBits )
                        return nullopt;

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

                return TERM( valToLoad );
            } );

            co_yield { ValueToEIR( Value( *s, move( wrapped ) )
                .setLocationId( rhsVal.locationId() ) ), tcc };
        } );

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

        auto rtInt8PtrTypePattern = Value( TypeType(), MkStdType( TSID( rt_type ), TSID( pointer ),
            ValueToEIR( rtInt8TypePattern ) ) );

        // ct_string type against a char*:
        // return the char* type.
        e.typeCheckingRuleSet()->addTypeCheckingRule( TCRINFOS,
            ValueToEIR( rtInt8PtrTypePattern ),
            GetValueType< string >(),
        []( const Term& lhs, const Term& rhs, TypeCheckingContext c ) -> TCGen
        {
            if( auto s = HalfUnify( lhs, c ) )
                co_yield { *s, c };
        } );

        auto ptrTypePattern = Value( TypeType(), MkStdType( TSID( rt_type ), TSID( pointer ),
            ANYTERM( _ ) ) );

        // nullptr constant type checking against a pointer of any type;
        // Yield a value of the given pointer type, with a 0 integer as its content.
        // This'll be recognized by codegen to emit a null pointer value of the right type.
        e.typeCheckingRuleSet()->addTypeCheckingRule( TCRINFOS,

            ParamPat( ValueToEIR( ptrTypePattern ) ),

            ValueToEIR( ValuePattern(
                ANYTERM( _ ),
                GetValueType< NullPointer >(),
                ANYTERM( _ ) ) ),

        []( const Term& lhs, const Term& rhs, const TypeCheckingContext& c ) -> TCGen
        {
            auto lVal = *ValuePatternFromEIR( lhs );
            co_yield { ValueToEIR( Value( lVal.type(), 0U ) ), c };
        } );
    }
}
