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

using namespace goose;
using namespace goose::ir;
using namespace goose::llr;
using namespace goose::parse;

namespace goose::builtins
{
    void SetupLogicOps( Env& e )
    {
        BuildParseRule( e, "!"_sid,
            PrefixOp( "operator_logical_not"_sid, precedence::UnaryOps,
                BuildGenericTupleOperator(),

                ForType< bool >( []( auto&& c, auto&& operand ) -> Value
                {
                    return BuildComputedValue( GetValueType< bool >(),
                        llr::Not( operand ) );
                } )
            )
        );

        BuildParseRule( e, "~"_sid,
            PrefixOp( "operator_bitwise_not"_sid, precedence::UnaryOps,
                BuildGenericTupleOperator(),

                ForType< CustomPattern< IntegerType, IntegerType::Pattern > >( []( auto&& c, auto&& operand ) -> Value
                {
                    auto opTypeVal = *ValueFromIRExpr( operand.type() );
                    auto opType = *FromValue< IntegerType >( opTypeVal );
                    return BuildComputedValue( operand.type(),
                        Xor( operand,
                            Value( operand.type(), APSInt::getMaxValue( opType.m_numBits, true ) )
                        ) );
                } )
            )
        );

        BuildParseRule( e, "^"_sid,
            LeftAssInfixOp( "operator_xor"_sid, precedence::OrOp,
                BuildGenericTupleOperator(),

                // Logical xor
                ForType< bool, Xor >(),

                // ct_int xor
                ForType< BigInt, Xor >(),

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

        BuildParseRule( e, "|"_sid,
            LeftAssInfixOp( "operator_or"_sid, precedence::OrOp,
                BuildGenericTupleOperator(),

                // ct_int or
                ForType< BigInt, Or >(),

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

                // bool or
                ForType< bool >( []< typename L, typename R >( auto&& c, L&& lhs, R&& rhs ) -> Value
                {
                    // Handle the case where lhs is constant, so that
                    // the result gets properly eagerly evaluated (in case
                    // we are using the expression as a compile time constant)
                    if( lhs.isConstant() )
                    {
                        if( *FromValue< bool >( lhs ) )
                            return forward< L >( lhs );

                        return forward< R >( rhs );
                    }

                    // When building verification clauses we have bo code builder and no cfg,
                    // and we just want to generate a plain or operation.
                    if( !c.codeBuilder() )
                        return BuildComputedValue( GetValueType< bool >(), Or( lhs, rhs ) );

                    // Build the control flow for shortcut evaluation.
                    auto& cb = c.codeBuilder();

                    const auto& cfg = cb->cfg();
                    const auto& predBB = cfg->currentBB();

                    auto pRhsBB = cfg->createBB();
                    auto pSuccBB = cfg->createBB();

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

                    auto rhsIndex = cfg->getNewTemporaryIndex();
                    pRhsBB->append( CreateTemporary( rhsIndex, rhs ) );
                    pRhsBB->setTerminator( Branch( pSuccBB ) );

                    auto resultIndex = cfg->getNewTemporaryIndex();

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

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

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

                    pSuccBB->append( move( phi ) );
                    cfg->setCurrentBB( pSuccBB );

                    // Build the result val which pulls the temporary created above.
                    return BuildComputedValue( GetValueType< bool >(),
                        GetTemporary( GetValueType< bool >(), resultIndex ) );
                } )
            )
        );

        BuildParseRule( e, "&"_sid,
            LeftAssInfixOp( "operator_and"_sid, precedence::AndOp,
                BuildGenericTupleOperator(),

                // ct_int and
                ForType< BigInt, And >(),

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

                // bool and
                ForType< bool >( []< typename L, typename R >( auto&& c, L&& lhs, R&& rhs ) -> Value
                {
                    // Handle the case where lhs is constant, so that
                    // the result gets properly eagerly evaluated (in case
                    // we are using the expression as a compile time constant)
                    if( lhs.isConstant() )
                    {
                        if( *FromValue< bool >( lhs ) )
                            return forward< R >( rhs );

                        return forward< L >( lhs );
                    }

                    // When building verification clauses we have bo code builder and no cfg,
                    // and we just want to generate a plain and operation.
                    if( !c.codeBuilder() )
                        return BuildComputedValue( GetValueType< bool >(), And( lhs, rhs ) );

                    // Build the control flow for shortcut evaluation.
                    auto& cb = c.codeBuilder();

                    const auto& cfg = cb->cfg();
                    const auto& predBB = cfg->currentBB();

                    auto pRhsBB = cfg->createBB();
                    auto pSuccBB = cfg->createBB();

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

                    auto rhsIndex = cfg->getNewTemporaryIndex();
                    pRhsBB->append( CreateTemporary( rhsIndex, rhs ) );
                    pRhsBB->setTerminator( Branch( pSuccBB ) );

                    auto resultIndex = cfg->getNewTemporaryIndex();

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

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

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

                    pSuccBB->append( move( phi ) );
                    cfg->setCurrentBB( pSuccBB );

                    // Build the result val which pulls the temporary created above.
                    return BuildComputedValue( GetValueType< bool >(),
                        GetTemporary( GetValueType< bool >(), resultIndex ) );
                } )
            )
        );

        BuildParseRule( e, "<<"_sid,
            LeftAssInfixOp( "operator_shift_left"_sid, precedence::BitShiftOp,
                BuildGenericTupleOperator(),

                // ct_int left shift
                ForTypes< BigInt, uint32_t >(
                []( auto&& c, auto&& lhs, auto&& rhs ) -> Value
                {
                    return BuildComputedValue( lhs.type(),
                        Shl( lhs, rhs ) );
                } ),

                // runtime integer left shift.
                ForTypes< CustomPattern< IntegerType, IntegerType::Pattern >,
                    CustomPattern< IntegerType, IntegerType::PatternUnsigned > >(
                []( auto&& c, auto&& lhs, auto&& rhs ) -> Value
                {
                    using namespace goose::builtins::exprbuilder;

                    auto cb = c.codeBuilder();
                    auto cfg = cb->cfg();
                    assert( cfg );

                    // Shifting for a number of bits equal or larger than the bitsize
                    // of lhs is an undefined behavior, so we require verification that
                    // it won't happen.
                    // Extract the integer type of lhs to retreieve its bit size.
                    auto lhsType = *FromValue< IntegerType >( *ValueFromIRExpr( lhs.type() ) );
                    auto bitSizeValue = Value( rhs.type(), APSInt::get( lhsType.m_numBits) );

                    auto cond = ULT( rhs, bitSizeValue );

                    DiagnosticsManager::GetInstance().defineCustomDiagnostic(
                        cond.locationId(), "assert"_sid, "the shift amount may be equal or greater than the bitsize." );

                    cfg->currentBB()->append(
                        llr::Assert( move( cond ) )
                    );

                    return BuildComputedValue( lhs.type(),
                        Shl( lhs, rhs ) );
                } )
            )
        );

        BuildParseRule( e, ">>"_sid,
            LeftAssInfixOp( "operator_shift_right"_sid, precedence::BitShiftOp,
                BuildGenericTupleOperator(),

                // ct_int right shift
                ForTypes< BigInt, uint32_t >(
                []( auto&& c, auto&& lhs, auto&& rhs ) -> Value
                {
                    return BuildComputedValue( lhs.type(),
                        AShr( lhs, rhs ) );
                } ),

                // runtime signed integer right shift, defined to work for any two integers of same
                // bit size.
                ForTypes< CustomPattern< IntegerType, IntegerType::PatternSigned >,
                    CustomPattern< IntegerType, IntegerType::PatternUnsigned > >(
                []( auto&& c, auto&& lhs, auto&& rhs ) -> Value
                {
                    using namespace goose::builtins::exprbuilder;

                    auto cb = c.codeBuilder();
                    auto cfg = cb->cfg();
                    assert( cfg );

                    // Shifting for a number of bits equal or larger than the bitsize
                    // of lhs is an undefined behavior, so we require verification that
                    // it won't happen.
                    // Extract the integer type of lhs to retreieve its bit size.
                    auto lhsType = *FromValue< IntegerType >( *ValueFromIRExpr( lhs.type() ) );
                    auto bitSizeValue = Value( rhs.type(), APSInt::get( lhsType.m_numBits) );

                    auto cond = ULT( rhs, bitSizeValue );

                    DiagnosticsManager::GetInstance().defineCustomDiagnostic(
                        cond.locationId(), "assert"_sid, "the shift amount may be equal or greater than the bitsize." );

                    cfg->currentBB()->append(
                        llr::Assert( move( cond ) )
                    );

                    return BuildComputedValue( lhs.type(),
                        AShr( lhs, rhs ) );
                } ),

                // runtime unsigned integer right shift, defined to work for any two integers of same
                // bit size.
                ForType< CustomPattern< IntegerType, IntegerType::PatternUnsigned > >(
                []( auto&& c, auto&& lhs, auto&& rhs ) -> Value
                {
                    using namespace goose::builtins::exprbuilder;

                    auto cb = c.codeBuilder();
                    auto cfg = cb->cfg();
                    assert( cfg );

                    // Shifting for a number of bits equal or larger than the bitsize
                    // of lhs is an undefined behavior, so we require verification that
                    // it won't happen.
                    // Extract the integer type of lhs to retreieve its bit size.
                    auto lhsType = *FromValue< IntegerType >( *ValueFromIRExpr( lhs.type() ) );
                    auto bitSizeValue = Value( rhs.type(), APSInt::get( lhsType.m_numBits) );

                    auto cond = ULT( rhs, bitSizeValue );

                    DiagnosticsManager::GetInstance().defineCustomDiagnostic(
                        cond.locationId(), "assert"_sid, "the shift amount may be equal or greater than the bitsize." );

                    cfg->currentBB()->append(
                        llr::Assert( move( cond ) )
                    );

                    return BuildComputedValue( lhs.type(),
                        LShr( lhs, rhs ) );
                } )
            )
        );
    }
}
