#include "builtins/builtins.h"
#include "codegen/codegen.h"
#include "builtins/helpers.h"

using namespace goose;
using namespace goose::builtins;
using namespace goose::codegen;

namespace goose::builtins
{
    void SetupRuntimeBasicTypes( Env& e )
    {
        e.storeValue( AppendToVectorTerm( RootIdentity(), TSID( half ) ), ANYTERM( _ ), ValueToIRExpr( ToValue( HalfFloatType() ) ) );
        e.storeValue( AppendToVectorTerm( RootIdentity(), TSID( float ) ), ANYTERM( _ ), ValueToIRExpr( ToValue( FloatType() ) ) );
        e.storeValue( AppendToVectorTerm( RootIdentity(), TSID( double ) ), ANYTERM( _ ), ValueToIRExpr( ToValue( DoubleFloatType() ) ) );

        RegisterBuiltinFunc< Eager< Value > ( uint32_t ) >( e, "uint"_sid,
            []( uint32_t numBits )
            {
                return ToValue( IntegerType( numBits ) );
            } );

        RegisterBuiltinFunc< Eager< Value > ( uint32_t ) >( e, "sint"_sid,
            []( uint32_t numBits )
            {
                return ToValue( IntegerType( numBits, true ) );
            } );
    }

    const Term& IntegerType::Pattern::GetPattern()
    {
        static auto pattern = ValueToIRExpr( Value( TypeType(), MkStdRTType( TSID( integer ),
            nullptr,
            VEC( MkHole( "size"_sid ), MkHole( "signedness"_sid ) ) ) ) );

        return pattern;
    }

    const Term& IntegerType::PatternSigned::GetPattern()
    {
        static auto pattern = ValueToIRExpr( Value( TypeType(), MkStdRTType( TSID( integer ),
          nullptr,
            VEC( MkHole( "size"_sid ), TERM( 1U ) ) ) ) );

        return pattern;
    }

    const Term& IntegerType::PatternUnsigned::GetPattern()
    {
        static auto pattern = ValueToIRExpr( Value( TypeType(), MkStdRTType( TSID( integer ),
         nullptr,
            VEC( MkHole( "size"_sid ), TERM( 0U ) ) ) ) );

        return pattern;
    }

    llvm::Type* GetLLVMType( const HalfFloatType& t )
    {
        return llvm::Type::getHalfTy( GetLLVMContext() );
    }

    llvm::Type* GetLLVMType( const FloatType& t )
    {
        return llvm::Type::getFloatTy( GetLLVMContext() );
    }

    llvm::Type* GetLLVMType( const DoubleFloatType& t )
    {
        return llvm::Type::getDoubleTy( GetLLVMContext() );
    }

    llvm::Type* GetLLVMType( const IntegerType& t )
    {
        return llvm::IntegerType::get( GetLLVMContext(), t.m_numBits );
    }
}

namespace goose::ir
{
    //// Half
    Value Bridge< HalfFloatType >::ToValue( const HalfFloatType& t )
    {
        return Value( Type(), MkStdRTType( TSID( half ), GetLLVMType( t ) ) );
    }

    optional< HalfFloatType > Bridge< HalfFloatType >::FromValue( const Value& v )
    {
        if( v != ToValue( HalfFloatType() ) )
            return nullopt;

        return {};
    }

    //// Float
    Value Bridge< FloatType >::ToValue( const FloatType& t )
    {
        return Value( Type(), MkStdRTType( TSID( float ), GetLLVMType( t ) ) );
    }

    optional< FloatType > Bridge< FloatType >::FromValue( const Value& v )
    {
        if( v != ToValue( FloatType() ) )
            return nullopt;

        return {};
    }

    //// Double
    Value Bridge< DoubleFloatType >::ToValue( const DoubleFloatType& t )
    {
        return Value( Type(), MkStdRTType( TSID( double ), GetLLVMType( t ) ) );
    }

    optional< DoubleFloatType > Bridge< DoubleFloatType >::FromValue( const Value& v )
    {
        if( v != ToValue( DoubleFloatType() ) )
            return nullopt;

        return {};
    }

    //// Integer
    Value Bridge< IntegerType >::ToValue( const IntegerType& t )
    {
        return Value( Type(), MkStdRTType( TSID( integer ), GetLLVMType( t ),
            VEC( TERM( t.m_numBits ), t.m_signed ? TERM( 1U ) : TERM( 0U ) ) ) );
    }

    optional< IntegerType > Bridge< IntegerType >::FromValue( const Value& v )
    {
        auto result = Decompose( v.val(),
            Vec(
                Lit( "integer"_sid ),
                Val< ptr< void > >(),
                Val< void* >(),
                Vec(
                    Val< uint32_t >(),
                    Val< uint32_t >()
                )
            )
        );

        if( !result )
            return nullopt;

        auto&& [predicates, llvmType, params] = *result;
        auto&& [numBits, signd] = params;
        return IntegerType( numBits, !!signd );
    }

    Term Bridge< APSInt >::Type( const APSInt& i )
    {
        return ValueToIRExpr( ir::ToValue(
            IntegerType( i.getBitWidth(), i.isSigned() ) ) );
    }

    Value Bridge< APSInt >::ToValue( const APSInt& i )
    {
        return Value( Type( i ), TERM( i ) );
    }

    optional< APSInt > Bridge< APSInt >::FromValue( const Value& v )
    {
        auto result = Decompose( v.val(),
            Val< APSInt >()
        );

        if( !result )
            return nullopt;

        return *result;
    }


    const Term& Bridge< uint8_t >::Type()
    {
        static auto type = ValueToIRExpr( ir::ToValue(
            IntegerType( 8, false ) ) );
        return type;
    }

    Value Bridge< uint8_t >::ToValue( uint8_t x )
    {
        return ir::ToValue( APSInt::getUnsigned( x ).trunc( 8 ) );
    }

    optional< uint8_t > Bridge< uint8_t >::FromValue( const Value& v )
    {
        return FromValue< APSInt >( v )->getLimitedValue();
    }


    const Term& Bridge< uint32_t >::Type()
    {
        static auto type = ValueToIRExpr( ir::ToValue(
            IntegerType( 32, false ) ) );
        return type;
    }

    Value Bridge< uint32_t >::ToValue( uint32_t x )
    {
        return ir::ToValue( APSInt::getUnsigned( x ).trunc( 32 ) );
    }

    optional< uint32_t > Bridge< uint32_t >::FromValue( const Value& v )
    {
        return FromValue< APSInt >( v )->getLimitedValue();
    }


    const Term& Bridge< uint64_t >::Type()
    {
        static auto type = ValueToIRExpr( ir::ToValue(
            IntegerType( 64, false ) ) );
        return type;
    }

    Value Bridge< uint64_t >::ToValue( uint64_t x )
    {
        return ir::ToValue( APSInt::getUnsigned( x ) );
    }

    optional< uint64_t > Bridge< uint64_t >::FromValue( const Value& v )
    {
        return FromValue< APSInt >( v )->getLimitedValue();
    }
}
