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

using namespace empathy;
using namespace empathy::builtins;
using namespace empathy::codegen;

namespace empathy::builtins
{
    void SetupRuntimeStructType( Env& e )
    {
        auto buildStruct = []( const Value& types, uint64_t packed, const optional< Term > identity )
        {
            if( !IsTuple( types ) )
            {
                cout << "panic: StructType expects a tuple as first param.\n";
                exit( 0 );
            }

            bool ok = true;
            ForEachInTuple( types, [&]( auto&& t )
            {
                if( !GetLLVMType( t ) )
                {
                    ok = false;
                    return false;
                }

                return true;
            } );

            if( !ok )
            {
                cout << "panic: StructType expects a tuple of runtime types as first param.\n";
                exit( 0 );
            }

            return ToValue( StructType( identity, get< pvec >( types.val().content() ), !!packed ) );
        };

        RegisterBuiltinFunc< Eager< Value > ( Value, uint64_t ) >( e, "struct"_sid,
            [&]( const Value& types, uint64_t packed )
            {
                return buildStruct( types, packed, nullopt );
            } );

        // TODO: make an overload of StructType that can accept a term to use as an identity
        // but first I need to implemented the wrapping of terms as values.
    }
}

namespace empathy::ir
{
    Value Bridge< StructType >::ToValue( const StructType& s )
    {
        vector< llvm::Type* > elements;
        elements.reserve( s.m_memberTypes->terms().size() );

        for( auto&& mt : s.m_memberTypes->terms() )
            elements.emplace_back( GetLLVMType( *ValueFromIRExpr( mt ) ) );

        auto* pllvmType = llvm::StructType::get( GetLLVMContext(), elements, s.m_packed );

        if( s.m_identity )
        {
            return Value( Type(), TVEC( TSID( rt_type ), TERM( pllvmType ),
                TSID( struct ), TERM( s.m_packed ? 1ULL : 0ULL ),
                Quote( *s.m_identity ), TERM( s.m_memberTypes ) ) );
        }

        return Value( Type(), TVEC( TSID( rt_type ), TERM( pllvmType ),
            TSID( struct ), TERM( s.m_packed ? 1ULL : 0ULL ),
            TERM( s.m_memberTypes ) ) );
    }

    optional< StructType > Bridge< StructType >::FromValue( const Value& v )
    {
        auto result = Decompose( v.val(),
            Vec(
                Lit( "rt_type"_sid ),
                Val< void* >(),
                Lit( "struct"_sid ),
                Val< uint64_t >(),
                SubTerm(),
                Val< pvec >()
            )
        );

        if( !result )
        {
            auto result = Decompose( v.val(),
                Vec(
                    Lit( "struct"_sid ),
                    Val< void* >(),
                    Val< uint64_t >(),
                    Val< pvec >()
                )
            );

            if( !result )
                return nullopt;

            auto&& [llvmType, packed, memberTypes] = *result;
            return StructType( memberTypes, !!packed );
        }

        auto&& [llvmType, packed, identity, memberTypes] = *result;
        return StructType( *Unquote( identity ), memberTypes, !!packed );
    }
}
