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

using namespace empathy;
using namespace empathy::ir;
using namespace empathy::llr;
using namespace empathy::parse;

namespace empathy::builtins
{
    void SetupLogicOps( Env& e )
    {
        CreatePrefixOp( e, "!"_sid, "operator_logical_not"_sid, precedence::UnaryOps,
            ForType< bool >( []( auto&& operand ) -> Value
            {
                return BuildComputedValue( GetValueType< bool >(),
                    Xor( operand, ToValue( true ) ) );
            } )
        );

        CreatePrefixOp( e, "~"_sid, "operator_bitwise_not"_sid, precedence::UnaryOps,
            ForType< CustomPattern< RTInteger, RTInteger::Pattern > >( []( auto&& operand ) -> Value
            {
                auto opTypeVal = *ValueFromIRExpr( operand.type() );
                auto opType = *FromValue< RTInteger >( opTypeVal );
                return BuildComputedValue( operand.type(),
                    Xor( operand,
                        BuildComputedValue( operand.type(),
                            LoadConstInt( static_cast< llvm::IntegerType* >( GetLLVMType( opTypeVal ) ),
                            APSInt::getMaxValue( opType.m_numBits, true) )
                        )
                    ) );
            } )
        );

        CreateLeftAssInfixOp( e, "^"_sid, "operator_xor"_sid, precedence::OrOp,
            // Logical xor
            ForType< bool >( []( auto&& lhs, auto&& rhs ) -> Value
            {
                return BuildComputedValue( GetValueType< bool >(),
                    Xor( lhs, rhs ) );
            } ),

            // ct_int xor
            ForType< BigInt >( []( auto&& lhs, auto&& rhs ) -> Value
            {
                return BuildComputedValue( GetValueType< BigInt >(),
                    Xor( lhs, rhs ) );
            } ),

            // runtime integer xor, defined to work for any two integers of same
            // bit size and signedness.
            ForType< CustomPattern< RTInteger, RTInteger::Pattern > >(
            []( auto&& lhs, auto&& rhs ) -> Value
            {
                return BuildComputedValue( lhs.type(),
                    Xor( lhs, rhs ) );
            } )
        );

        CreateLeftAssInfixOp( e, "|"_sid, "operator_or"_sid, precedence::OrOp,
            // ct_int or
            ForType< BigInt >( []( auto&& lhs, auto&& rhs ) -> Value
            {
                return BuildComputedValue( GetValueType< BigInt >(),
                    Or( lhs, rhs ) );
            } ),

            // runtime integer or, defined to work for any two integers of same
            // bit size and signedness.
            ForType< CustomPattern< RTInteger, RTInteger::Pattern > >(
            []( auto&& lhs, auto&& rhs ) -> Value
            {
                return BuildComputedValue( lhs.type(),
                    Or( lhs, rhs ) );
            } ),

            // bool or
            ForType< bool >( []( auto&& lhs, auto&& rhs ) -> Value
            {
                // Build a CFG that implements the control flow for
                // shortcut evaluation.
                auto cfg = make_shared< CFG >();
                auto pLhsBB = cfg->entryBB();
                auto pRhsBB = cfg->createBB();
                auto pEndBB = cfg->createBB();

                // If the lhs is true, skip to the end directly.
                // Otherwise, jump to the BB that computes rhs.
                pLhsBB->setTerminator( CondBranch( lhs, pEndBB, pRhsBB ) );

                auto rhsIndex = cfg->getNewTemporaryIndex();
                pRhsBB->emplace_back( CreateTemporary( cfg->uniqueId(), rhsIndex, rhs ) );
                pRhsBB->setTerminator( Branch( pEndBB ) );

                auto resultIndex = cfg->getNewTemporaryIndex();

                // Build the Phi instruction that will collect the final result.
                auto phi = Phi( *ValueFromIRExpr( GetValueType< bool >() ),
                    2, cfg->uniqueId(), resultIndex );

                // If coming directly from the lhs BB, we know the result is true.
                phi.setIncoming( pLhsBB, ToValue( true ) );

                // Otherwise, the result is whatever was computed by the rhs block.
                phi.setIncoming( pRhsBB, BuildComputedValue( GetValueType< bool >(),
                    GetTemporary( cfg->uniqueId(), rhsIndex ) ) );

                pEndBB->emplace_back( move( phi ) );

                // Build the result val which pulls the temporary created by the
                // cfg above.
                auto resultVal = BuildComputedValue( GetValueType< bool >(),
                    GetTemporary( cfg->uniqueId(), resultIndex ) );

                // Return it
                pEndBB->setTerminator( Ret( move( resultVal ) ) );

                // Pachage our cfg in a value with an inline CFG instruction.
                return BuildComputedValue( GetValueType< bool >(), move( cfg ) );
            } )
        );

        CreateLeftAssInfixOp( e, "&"_sid, "operator_and"_sid, precedence::AndOp,
            // ct_int and
            ForType< BigInt >( []( auto&& lhs, auto&& rhs ) -> Value
            {
                return BuildComputedValue( GetValueType< BigInt >(),
                    And( lhs, rhs ) );
            } ),

            // runtime integer and, defined to work for any two integers of same
            // bit size and signedness.
            ForType< CustomPattern< RTInteger, RTInteger::Pattern > >(
            []( auto&& lhs, auto&& rhs ) -> Value
            {
                return BuildComputedValue( lhs.type(),
                    And( lhs, rhs ) );
            } ),

            // bool or
            ForType< bool >( []( auto&& lhs, auto&& rhs ) -> Value
            {
                // Build a CFG that implements the control flow for
                // shortcut evaluation.
                auto cfg = make_shared< CFG >();
                auto pLhsBB = cfg->entryBB();
                auto pRhsBB = cfg->createBB();
                auto pEndBB = cfg->createBB();

                // If the lhs is false, skip to the end directly.
                // Otherwise, jump to the BB that computes rhs.
                pLhsBB->setTerminator( CondBranch( lhs, pRhsBB, pEndBB ) );

                auto rhsIndex = cfg->getNewTemporaryIndex();
                pRhsBB->emplace_back( CreateTemporary( cfg->uniqueId(), rhsIndex, rhs ) );
                pRhsBB->setTerminator( Branch( pEndBB ) );

                auto resultIndex = cfg->getNewTemporaryIndex();

                // Build the Phi instruction that will collect the final result.
                auto phi = Phi( *ValueFromIRExpr( GetValueType< bool >() ),
                    2, cfg->uniqueId(), resultIndex );

                // If coming directly from the lhs BB, we know the result is false.
                phi.setIncoming( pLhsBB, ToValue( false ) );

                // Otherwise, the result is whatever was computed by the rhs block.
                phi.setIncoming( pRhsBB, BuildComputedValue( GetValueType< bool >(),
                    GetTemporary( cfg->uniqueId(), rhsIndex ) ) );

                pEndBB->emplace_back( move( phi ) );

                // Build the result val which pulls the temporary created by the
                // cfg above.
                auto resultVal = BuildComputedValue( GetValueType< bool >(),
                    GetTemporary( cfg->uniqueId(), resultIndex ) );

                // Return it
                pEndBB->setTerminator( Ret( move( resultVal ) ) );

                // Pachage our cfg in a value with an inline CFG instruction.
                return BuildComputedValue( GetValueType< bool >(), move( cfg ) );
            } )
        );
    }
}
