// Copyright (c) 2012-2015 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.

#include "big_int.h"

#include "bn_helpers.h"
#include <boost/test/unit_test.hpp>

#include "script/int_serialization.h"
#include "script/interpreter.h"
#include "script/script_flags.h"
#include "taskcancellation.h"

#include "config.h"
#include <vector>

using namespace std;

using frame_type = vector<uint8_t>;
using stack_type = vector<frame_type>;

using bsv::bint;

constexpr auto min64{std::numeric_limits<int64_t>::min() + 1};
constexpr auto max64{std::numeric_limits<int64_t>::max()};

BOOST_AUTO_TEST_SUITE(bn_op_tests)

BOOST_AUTO_TEST_CASE(bint_unary_ops)
{
    const Config& config = GlobalConfig::GetConfig();

    using polynomial = vector<int>;
    using test_args = tuple<int64_t, polynomial, opcodetype, polynomial>;
    // clang-format off
    vector<test_args> test_data = {
        {0, {-2}, OP_1ADD, {-1}},
        {0, {-1}, OP_1ADD, {0}},
        {0, {0}, OP_1ADD, {1}},
        {0, {1}, OP_1ADD, {2}},
        {max64, {1, 0}, OP_1ADD, {1, 1}},
        {max64, {1, 1}, OP_1ADD, {1, 2}},

        {0, {-1}, OP_1SUB, {-2}},
        {0, {0}, OP_1SUB, {-1}},
        {0, {1}, OP_1SUB, {0}},
        {0, {2}, OP_1SUB, {1}},
        {min64, {1, 0}, OP_1SUB, {1, -1}},

        {0, {-1}, OP_NEGATE, {1}},
        {0, {0}, OP_NEGATE, {0}},
        {0, {1}, OP_NEGATE, {-1}},
        {max64, {1, 0}, OP_NEGATE, {-1, 0}},
        {max64, {1, 1}, OP_NEGATE, {-1, -1}},
        {min64, {1, 0}, OP_NEGATE, {-1, 0}},
        {min64, {1, -1}, OP_NEGATE, {-1, 1}},

        {0, {-1}, OP_ABS, {1}},
        {0, {0}, OP_ABS, {0}},
        {0, {1}, OP_ABS, {1}},
        {max64, {1, 1}, OP_ABS, {1, 1}},
        {min64, {1, 1}, OP_ABS, {-1, -1}},

        {0, {-1}, OP_NOT, {0}},
        {0, {0}, OP_NOT, {1}},
        {0, {1}, OP_NOT, {0}},
        {max64, {1, 1}, OP_NOT, {0}},
        {min64, {1, 1}, OP_NOT, {0}},

        {0, {-1}, OP_0NOTEQUAL, {1}},
        {0, {0}, OP_0NOTEQUAL, {0}},
        {0, {1}, OP_0NOTEQUAL, {1}},
        {max64, {1, 1}, OP_0NOTEQUAL, {1}},
        {min64, {1, 1}, OP_0NOTEQUAL, {1}},
    };
    // clang-format on

    for(const auto [n, arg_poly, op_code, exp_poly] : test_data)
    {
        const bint bn{n};

        vector<uint8_t> args;
        args.push_back(OP_PUSHDATA1);

        const bint arg = polynomial_value(begin(arg_poly), end(arg_poly), bn);
        const auto arg_serialized{arg.serialize()};
        args.push_back(arg_serialized.size());
        copy(begin(arg_serialized), end(arg_serialized), back_inserter(args));

        args.push_back(op_code);

        CScript script(args.begin(), args.end());

        const auto flags{SCRIPT_UTXO_AFTER_GENESIS};
        ScriptError error;
        auto source = task::CCancellationSource::Make();
        LimitedStack stack(UINT32_MAX);
        const auto status =
            EvalScript(config, false, source->GetToken(), stack, script, flags,
                       BaseSignatureChecker{}, &error);
        BOOST_CHECK_EQUAL(true, status.value());
        BOOST_CHECK_EQUAL(SCRIPT_ERR_OK, error);
        BOOST_CHECK_EQUAL(1, stack.size());
        const auto frame = stack.front();
        const auto actual =
            frame.empty() ? bint{0}
                          : bsv::bint::deserialize(frame.GetElement());
        const bint expected =
            polynomial_value(begin(exp_poly), end(exp_poly), bn);
        BOOST_CHECK_EQUAL(expected, actual);
    }
}

BOOST_AUTO_TEST_CASE(bint_binary_ops)
{
    const Config& config = GlobalConfig::GetConfig();

    using polynomial = vector<int>;
    using test_args =
        tuple<int64_t, polynomial, polynomial, opcodetype, polynomial>;
    vector<test_args> test_data = {
        {max64, {1, 1}, {1, 1}, OP_ADD, {2, 2}},
        {max64, {1, 1, 1}, {1, 0, 0}, OP_ADD, {2, 1, 1}},
        {min64, {1, 0, 0}, {1, 0, 0}, OP_ADD, {2, 0, 0}},
        {max64, {-1, 0, 0}, {1, 0, 0}, OP_ADD, {0}},
        {min64, {1, 0, 0}, {-1, 0, 0}, OP_ADD, {0}},

        {max64, {2, 0, 0}, {1, 0, 0}, OP_SUB, {1, 0, 0}},

        {max64, {1, 0}, {1, 0}, OP_MUL, {1, 0, 0}},

        {max64, {1, 0, 0}, {1, 0}, OP_DIV, {1, 0}},

        {max64, {1, 0, 0}, {1, 0}, OP_MOD, {0}},
        {max64, {1, 1, 1}, {1, 0}, OP_MOD, {1}},
        {max64, {1, 1, 1, 1}, {1, 1, 0}, OP_MOD, {1, 1}},

        {max64, {1, 0, 0}, {1, 0, 0}, OP_BOOLAND, {1}},
        {max64, {1, 0, 0}, {0}, OP_BOOLAND, {0}},
        {max64, {0}, {1, 0, 0}, OP_BOOLAND, {0}},
        {max64, {0}, {0}, OP_BOOLAND, {0}},

        {max64, {1, 0, 0}, {1, 0, 0}, OP_BOOLOR, {1}},
        {max64, {1, 0, 0}, {0}, OP_BOOLOR, {1}},
        {max64, {0}, {1, 0, 0}, OP_BOOLOR, {1}},
        {max64, {0}, {0}, OP_BOOLOR, {0}},

        {max64, {1, 0, 0}, {1, 0, 0}, OP_NUMEQUAL, {1}},
        {max64, {1, 0, 0}, {-1, 0, 0}, OP_NUMEQUAL, {0}},
        {max64, {1, 0, 0}, {2, 0, 0}, OP_NUMEQUAL, {0}},

        {max64, {1, 0, 0}, {1, 0, 0}, OP_NUMNOTEQUAL, {0}},
        {max64, {-1, 0, 0}, {1, 0, 0}, OP_NUMNOTEQUAL, {1}},

        {max64, {-1, 0, 0}, {1, 0, 0}, OP_LESSTHAN, {1}},
        {max64, {1, 0, 0}, {1, 0, 0}, OP_LESSTHAN, {0}},
        {max64, {-1, 0, 0}, {-1, 0, 0}, OP_LESSTHAN, {0}},
        {max64, {1, 0, 0}, {-1, 0, 0}, OP_LESSTHAN, {0}},

        {max64, {-1, 0, 0}, {1, 0, 0}, OP_LESSTHANOREQUAL, {1}},
        {max64, {1, 0, 0}, {1, 0, 0}, OP_LESSTHANOREQUAL, {1}},
        {max64, {-1, 0, 0}, {-1, 0, 0}, OP_LESSTHANOREQUAL, {1}},
        {max64, {1, 0, 0}, {-1, 0, 0}, OP_LESSTHANOREQUAL, {0}},

        {max64, {1, 0, 0}, {-1, 0, 0}, OP_GREATERTHAN, {1}},
        {max64, {1, 0, 0}, {1, 0, 0}, OP_GREATERTHAN, {0}},
        {max64, {-1, 0, 0}, {-1, 0, 0}, OP_GREATERTHAN, {0}},
        {max64, {-1, 0, 0}, {1, 0, 0}, OP_GREATERTHAN, {0}},

        {max64, {-1, 0, 0}, {1, 0, 0}, OP_GREATERTHANOREQUAL, {0}},
        {max64, {1, 0, 0}, {1, 0, 0}, OP_GREATERTHANOREQUAL, {1}},
        {max64, {-1, 0, 0}, {-1, 0, 0}, OP_GREATERTHANOREQUAL, {1}},
        {max64, {1, 0, 0}, {-1, 0, 0}, OP_GREATERTHANOREQUAL, {1}},

        {max64, {-1, 0, 0}, {1, 0, 0}, OP_MIN, {-1, 0, 0}},
        {max64, {1, 0, 0}, {-1, 0, 0}, OP_MIN, {-1, 0, 0}},

        {max64, {-1, 0, 0}, {1, 0, 0}, OP_MAX, {1, 0, 0}},
        {max64, {1, 0, 0}, {-1, 0, 0}, OP_MAX, {1, 0, 0}},
    };

    for(const auto [n, arg_0_poly, arg_1_poly, op_code, exp_poly] : test_data)
    {
        LimitedStack stack(UINT32_MAX);

        const bint bn{n};
        vector<uint8_t> args;

        args.push_back(OP_PUSHDATA1);
        const bint arg1 =
            polynomial_value(begin(arg_0_poly), end(arg_0_poly), bn);
        const auto arg1_serialized{arg1.serialize()};
        args.push_back(arg1_serialized.size());
        copy(begin(arg1_serialized), end(arg1_serialized), back_inserter(args));

        args.push_back(OP_PUSHDATA1);
        const bint arg2 =
            polynomial_value(begin(arg_1_poly), end(arg_1_poly), bn);
        const auto arg2_serialized{arg2.serialize()};
        args.push_back(arg2_serialized.size());
        copy(begin(arg2_serialized), end(arg2_serialized), back_inserter(args));

        args.push_back(op_code);

        CScript script(args.begin(), args.end());

        const auto flags{SCRIPT_UTXO_AFTER_GENESIS};
        ScriptError error;
        auto source = task::CCancellationSource::Make();
        const auto status =
            EvalScript(config, true, source->GetToken(), stack, script, flags,
                       BaseSignatureChecker{}, &error);
        BOOST_CHECK_EQUAL(true, status.value());
        BOOST_CHECK_EQUAL(SCRIPT_ERR_OK, error);
        BOOST_CHECK_EQUAL(1, stack.size());
        const auto frame = stack.front();
        const auto actual =
            frame.empty() ? bint{0}
                          : bsv::bint::deserialize(frame.GetElement());
        bint expected = polynomial_value(begin(exp_poly), end(exp_poly), bn);
        BOOST_CHECK_EQUAL(expected, actual);
    }
}

BOOST_AUTO_TEST_CASE(bint_ternary_ops)
{
    const Config& config = GlobalConfig::GetConfig();

    using polynomial = vector<int>;
    using test_args = tuple<int64_t, polynomial, polynomial, polynomial,
                            opcodetype, polynomial>;
    vector<test_args> test_data = {
        {0, {-1}, {0}, {2}, OP_WITHIN, {0}}, // too low
        {0, {0}, {0}, {2}, OP_WITHIN, {1}},  // lower boundary
        {0, {1}, {0}, {2}, OP_WITHIN, {1}},  // in-between
        {0, {2}, {0}, {2}, OP_WITHIN, {0}},  // upper boundary
        {0, {4}, {0}, {2}, OP_WITHIN, {0}},  // too high

        {max64, {1, -1}, {1, 0}, {1, 2}, OP_WITHIN, {0}}, // too low
        {max64, {1, 0}, {1, 0}, {1, 2}, OP_WITHIN, {1}},  // lower boundary
        {max64, {1, 1}, {1, 0}, {1, 2}, OP_WITHIN, {1}},  // in-between
        {max64, {1, 2}, {1, 0}, {1, 2}, OP_WITHIN, {0}},  // upper boundary
        {max64, {1, 4}, {1, 0}, {1, 2}, OP_WITHIN, {0}},  // too high

        {max64, {2, -1}, {2, 0}, {2, 2}, OP_WITHIN, {0}}, // too low
        {max64, {2, 0}, {2, 0}, {2, 2}, OP_WITHIN, {1}},  // lower boundary
        {max64, {2, 1}, {2, 0}, {2, 2}, OP_WITHIN, {1}},  // in-between
        {max64, {2, 2}, {2, 0}, {2, 2}, OP_WITHIN, {0}},  // upper boundary
        {max64, {2, 4}, {2, 0}, {2, 2}, OP_WITHIN, {0}},  // too high
    };

    for(const auto [n, arg_0_poly, arg_1_poly, arg_2_poly, op_code, exp_poly] :
        test_data)
    {
        LimitedStack stack(UINT32_MAX);

        const bint bn{n};
        vector<uint8_t> args;

        args.push_back(OP_PUSHDATA1);
        const bint arg1 =
            polynomial_value(begin(arg_0_poly), end(arg_0_poly), bn);
        const auto arg1_serialized{arg1.serialize()};
        args.push_back(arg1_serialized.size());
        copy(begin(arg1_serialized), end(arg1_serialized), back_inserter(args));

        args.push_back(OP_PUSHDATA1);
        const bint arg2 =
            polynomial_value(begin(arg_1_poly), end(arg_1_poly), bn);
        const auto arg2_serialized{arg2.serialize()};
        args.push_back(arg2_serialized.size());
        copy(begin(arg2_serialized), end(arg2_serialized), back_inserter(args));

        args.push_back(OP_PUSHDATA1);
        const bint arg3 =
            polynomial_value(begin(arg_2_poly), end(arg_2_poly), bn);
        const auto arg3_serialized{arg3.serialize()};
        args.push_back(arg3_serialized.size());
        copy(begin(arg3_serialized), end(arg3_serialized), back_inserter(args));

        args.push_back(op_code);

        CScript script(args.begin(), args.end());

        const auto flags{SCRIPT_UTXO_AFTER_GENESIS};
        ScriptError error;
        auto source = task::CCancellationSource::Make();
        const auto status =
            EvalScript(config, true, source->GetToken(), stack, script, flags,
                       BaseSignatureChecker{}, &error);
        BOOST_CHECK_EQUAL(true, status.value());
        BOOST_CHECK_EQUAL(SCRIPT_ERR_OK, error);
        BOOST_CHECK_EQUAL(1, stack.size());
        const auto frame = stack.front();
        const auto actual =
            frame.empty() ? bint{0}
                          : bsv::bint::deserialize(frame.GetElement());
        bint expected = polynomial_value(begin(exp_poly), end(exp_poly), bn);
        BOOST_CHECK_EQUAL(expected, actual);
    }
}

BOOST_AUTO_TEST_CASE(bint_bint_numequalverify)
{
    const Config& config = GlobalConfig::GetConfig();

    using polynomial = vector<int>;
    using test_args =
        tuple<int64_t, polynomial, polynomial, opcodetype, polynomial>;
    vector<test_args> test_data = {
        {max64, {1, 1}, {1, 1}, OP_NUMEQUALVERIFY, {0}},
        {max64, {1, 0, 0}, {1, 0, 0}, OP_NUMEQUALVERIFY, {0}},
        {max64, {1, 0, 0}, {-1, 0, 0}, OP_NUMEQUALVERIFY, {0}},
        {max64, {2, 0, 0}, {-1, 0, 0}, OP_NUMEQUALVERIFY, {0}},
    };

    for(const auto [n, arg_0_poly, arg_1_poly, op_code, exp_poly] : test_data)
    {
        LimitedStack stack(UINT32_MAX);

        const bint bn{n};
        vector<uint8_t> args;

        args.push_back(OP_PUSHDATA1);
        const bint arg1 =
            polynomial_value(begin(arg_0_poly), end(arg_0_poly), bn);
        const auto arg1_serialized{arg1.serialize()};
        args.push_back(arg1_serialized.size());
        copy(begin(arg1_serialized), end(arg1_serialized), back_inserter(args));

        args.push_back(OP_PUSHDATA1);
        const bint arg2 =
            polynomial_value(begin(arg_1_poly), end(arg_1_poly), bn);
        const auto arg2_serialized{arg2.serialize()};
        args.push_back(arg2_serialized.size());
        copy(begin(arg2_serialized), end(arg2_serialized), back_inserter(args));

        args.push_back(op_code);

        CScript script(args.begin(), args.end());

        const auto flags{SCRIPT_UTXO_AFTER_GENESIS};
        ScriptError error;
        auto source = task::CCancellationSource::Make();
        const auto status =
            EvalScript(config, true, source->GetToken(), stack, script, flags,
                       BaseSignatureChecker{}, &error);
        if(status.value())
        {
            BOOST_CHECK_EQUAL(SCRIPT_ERR_OK, error);
            BOOST_CHECK(stack.empty());
        }
        else
        {
            BOOST_CHECK_EQUAL(SCRIPT_ERR_NUMEQUALVERIFY, error);
            BOOST_CHECK_EQUAL(1, stack.size());
            auto frame = stack.front();
            auto actual =
                frame.empty()
                    ? bint{0}
                    : bsv::bint::deserialize(frame.GetElement());
            bint expected =
                polynomial_value(begin(exp_poly), end(exp_poly), bn);
            BOOST_CHECK_EQUAL(expected, actual);
        }
    }
}

BOOST_AUTO_TEST_CASE(operands_too_large)
{
    GlobalConfig& config = GlobalConfig::GetConfig();
    using test_args = tuple<int, int, opcodetype, bool, ScriptError>;
    const auto max_arg_len{ MAX_SCRIPT_NUM_LENGTH_AFTER_GENESIS };

    // set policy for script size, stack memory usage and max number length in scripts
    // to default after genesis
    config.SetMaxScriptSizePolicy(0);
    config.SetMaxStackMemoryUsage(0, 0);
    config.SetMaxScriptNumLengthPolicy(max_arg_len);

    // clang-format off
    vector<test_args> test_data = {
    {max_arg_len,   max_arg_len,   OP_ADD, true,  SCRIPT_ERR_OK},
    {max_arg_len + 1, max_arg_len,   OP_ADD, false, SCRIPT_ERR_SCRIPTNUM_OVERFLOW},
    {max_arg_len,   max_arg_len + 1, OP_ADD, false, SCRIPT_ERR_SCRIPTNUM_OVERFLOW},
    {max_arg_len + 1, max_arg_len + 1, OP_ADD, false, SCRIPT_ERR_SCRIPTNUM_OVERFLOW},
    {max_arg_len,   max_arg_len,   OP_SUB, true,  SCRIPT_ERR_OK},
    {max_arg_len + 1, max_arg_len,   OP_SUB, false, SCRIPT_ERR_SCRIPTNUM_OVERFLOW},
    {max_arg_len,   max_arg_len + 1, OP_SUB, false, SCRIPT_ERR_SCRIPTNUM_OVERFLOW},
    {max_arg_len + 1, max_arg_len + 1, OP_SUB, false, SCRIPT_ERR_SCRIPTNUM_OVERFLOW},
    {max_arg_len,   max_arg_len,   OP_MUL, true,  SCRIPT_ERR_OK},
    {max_arg_len + 1, max_arg_len,   OP_MUL, false, SCRIPT_ERR_SCRIPTNUM_OVERFLOW},
    {max_arg_len,   max_arg_len + 1, OP_MUL, false, SCRIPT_ERR_SCRIPTNUM_OVERFLOW},
    {max_arg_len + 1, max_arg_len + 1, OP_MUL, false, SCRIPT_ERR_SCRIPTNUM_OVERFLOW},
    {max_arg_len,   max_arg_len,   OP_DIV, true,  SCRIPT_ERR_OK},
    {max_arg_len + 1, max_arg_len,   OP_DIV, false, SCRIPT_ERR_SCRIPTNUM_OVERFLOW},
    {max_arg_len,   max_arg_len + 1, OP_DIV, false, SCRIPT_ERR_SCRIPTNUM_OVERFLOW},
    {max_arg_len + 1, max_arg_len + 1, OP_DIV, false, SCRIPT_ERR_SCRIPTNUM_OVERFLOW},
    {max_arg_len,   max_arg_len,   OP_MOD, true,  SCRIPT_ERR_OK},
    {max_arg_len + 1, max_arg_len,   OP_MOD, false, SCRIPT_ERR_SCRIPTNUM_OVERFLOW},
    {max_arg_len,   max_arg_len + 1, OP_MOD, false, SCRIPT_ERR_SCRIPTNUM_OVERFLOW},
    {max_arg_len + 1, max_arg_len + 1, OP_MOD, false, SCRIPT_ERR_SCRIPTNUM_OVERFLOW},
    {max_arg_len,   max_arg_len,   OP_BOOLAND, true,  SCRIPT_ERR_OK},
    {max_arg_len + 1, max_arg_len,   OP_BOOLAND, false, SCRIPT_ERR_SCRIPTNUM_OVERFLOW},
    {max_arg_len,   max_arg_len + 1, OP_BOOLAND, false, SCRIPT_ERR_SCRIPTNUM_OVERFLOW},
    {max_arg_len + 1, max_arg_len + 1, OP_BOOLAND, false, SCRIPT_ERR_SCRIPTNUM_OVERFLOW},
    {max_arg_len,   max_arg_len,   OP_BOOLOR, true,  SCRIPT_ERR_OK},
    {max_arg_len + 1, max_arg_len,   OP_BOOLOR, false, SCRIPT_ERR_SCRIPTNUM_OVERFLOW},
    {max_arg_len,   max_arg_len + 1, OP_BOOLOR, false, SCRIPT_ERR_SCRIPTNUM_OVERFLOW},
    {max_arg_len + 1, max_arg_len + 1, OP_BOOLOR, false, SCRIPT_ERR_SCRIPTNUM_OVERFLOW},
    {max_arg_len,   max_arg_len,   OP_NUMEQUAL, true,  SCRIPT_ERR_OK},
    {max_arg_len + 1, max_arg_len,   OP_NUMEQUAL, false, SCRIPT_ERR_SCRIPTNUM_OVERFLOW},
    {max_arg_len,   max_arg_len + 1, OP_NUMEQUAL, false, SCRIPT_ERR_SCRIPTNUM_OVERFLOW},
    {max_arg_len + 1, max_arg_len + 1, OP_NUMEQUAL, false, SCRIPT_ERR_SCRIPTNUM_OVERFLOW},
    {max_arg_len,   max_arg_len,   OP_NUMNOTEQUAL, true,  SCRIPT_ERR_OK},
    {max_arg_len + 1, max_arg_len,   OP_NUMNOTEQUAL, false, SCRIPT_ERR_SCRIPTNUM_OVERFLOW},
    {max_arg_len,   max_arg_len + 1, OP_NUMNOTEQUAL, false, SCRIPT_ERR_SCRIPTNUM_OVERFLOW},
    {max_arg_len + 1, max_arg_len + 1, OP_NUMNOTEQUAL, false, SCRIPT_ERR_SCRIPTNUM_OVERFLOW},
    {max_arg_len,   max_arg_len,   OP_LESSTHAN, true,  SCRIPT_ERR_OK},
    {max_arg_len + 1, max_arg_len,   OP_LESSTHAN, false, SCRIPT_ERR_SCRIPTNUM_OVERFLOW},
    {max_arg_len,   max_arg_len + 1, OP_LESSTHAN, false, SCRIPT_ERR_SCRIPTNUM_OVERFLOW},
    {max_arg_len + 1, max_arg_len + 1, OP_LESSTHAN, false, SCRIPT_ERR_SCRIPTNUM_OVERFLOW},
    {max_arg_len,   max_arg_len,   OP_LESSTHANOREQUAL, true,  SCRIPT_ERR_OK},
    {max_arg_len + 1, max_arg_len,   OP_LESSTHANOREQUAL, false, SCRIPT_ERR_SCRIPTNUM_OVERFLOW},
    {max_arg_len,   max_arg_len + 1, OP_LESSTHANOREQUAL, false, SCRIPT_ERR_SCRIPTNUM_OVERFLOW},
    {max_arg_len + 1, max_arg_len + 1, OP_LESSTHANOREQUAL, false, SCRIPT_ERR_SCRIPTNUM_OVERFLOW},
    {max_arg_len,   max_arg_len,   OP_GREATERTHAN, true,  SCRIPT_ERR_OK},
    {max_arg_len + 1, max_arg_len,   OP_GREATERTHAN, false, SCRIPT_ERR_SCRIPTNUM_OVERFLOW},
    {max_arg_len,   max_arg_len + 1, OP_GREATERTHAN, false, SCRIPT_ERR_SCRIPTNUM_OVERFLOW},
    {max_arg_len + 1, max_arg_len + 1, OP_GREATERTHAN, false, SCRIPT_ERR_SCRIPTNUM_OVERFLOW},
    {max_arg_len,   max_arg_len,   OP_GREATERTHANOREQUAL, true,  SCRIPT_ERR_OK},
    {max_arg_len + 1, max_arg_len,   OP_GREATERTHANOREQUAL, false, SCRIPT_ERR_SCRIPTNUM_OVERFLOW},
    {max_arg_len,   max_arg_len + 1, OP_GREATERTHANOREQUAL, false, SCRIPT_ERR_SCRIPTNUM_OVERFLOW},
    {max_arg_len + 1, max_arg_len + 1, OP_GREATERTHANOREQUAL, false, SCRIPT_ERR_SCRIPTNUM_OVERFLOW},
    {max_arg_len,   max_arg_len,   OP_MIN, true,  SCRIPT_ERR_OK},
    {max_arg_len + 1, max_arg_len,   OP_MIN, false, SCRIPT_ERR_SCRIPTNUM_OVERFLOW},
    {max_arg_len,   max_arg_len + 1, OP_MIN, false, SCRIPT_ERR_SCRIPTNUM_OVERFLOW},
    {max_arg_len + 1, max_arg_len + 1, OP_MIN, false, SCRIPT_ERR_SCRIPTNUM_OVERFLOW},
    {max_arg_len,   max_arg_len,   OP_MAX, true,  SCRIPT_ERR_OK},
    {max_arg_len + 1, max_arg_len,   OP_MAX, false, SCRIPT_ERR_SCRIPTNUM_OVERFLOW},
    {max_arg_len,   max_arg_len + 1, OP_MAX, false, SCRIPT_ERR_SCRIPTNUM_OVERFLOW},
    {max_arg_len + 1, max_arg_len + 1, OP_MAX, false, SCRIPT_ERR_SCRIPTNUM_OVERFLOW},
    };
    // clang-format on

    for (const auto [arg0_size, arg1_size, op_code, exp_status,
        exp_script_error] : test_data)
    {
        LimitedStack stack(UINT32_MAX);

        vector<uint8_t> arg0(arg0_size, 42);
        vector<uint8_t> arg1(arg1_size, 69);

        CScript script = CScript() << arg0 << arg1 << op_code;

        const auto flags{ SCRIPT_UTXO_AFTER_GENESIS };
        ScriptError error;
        auto source = task::CCancellationSource::Make();
        const auto status =
            EvalScript(config, false, source->GetToken(), stack, script, flags,
                BaseSignatureChecker{}, &error);
        BOOST_CHECK_EQUAL(exp_status, status.value());
        BOOST_CHECK_EQUAL(exp_script_error, error);
        BOOST_CHECK_EQUAL(status.value() ? 1 : 2, stack.size());
    }
}

BOOST_AUTO_TEST_CASE(op_bin2num)
{
    const Config& config = GlobalConfig::GetConfig();
    // clang-format off
    vector<tuple<vector<uint8_t>, vector<uint8_t>>> test_data = {
        { {}, {}},
        { {0x1}, {0x1}},               // +1
        { {0x7f}, {0x7f}},             // +127
        { {0x80, 0x0}, {0x80, 0x0}},   // +128
        { {0xff, 0x0}, {0xff, 0x0}},   // 255
        { {0x81}, {0x81}},             // -1
        { {0xff}, {0xff}},             // -127
        { {0x80, 0x80}, {0x80, 0x80}}, // -128
        { {0xff, 0x80}, {0xff, 0x80}}, // -255
        { {0x1, 0x0}, {0x1}},           // should be 0x1 for +1
        { {0x7f, 0x80}, {0xff}},        // should be 0xff for -127
        { {0x1, 0x2, 0x3, 0x4, 0x5}, {0x1, 0x2, 0x3, 0x4, 0x5}} // invalid range?
    };
    // clang-format on
    for(auto& [ip, op] : test_data)
    {
        LimitedStack stack(UINT32_MAX);
        vector<uint8_t> args;

        args.push_back(OP_PUSHDATA1);
        args.push_back(ip.size());
        copy(begin(ip), end(ip), back_inserter(args));

        args.push_back(OP_BIN2NUM);

        CScript script(args.begin(), args.end());

        const auto flags{SCRIPT_UTXO_AFTER_GENESIS};
        ScriptError error;
        const auto status = EvalScript(
            config, false, task::CCancellationSource::Make()->GetToken(), stack,
            script, flags, BaseSignatureChecker{}, &error);

        BOOST_CHECK_EQUAL(true, status.value());
        BOOST_CHECK_EQUAL(SCRIPT_ERR_OK, error);
        BOOST_CHECK_EQUAL(1, stack.size());
        BOOST_CHECK_EQUAL(op.size(), stack.front().size());
        BOOST_CHECK_EQUAL_COLLECTIONS(begin(stack.front()), end(stack.front()), begin(op),
                                      end(op));
    }
}

BOOST_AUTO_TEST_CASE(op_num2bin)
{
    const Config& config = GlobalConfig::GetConfig();
    // clang-format off
    vector<tuple<vector<uint8_t>,
                 vector<uint8_t>,
                 bool,
                 ScriptError,
                 vector<uint8_t>>> test_data = {

        { {}, {}, true, SCRIPT_ERR_OK, {}},
        { {}, {0x0}, true, SCRIPT_ERR_OK, {}},
        { {}, {0x1}, true, SCRIPT_ERR_OK, {0x0}},
        { {}, {0x2}, true, SCRIPT_ERR_OK, {0x0, 0x0}},
        { {0x0}, {0x0}, true, SCRIPT_ERR_OK, {}},
        { {0x0}, {0x1}, true, SCRIPT_ERR_OK, {0x0}},
        { {0x0}, {0x2}, true, SCRIPT_ERR_OK, {0x0, 0x0}},
        { {0x1}, {0x1}, true, SCRIPT_ERR_OK, { 0x1}},
        { {0x1, 0x2}, {0x2}, true, SCRIPT_ERR_OK, { 0x1, 0x2}},
        { {0x1, 0x2, 0x3}, {0x3}, true, SCRIPT_ERR_OK, { 0x1, 0x2, 0x3}},
        { {0x1, 0x2, 0x3, 0x4}, {0x4}, true, SCRIPT_ERR_OK, { 0x1, 0x2, 0x3, 0x4}},
        { {0x1, 0x2, 0x3, 0x4, 0x5}, {0x5}, true, SCRIPT_ERR_OK, { 0x1, 0x2, 0x3, 0x4, 0x5}},

        // 0x0 used as padding
        { {0x1}, {0x2}, true, SCRIPT_ERR_OK, {0x1, 0x0}},
        { {0x2}, {0x2}, true, SCRIPT_ERR_OK, {0x2, 0x0}},

        // -ve numbers
        { {0x81}, {0x1}, true, SCRIPT_ERR_OK, {0x81}},
        { {0x81}, {0x2}, true, SCRIPT_ERR_OK, {0x1, 0x80}},
        { {0x81}, {0x3}, true, SCRIPT_ERR_OK, {0x1, 0x0, 0x80}},

        // -ve length
        { {0x1}, {0x81}, false, SCRIPT_ERR_PUSH_SIZE, {0x1}},

        // requested length to short
        { {0x1}, {}, false, SCRIPT_ERR_IMPOSSIBLE_ENCODING, {0x1}},
        { {0x1}, {0x0}, false, SCRIPT_ERR_IMPOSSIBLE_ENCODING, {0x1}},
        { {0x1, 0x2}, {0x1}, false, SCRIPT_ERR_IMPOSSIBLE_ENCODING, { 0x1, 0x2}},

    };
    // clang-format on
    for(auto& [arg1, arg2, exp_status, exp_error, op] : test_data)
    {
        LimitedStack stack(UINT32_MAX);
        vector<uint8_t> args;

        args.push_back(OP_PUSHDATA1);
        args.push_back(arg1.size());
        copy(begin(arg1), end(arg1), back_inserter(args));

        args.push_back(OP_PUSHDATA1);
        args.push_back(arg2.size());
        copy(begin(arg2), end(arg2), back_inserter(args));

        args.push_back(OP_NUM2BIN);

        CScript script(args.begin(), args.end());

        const auto flags{SCRIPT_UTXO_AFTER_GENESIS};
        ScriptError error;
        const auto status = EvalScript(
            config, false, task::CCancellationSource::Make()->GetToken(), stack,
            script, flags, BaseSignatureChecker{}, &error);

        BOOST_CHECK_EQUAL(exp_status, status.value());
        BOOST_CHECK_EQUAL(exp_error, error);
        BOOST_CHECK_EQUAL_COLLECTIONS(begin(stack.front()), end(stack.front()), begin(op),
                                      end(op));
    }
}

BOOST_AUTO_TEST_CASE(op_depth)
{
    const Config& config = GlobalConfig::GetConfig();

    const vector<size_t> test_data = {0, 1, 20'000};
    for(const auto i : test_data)
    {
        LimitedStack stack(UINT32_MAX);
        vector<uint8_t> args(i, OP_0);

        args.push_back(OP_DEPTH);

        CScript script(args.begin(), args.end());

        const auto cancellation_source{task::CCancellationSource::Make()};
        const auto token{cancellation_source->GetToken()};
        const auto flags{SCRIPT_UTXO_AFTER_GENESIS | SCRIPT_GENESIS};
        ScriptError error;
        const auto status = EvalScript(config, true, token, stack, script,
                                       flags, BaseSignatureChecker{}, &error);

        BOOST_CHECK_EQUAL(true, status.value());
        BOOST_CHECK_EQUAL(SCRIPT_ERR_OK, error);
        BOOST_CHECK_EQUAL(i + 1, stack.size());
        vector<uint8_t> op;
        bsv::serialize<int>(i, back_inserter(op));
        BOOST_CHECK_EQUAL_COLLECTIONS(begin(stack.at(i)), end(stack.at(i)), begin(op),
                                      end(op));
    }
}

BOOST_AUTO_TEST_CASE(op_size)
{
    const Config& config = GlobalConfig::GetConfig();

    using polynomial = vector<int>;
    using test_args = tuple<int64_t, polynomial>;
    vector<test_args> test_data = {
        {2, {1, 1}},
        {max64, {1, 1} },
    };

    for(const auto [n, arg_poly] : test_data)
    {
        const bint bn{n};

        vector<uint8_t> args;
        args.push_back(OP_PUSHDATA1);

        const bint arg = polynomial_value(begin(arg_poly), end(arg_poly), bn);
        const auto arg_serialized{arg.serialize()};
        args.push_back(arg_serialized.size());
        copy(begin(arg_serialized), end(arg_serialized), back_inserter(args));

        args.push_back(OP_SIZE);

        CScript script(args.begin(), args.end());

        const auto cancellation_source{task::CCancellationSource::Make()};
        const auto token{cancellation_source->GetToken()};
        const auto flags{SCRIPT_UTXO_AFTER_GENESIS};
        ScriptError error;
        LimitedStack stack(UINT32_MAX);
        const auto status = EvalScript(config, false, token, stack, script,
                                       flags, BaseSignatureChecker{}, &error);

        BOOST_CHECK_EQUAL(true, status.value());
        BOOST_CHECK_EQUAL(SCRIPT_ERR_OK, error);
        BOOST_CHECK_EQUAL(2, stack.size());
        const auto expected{stack.front().size()};
        const auto actual{bsv::deserialize(begin(stack.at(1)), end(stack.at(1)))};
        BOOST_CHECK_EQUAL(expected, actual);
    }
}

BOOST_AUTO_TEST_CASE(op_pick)
{
    const Config& config = GlobalConfig::GetConfig();

    using test_args = tuple<opcodetype, size_t>;
    vector<test_args> test_data = {
        {OP_0, 2},
        {OP_1, 1},
        {OP_2, 0},
    };

    for(const auto [op_code, i] : test_data)
    {
        vector<uint8_t> args;
        args.push_back(OP_0);
        args.push_back(OP_1);
        args.push_back(OP_2);
        args.push_back(op_code);
        args.push_back(OP_PICK);

        CScript script(args.begin(), args.end());

        const auto cancellation_source{task::CCancellationSource::Make()};
        const auto token{cancellation_source->GetToken()};
        const auto flags{SCRIPT_UTXO_AFTER_GENESIS};
        ScriptError error;
        LimitedStack stack(UINT32_MAX);
        const auto status = EvalScript(config, false, token, stack, script,
                                       flags, BaseSignatureChecker{}, &error);

        BOOST_CHECK_EQUAL(true, status.value());
        BOOST_CHECK_EQUAL(SCRIPT_ERR_OK, error);
        BOOST_CHECK_EQUAL(4, stack.size());
        if(op_code == OP_2)
            BOOST_CHECK(stack.at(3).empty());
        else
            BOOST_CHECK_EQUAL(stack.at(i).front(), stack.at(3).front());
    }
}

BOOST_AUTO_TEST_CASE(op_roll)
{
    const Config& config = GlobalConfig::GetConfig();

    vector<opcodetype> test_data{
        OP_0,
        OP_1,
        OP_2,
    };

    for(const auto op_code : test_data)
    {
        vector<uint8_t> args;
        args.push_back(OP_0);
        args.push_back(OP_1);
        args.push_back(OP_2);
        args.push_back(op_code);
        args.push_back(OP_ROLL);

        CScript script(args.begin(), args.end());

        const auto cancellation_source{task::CCancellationSource::Make()};
        const auto token{cancellation_source->GetToken()};
        const auto flags{SCRIPT_UTXO_AFTER_GENESIS};
        ScriptError error;
        LimitedStack stack(UINT32_MAX);
        const auto status = EvalScript(config, false, token, stack, script,
                                       flags, BaseSignatureChecker{}, &error);

        BOOST_CHECK_EQUAL(true, status.value());
        BOOST_CHECK_EQUAL(SCRIPT_ERR_OK, error);
        BOOST_CHECK_EQUAL(3, stack.size());

        if(op_code == OP_0)
        {
            BOOST_CHECK_EQUAL(2, stack.at(2).front());
            BOOST_CHECK_EQUAL(1, stack.at(1).front());
            BOOST_CHECK(stack.at(0).empty());
        }
        else if(op_code == OP_1)
        {
            BOOST_CHECK_EQUAL(1, stack.at(2).front());
            BOOST_CHECK_EQUAL(2, stack.at(1).front());
            BOOST_CHECK(stack.at(0).empty());
        }
        else if(op_code == OP_2)
        {
            BOOST_CHECK(stack.at(2).empty());
            BOOST_CHECK_EQUAL(2, stack.at(1).front());
            BOOST_CHECK_EQUAL(1, stack.at(0).front());
        }
        else
            BOOST_CHECK(false);
    }
}

BOOST_AUTO_TEST_CASE(op_split)
{
    const Config& config = GlobalConfig::GetConfig();

    using data = vector<uint8_t>;
    using test_args = tuple<size_t, opcodetype, data, data>;
    // clang-format off
    vector<test_args> test_data =
    {
        {0, OP_0, {}, {0, 1}},
        {1, {OP_1}, {0}, {1}},
        {2, {OP_2}, {0, 1}, {}},
    };
    // clang-format on

    for (const auto [i, pos, lhs, rhs] : test_data) {
        vector<uint8_t> args;
        args.push_back(0x2);
        args.push_back(0x0);
        args.push_back(0x1);
        args.push_back(pos);

        args.push_back(OP_SPLIT);

        CScript script(args.begin(), args.end());

        const auto cancellation_source{task::CCancellationSource::Make()};
        const auto token{cancellation_source->GetToken()};
        const auto flags{SCRIPT_UTXO_AFTER_GENESIS};
        ScriptError error;
        LimitedStack stack(UINT32_MAX);
        const auto status = EvalScript(config, false, token, stack, script,
                                       flags, BaseSignatureChecker{}, &error);

        BOOST_CHECK_EQUAL(true, status.value());
        BOOST_CHECK_EQUAL(SCRIPT_ERR_OK, error);
        BOOST_CHECK_EQUAL(2, stack.size());
        BOOST_CHECK_EQUAL(2 - i, stack.at(1).size());
        BOOST_CHECK_EQUAL_COLLECTIONS(begin(stack.at(1)), end(stack.at(1)),
                                      begin(rhs), end(rhs));
        BOOST_CHECK_EQUAL(i, stack.front().size());
        BOOST_CHECK_EQUAL_COLLECTIONS(begin(stack.front()), end(stack.front()),
                                      begin(lhs), end(lhs));
    }
}

BOOST_AUTO_TEST_CASE(op_lshift)
{
    const Config& config = GlobalConfig::GetConfig();

    using test_args = tuple<opcodetype, vector<uint8_t>>;
    // clang-format off
    vector<test_args> test_data =
    {
        {OP_0, {0x0, 0x1}},
        {OP_1, {0x0, 0x2}},
        {OP_2, {0x0, 0x4}},
        {OP_8, {0x1, 0x0}},
        {OP_16, {0x0, 0x0}},
    };
    // clang-format on

    for(const auto [pos, expected] : test_data)
    {
        vector<uint8_t> args;
        args.push_back(0x2);
        args.push_back(0x0);
        args.push_back(0x1);
        args.push_back(pos);

        args.push_back(OP_LSHIFT);

        CScript script(args.begin(), args.end());

        const auto cancellation_source{task::CCancellationSource::Make()};
        const auto token{cancellation_source->GetToken()};
        const auto flags{SCRIPT_UTXO_AFTER_GENESIS};
        ScriptError error;
        LimitedStack stack(UINT32_MAX);
        const auto status = EvalScript(config, false, token, stack, script,
                                       flags, BaseSignatureChecker{}, &error);

        BOOST_CHECK_EQUAL(true, status.value());
        BOOST_CHECK_EQUAL(SCRIPT_ERR_OK, error);
        BOOST_CHECK_EQUAL(1, stack.size());
        BOOST_CHECK_EQUAL(2, stack.front().size());
        BOOST_CHECK_EQUAL_COLLECTIONS(begin(stack.front()), end(stack.front()),
                                      begin(expected), end(expected));
    }
}

BOOST_AUTO_TEST_CASE(op_rshift)
{
    const Config& config = GlobalConfig::GetConfig();

    using test_args = tuple<opcodetype, vector<uint8_t>>;
    // clang-format off
    vector<test_args> test_data =
    {
        {OP_0, {0x80, 0x0}},
        {OP_1, {0x40, 0x0}},
        {OP_2, {0x20, 0x0}},
        {OP_8, {0x0, 0x80}},
        {OP_16, {0x0, 0x0}},
    };
    // clang-format on

    for(const auto [pos, expected] : test_data)
    {
        vector<uint8_t> args;
        args.push_back(0x2);
        args.push_back(0x80);
        args.push_back(0x0);
        args.push_back(pos);

        args.push_back(OP_RSHIFT);

        CScript script(args.begin(), args.end());

        const auto cancellation_source{task::CCancellationSource::Make()};
        const auto token{cancellation_source->GetToken()};
        const auto flags{SCRIPT_UTXO_AFTER_GENESIS};
        ScriptError error;
        LimitedStack stack(UINT32_MAX);
        const auto status = EvalScript(config, false, token, stack, script,
                                       flags, BaseSignatureChecker{}, &error);

        BOOST_CHECK_EQUAL(true, status.value());
        BOOST_CHECK_EQUAL(SCRIPT_ERR_OK, error);
        BOOST_CHECK_EQUAL(1, stack.size());
        BOOST_CHECK_EQUAL(2, stack.front().size());
        BOOST_CHECK_EQUAL_COLLECTIONS(begin(stack.front()), end(stack.front()),
                                      begin(expected), end(expected));
    }
}

namespace
{
    const vector<uint8_t> failure = {};
    const vector<uint8_t> success = {1};

    struct equality_checker : BaseSignatureChecker
    {
        bool CheckSig(const std::vector<uint8_t>& scriptsig,
                      const std::vector<uint8_t>& pubkey,
                      const CScript&,
                      bool enabledSighashForkid) const override
        {
            return scriptsig == pubkey;
        }

        bool CheckLockTime(const CScriptNum&) const override { return true; }
        bool CheckSequence(const CScriptNum&) const override { return true; }
    };
}

BOOST_AUTO_TEST_CASE(op_checksig)
{
    const Config& config = GlobalConfig::GetConfig();

    using test_args =
        tuple<opcodetype, opcodetype, bool, ScriptError, vector<uint8_t>>;
    // clang-format off
    vector<test_args> test_data =
    {
        // signature, pub_key, exp_status, exp_error,
        {OP_1, OP_1, true, SCRIPT_ERR_OK, success },
        {OP_1, OP_2, true, SCRIPT_ERR_OK, failure }
    };
    // clang-format on

    for(const auto [signature, pub_key, exp_status, exp_error, exp_stack_top] :
        test_data)
    {
        vector<uint8_t> args;

        args.push_back(signature);
        args.push_back(pub_key);
        args.push_back(OP_CHECKSIG);

        const CScript script(args.begin(), args.end());

        uint32_t flags{};
        ScriptError error;
        LimitedStack stack(UINT32_MAX);
        const equality_checker checker;
        const auto status = EvalScript(
            config, false, task::CCancellationSource::Make()->GetToken(), stack,
            script, flags, checker, &error);

        BOOST_CHECK_EQUAL(exp_status, status.value());
        BOOST_CHECK_EQUAL(exp_error, error);
        BOOST_CHECK_EQUAL(1, stack.size());
        const auto stack_0{stack.at(0)};
        BOOST_CHECK_EQUAL_COLLECTIONS(begin(stack_0), end(stack_0),
                                      begin(exp_stack_top), end(exp_stack_top));
    }
}

BOOST_AUTO_TEST_CASE(op_checkmultisig)
{
    const Config& config = GlobalConfig::GetConfig();

    using test_args = tuple<int, vector<opcodetype>, int, vector<opcodetype>,
                            bool, ScriptError, vector<uint8_t>>;
    // clang-format off
    vector<test_args> test_data =
    {
        // n_signatures, signatures,
        // n_public_keys, public_keys,
        // exp_status, exp_error, top_stack_value

        // Success True
        {1, {OP_1}, 1, {OP_1}, true, SCRIPT_ERR_OK, success },
        {1, {OP_1}, 2, {OP_1, OP_16}, true, SCRIPT_ERR_OK, success },
        {1, {OP_1}, 2, {OP_16, OP_1}, true, SCRIPT_ERR_OK, success },

        {2, {OP_1, OP_2}, 2, {OP_1, OP_2}, true, SCRIPT_ERR_OK, success },

        {2, {OP_1, OP_2}, 3, {OP_16, OP_1, OP_2}, true, SCRIPT_ERR_OK, success},
        {2, {OP_1, OP_2}, 3, {OP_1, OP_16, OP_2}, true, SCRIPT_ERR_OK, success},
        {2, {OP_1, OP_2}, 3, {OP_1, OP_2, OP_16}, true, SCRIPT_ERR_OK, success},

        {2, {OP_1, OP_2}, 4, {OP_16, OP_1, OP_16, OP_2}, true, SCRIPT_ERR_OK, success},

        // Success false
        {1, {OP_1}, 1, {OP_16}, true, SCRIPT_ERR_OK, failure},

        {2, {OP_1, OP_2}, 2, {OP_1, OP_16}, true, SCRIPT_ERR_OK, failure},
        {2, {OP_1, OP_2}, 2, {OP_16, OP_2}, true, SCRIPT_ERR_OK, failure},
        {2, {OP_1, OP_2}, 2, {OP_2, OP_1}, true, SCRIPT_ERR_OK, failure},

        // Fails
        {2, {OP_1, OP_2}, 1, {OP_1}, false, SCRIPT_ERR_SIG_COUNT, failure},
        {-1, {OP_1}, 1, {OP_1}, false, SCRIPT_ERR_SIG_COUNT, failure},
        {1, {OP_1}, -1, {OP_1}, false, SCRIPT_ERR_PUBKEY_COUNT, failure},
    };
    // clang-format on

    for(const auto [n_sigs, signatures, n_pub_keys, public_keys, exp_status,
                    exp_error, exp_stack_top] : test_data)
    {
        vector<uint8_t> args{OP_0}; // historic bug start with OP_0

        reverse_copy(begin(signatures), end(signatures), back_inserter(args));
        args.push_back(1);
        args.push_back(n_sigs);

        reverse_copy(begin(public_keys), end(public_keys), back_inserter(args));
        args.push_back(1);
        args.push_back(n_pub_keys);

        args.push_back(OP_CHECKMULTISIG);

        const CScript script(args.begin(), args.end());

        uint32_t flags{};
        ScriptError error;
        LimitedStack stack(UINT32_MAX);
        const equality_checker checker;
        const auto status = EvalScript(
            config, false, task::CCancellationSource::Make()->GetToken(), stack,
            script, flags, checker, &error);

        BOOST_CHECK_EQUAL(exp_status, status.value());
        BOOST_CHECK_EQUAL(exp_error, error);
        BOOST_CHECK_EQUAL(
            exp_status ? 1 : signatures.size() + public_keys.size() + 3,
            stack.size());
        const auto stack_0{stack.at(0)};
        BOOST_CHECK_EQUAL_COLLECTIONS(begin(stack_0), end(stack_0),
                                      begin(exp_stack_top), end(exp_stack_top));
    }
}

BOOST_AUTO_TEST_CASE(op_rshift_far)
{
    constexpr vector<uint8_t>::size_type size{INT32_MAX / 8};
    std::vector<uint8_t> data(size + 1l, 0x0);
    data[0] = 0x80;

    auto source = task::CCancellationSource::Make();
    LimitedStack stack = LimitedStack({data}, INT64_MAX);
    const auto flags{SCRIPT_UTXO_AFTER_GENESIS};
    ScriptError err;
    const auto r =
        EvalScript(GlobalConfig::GetConfig(), true, source->GetToken(), stack,
                   CScript() << (size * 8) + 7 << OP_RSHIFT, flags,
                   BaseSignatureChecker{}, &err);
    BOOST_CHECK(r.value());
    BOOST_CHECK_EQUAL(SCRIPT_ERR_OK, err);
    const auto top = stack.front();
    const auto values = top.GetElement();
    const auto it{find_if(begin(values), end(values),
                          [](const auto n) { return n != 0; })};
    BOOST_CHECK_EQUAL(distance(begin(values), it), values.size() - 1);
    BOOST_CHECK_EQUAL(1, values[values.size() - 1]);
}

BOOST_AUTO_TEST_CASE(op_lshift_far)
{
    constexpr vector<uint8_t>::size_type size{INT32_MAX / 8};
    std::vector<uint8_t> data(size + 1l, 0x0);
    data[size] = 0x1;

    auto source = task::CCancellationSource::Make();
    LimitedStack stack = LimitedStack({data}, INT64_MAX);
    const auto flags{SCRIPT_UTXO_AFTER_GENESIS};
    ScriptError err;
    const auto r =
        EvalScript(GlobalConfig::GetConfig(), true, source->GetToken(), stack,
                   CScript() << (size * 8) + 7 << OP_LSHIFT, flags,
                   BaseSignatureChecker{}, &err);
    BOOST_CHECK(r.value());
    BOOST_CHECK_EQUAL(SCRIPT_ERR_OK, err);
    const auto top = stack.front();
    const auto values = top.GetElement();
    const auto it{find_if(begin(values), end(values),
                          [](const auto n) { return n != 0; })};
    BOOST_CHECK_EQUAL(distance(begin(values), it), 0);
    BOOST_CHECK_EQUAL(0x80, values[0]);
}

BOOST_AUTO_TEST_SUITE_END()


/* -*- mode: c++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */

/*

 Copyright (C) 2016 Klaus Spanderen

 This file is part of QuantLib, a free-software/open-source library
 for financial quantitative analysts and developers - http://quantlib.org/

 QuantLib is free software: you can redistribute it and/or modify it
 under the terms of the QuantLib license.  You should have received a
 copy of the license along with this program; if not, please email
 <quantlib-dev@lists.sf.net>. The license is also available online at
 <http://quantlib.org/license.shtml>.

 This program is distributed in the hope that it will be useful, but WITHOUT
 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 FOR A PARTICULAR PURPOSE.  See the license for more details.
*/

/*! \file stochasticcollocationinvcdf.hpp
    Stochastic collocation inverse cumulative distribution function
*/

#ifndef quantlib_stochastic_collation_inv_cdf_hpp
#define quantlib_stochastic_collation_inv_cdf_hpp

#include <ql/math/distributions/normaldistribution.hpp>
#include <ql/math/interpolations/lagrangeinterpolation.hpp>

#include <boost/function.hpp>
#include <functional>

namespace QuantLib {
    //! Stochastic collocation inverse cumulative distribution function

    /*! References:
        L.A. Grzelak, J.A.S. Witteveen, M.Suárez-Taboada, C.W. Oosterlee,
        The Stochastic Collocation Monte Carlo Sampler: Highly efficient
        sampling from “expensive” distributions
        http://papers.ssrn.com/sol3/papers.cfm?abstract_id=2529691
     */

    class StochasticCollocationInvCDF {
      public:
        typedef Real argument_type;
        typedef Real result_type;

        StochasticCollocationInvCDF(
            const boost::function<Real(Real)>& invCDF,
            Size lagrangeOrder,
            Real pMax = Null<Real>(),
            Real pMin = Null<Real>());

        Real value(Real x) const;
        Real operator()(Real u) const;

      private:
        const Array x_;
        const Volatility sigma_;
        const Array y_;
        const LagrangeInterpolation interpl_;
    };
}

#endif


#pragma once

#include "RRCoord.hpp"
#include "utils/basic_maths.hpp"
#include "utils/SPLDefs.hpp"
#include "types/boostSerializationEigenTypes.hpp"
#include <Eigen/Eigen>

struct AbsCoord {
   AbsCoord(float x, float y, float theta) : vec(x, y, theta) {
      var.setZero();
      var(0, 0) = SQUARE(FULL_FIELD_LENGTH);
      var(1, 1) = SQUARE(FULL_FIELD_WIDTH);
      var(2, 2) = SQUARE(M_PI);
      weight = 1.0;
   }

   AbsCoord() : vec(0, 0, 0) {
      var.setZero();
      var(0, 0) = SQUARE(FULL_FIELD_LENGTH);
      var(1, 1) = SQUARE(FULL_FIELD_WIDTH);
      var(2, 2) = SQUARE(M_PI);
      weight = 1.0;
   }

   Eigen::Vector3f vec;
   Eigen::Matrix<float, 3, 3> var;
   float weight;

   const float x() const {
      return vec[0];
   }

   float &x() {
      return vec[0];
   }

   const float y() const {
      return vec[1];
   }

   float &y() {
      return vec[1];
   }

   const float theta() const {
      return vec[2];
   }

   float &theta() {
      return vec[2];
   }

   float getVar(int m, int n) const {
      return var(m,n);
   }

   bool operator== (const AbsCoord &other) const {
      return vec == other.vec;
   }

   template<class Archive>
   void serialize(Archive &ar, const unsigned int file_version) {
      ar & vec;
      ar & var;
   }

   /**
    * This assumes that the AbsCoord is already in robot relative coordinates, this will simply
    * convert it to polar coords.
    */
   RRCoord convertToRobotRelative(void) const {
      return RRCoord(sqrtf(vec.x()*vec.x() + vec.y()*vec.y()), atan2f(vec.y(), vec.x()), 0.0f);
   }

   RRCoord convertToRobotRelative(const AbsCoord &robotPose) const {
      float xdiff = x() - robotPose.x();
      float ydiff = y() - robotPose.y();
      float distance = sqrtf(xdiff*xdiff + ydiff*ydiff);

      float angle = atan2(ydiff, xdiff);
      float rrHeading = normaliseTheta(angle - robotPose.theta());

      return RRCoord(distance, rrHeading, 0.0f);
   }

   AbsCoord convertToRobotRelativeCartesian(const AbsCoord &robotPose) const {
      RRCoord rrCoord = convertToRobotRelative(robotPose);
      return AbsCoord(
            rrCoord.distance() * cos(rrCoord.heading()),
            rrCoord.distance() * sin(rrCoord.heading()),
            0.0f);
   }
};


/**
 * @file
 * @brief NPDE homework ResidualErrorEstimator Tests
 * @author Ralf Hiptmair
 * @date July 2021
 * @copyright Developed at SAM, ETH Zurich
 */

#include "../residualerrorestimator.h"

#include <gtest/gtest.h>

#include <Eigen/Core>
#include <iostream>

namespace REE::test {

TEST(REE, TwoTriangleMesh) {
  /* Macros available in the Google test framework:
     EXPECT_EQ(x,y), EXPECT_NE(x,y), EXPECT_LT(x,y), EXPECT_LE(x,y),
     EXPECT_GT(x,y), EXPECT_GE(x,y) EXPECT_STREQ(x,y), EXPECT_STRNE(x,y) -> for
     C-strings only ! EXPECT_NEAR(x,y,abs_tol) All testing macros can output a
     message by a trailing << ....
   */
  using size_type = lf::base::size_type;
  using glb_idx_t = lf::assemble::glb_idx_t;
  using coord_t = Eigen::Vector2d;

  // Build a mesh with two triangles
  // Create helper object: mesh factory
  std::shared_ptr<lf::mesh::hybrid2d::MeshFactory> mesh_factory_ptr =
      std::make_shared<lf::mesh::hybrid2d::MeshFactory>(2);
  // Generate nodes of the mesh
  // clang-format off
  std::array<std::array<double, 2>, 4> node_coord{
  std::array<double, 2>({0 , 0 }),
  std::array<double, 2>({0.5 , 0 }),
  std::array<double, 2>({0.5 , 0.5 }),
  std::array<double, 2>({0 , 0.5 })};
  // clang-format on
  // Add nodes to the mesh via the MeshFactory object
  for (const auto& node : node_coord) {
    mesh_factory_ptr->AddPoint(coord_t({node[0], node[1]}));
  }
  // Add plain triangles to the mesh, defined by their vertex nodes.
  // Since no particular geometry is specified, the triangles are assumed to
  // have straght edges.
  mesh_factory_ptr->AddEntity(lf::base::RefEl::kTria(),
                              std::vector<size_type>({0, 1, 2}),
                              std::unique_ptr<lf::geometry::Geometry>(nullptr));
  mesh_factory_ptr->AddEntity(lf::base::RefEl::kTria(),
                              std::vector<size_type>({0, 2, 3}),
                              std::unique_ptr<lf::geometry::Geometry>(nullptr));
  // Get a pointer to the coarsest mesh
  std::shared_ptr<lf::mesh::Mesh> mesh_p = mesh_factory_ptr->Build();

  // Print information about the coarsest mesh
  std::cout << "\t Coarsest mesh for demonstration run\n";
  lf::mesh::utils::PrintInfo(std::cout, *mesh_p, 100);

  std::function<double(Eigen::Vector2d)> alpha =
      [](Eigen::Vector2d x) -> double {
    return ((x[0] - x[1]) > 0) ? 1.0 / 3.0 : 1.0 / 7.0;
  };

  auto f = [](Eigen::Vector2d x) -> double { return (1.0); };

  // Defining the discretized boundary value problem including the
  // finite-element space
  const dataDiscreteBVP disc_bvp(mesh_p, alpha, f);

  // Fix a finite element function for testing
  Eigen::VectorXd mu(4);
  mu(0) = 1;
  mu(1) = 0;
  mu(2) = 1;
  mu(3) = 0;

  // Compute edge contributions
  lf::mesh::utils::CodimMeshDataSet<double> ed_res{edgeResiduals(disc_bvp, mu)};

  // Values of the jump residual for the five edges of the mesh
  std::array<double, 5> vals({0.0, 2.721088435374150, 0.0, 0.0, 0.0});
  for (const lf::mesh::Entity* edge : mesh_p->Entities(1)) {
    EXPECT_NEAR(ed_res(*edge), vals[mesh_p->Index(*edge)], 1.0E-6);
  }
}

TEST(REE, ZeroEdgeResioduals) {
  // Obtain test mesh
  std::shared_ptr<lf::mesh::Mesh> mesh_p =
      lf::mesh::test_utils::GenerateHybrid2DTestMesh(3);
  // Defining the discretized boundary value problem including the
  // finite-element space
  std::function<double(Eigen::Vector2d)> alpha =
      [](Eigen::Vector2d x) -> double { return 1.0; };
  auto f = [](Eigen::Vector2d) -> double { return 1.0; };
  const dataDiscreteBVP disc_bvp(mesh_p, alpha, f);
  // A simple linear function
  auto lin_fun = [](Eigen::Vector2d x) -> double {
    return 2.0 * x[0] + x[1] + 1.0;
  };
  // Sample nodal values
  lf::mesh::utils::MeshFunctionGlobal mf_lin(lin_fun);
  Eigen::VectorXd mu =
      lf::fe::NodalProjection(*disc_bvp.pwlinfespace_p_, mf_lin);
  // Compute edge contributions
  lf::mesh::utils::CodimMeshDataSet<double> ed_res{edgeResiduals(disc_bvp, mu)};
  // All edge residual should vanish
  for (const lf::mesh::Entity* edge : mesh_p->Entities(1)) {
    EXPECT_NEAR(ed_res(*edge), 0.0, 1.0E-6);
  }
}

}  // namespace REE::test


#ifndef FUN_RAT_HPP
#define FUN_RAT_HPP

/**

- Modify from boost::rational<>
- Features:
  - No exception, support NaN, $\pm\Infty$.
  - Avoid normalization in every operation.
    Avoid gcd() in every operation to speed up the calculation.
    The rationale behind this is that: the comparsion (operator==)
    and printout are not often used.
  - Check 2/4 == 1/2

- Rule:
  1. check if den1 == den2 first

**/

#include <boost/config.hpp> // for BOOST_NO_STDC_NAMESPACE, BOOST_MSVC, etc
#ifndef BOOST_NO_IOSTREAM
#include <iomanip> // for std::setw
#include <ios>     // for std::noskipws, streamsize
#include <istream> // for std::istream
#include <ostream> // for std::ostream
#include <sstream> // for std::ostringstream
#endif
#include <cstddef> // for NULL
// xxx #include <stdexcept>             // for std::domain_error
#include <boost/assert.hpp>            // for BOOST_ASSERT
#include <boost/call_traits.hpp>       // for boost::call_traits
#include <boost/detail/workaround.hpp> // for BOOST_WORKAROUND
#include <boost/operators.hpp>         // for boost::addable etc
#include <cstdlib>                     // for std::abs
#include <string>                      // for std::string implicit constructor

//#include <boost/integer/common_factor_rt.hpp> // for boost::integer::gcd, lcm
#include <boost/static_assert.hpp> // for BOOST_STATIC_ASSERT
#include <limits>                  // for std::numeric_limits
#include <type_traits>             // is_integral<T>

// Control whether depreciated GCD and LCM functions are included (default: yes)
#ifndef BOOST_CONTROL_RATIONAL_HAS_GCD
#define BOOST_CONTROL_RATIONAL_HAS_GCD 1
#endif

namespace boost {

template <typename _Z,
          class = typename std::enable_if<std::is_integral<_Z>::value>::type>
inline constexpr _Z gcd(const _Z &a, const _Z &b) noexcept {
  return b == _Z(0) ? abs(a) : gcd(b, a % b);
}

template <typename IntType>
class rat
    : less_than_comparable<
          rat<IntType>,
          equality_comparable<
              rat<IntType>,
              less_than_comparable2<
                  rat<IntType>, IntType,
                  equality_comparable2<
                      rat<IntType>, IntType,
                      addable<
                          rat<IntType>,
                          subtractable<
                              rat<IntType>,
                              multipliable<
                                  rat<IntType>,
                                  dividable<
                                      rat<IntType>,
                                      addable2<
                                          rat<IntType>, IntType,
                                          subtractable2<
                                              rat<IntType>, IntType,
                                              subtractable2_left<
                                                  rat<IntType>, IntType,
                                                  multipliable2<
                                                      rat<IntType>, IntType,
                                                      dividable2<
                                                          rat<IntType>, IntType,
                                                          dividable2_left<
                                                              rat<IntType>,
                                                              IntType,
                                                              incrementable<
                                                                  rat<IntType>,
                                                                  decrementable<rat<
                                                                      IntType>>>>>>>>>>>>>>>>> {
  // Class-wide pre-conditions
  static_assert(::std::numeric_limits<IntType>::is_specialized);

  // Helper types
  typedef typename boost::call_traits<IntType>::param_type param_type;

  struct helper {
    IntType parts[2];
  };
  typedef IntType (helper::*bool_type)[2];

public:
  // Component type
  typedef IntType int_type;

  BOOST_CONSTEXPR
  rat() : num(0), den(1) {}
  BOOST_CONSTEXPR
  rat(param_type n) : num(n), den(1) {}
  rat(param_type n, param_type d) : num(n), den(d) { normalize(); }

#ifndef BOOST_NO_MEMBER_TEMPLATES
  template <typename NewType>
  BOOST_CONSTEXPR explicit rat(rat<NewType> const &r)
      : num(r.numerator()), den(r.denominator()) {}
#endif

  // Default copy constructor and assignment are fine

  // Add assignment from IntType
  rat &operator=(param_type i) {
    num = i;
    den = 1;
    return *this;
  }

  // Assign in place
  rat &assign(param_type n, param_type d);

  // Access to representation
  BOOST_CONSTEXPR
  IntType numerator() const { return num; }
  BOOST_CONSTEXPR
  IntType denominator() const { return den; }

  // Arithmetic assignment operators
  rat &operator+=(const rat &r);
  rat &operator-=(const rat &r);
  rat &operator*=(const rat &r);
  rat &operator/=(const rat &r);

  rat &operator+=(param_type i) {
    num += i * den;
    return *this;
  }
  rat &operator-=(param_type i) {
    num -= i * den;
    return *this;
  }
  rat &operator*=(param_type i);
  rat &operator/=(param_type i);

  // Increment and decrement
  const rat &operator++() {
    num += den;
    return *this;
  }
  const rat &operator--() {
    num -= den;
    return *this;
  }

  // Operator not
  BOOST_CONSTEXPR
  bool operator!() const { return !num; }

// Boolean conversion

#if BOOST_WORKAROUND(__MWERKS__, <= 0x3003)
// The "ISO C++ Template Parser" option in CW 8.3 chokes on the
// following, hence we selectively disable that option for the
// offending memfun.
#pragma parse_mfunc_templ off
#endif

  BOOST_CONSTEXPR
  operator bool_type() const { return operator!() ? 0 : &helper::parts; }

#if BOOST_WORKAROUND(__MWERKS__, <= 0x3003)
#pragma parse_mfunc_templ reset
#endif

  // Comparison operators
  bool operator<(const rat &r) const;
  BOOST_CONSTEXPR
  bool operator==(const rat &r) const;

  bool operator<(param_type i) const;
  bool operator>(param_type i) const;
  BOOST_CONSTEXPR
  bool operator==(param_type i) const;

private:
  // Implementation - numerator and denominator (normalized).
  // Other possibilities - separate whole-part, or sign, fields?
  IntType num;
  IntType den;

  // Helper functions
  static BOOST_CONSTEXPR int_type
  inner_gcd(param_type a, param_type b, int_type const &zero = int_type(0)) {
    return b == zero ? a : inner_gcd(b, a % b, zero);
  }

  static BOOST_CONSTEXPR int_type
  inner_abs(param_type x, int_type const &zero = int_type(0)) {
    return x < zero ? -x : +x;
  }

  // Representation note: Fractions are kept in normalized form at all
  // times. normalized form is defined as gcd(num,den) == 1 and den > 0.
  // In particular, note that the implementation of abs() below relies
  // on den always being positive.
  // bool test_invariant() const;
  void normalize();

  static BOOST_CONSTEXPR bool is_normalized(param_type n, param_type d,
                                            int_type const &zero = int_type(0),
                                            int_type const &one = int_type(1)) {
    return d >= zero;
  }
};

// Assign in place
template <typename IntType>
inline rat<IntType> &rat<IntType>::assign(param_type n, param_type d) {
  return *this = rat(n, d);
}

// Unary plus and minus
template <typename IntType>
BOOST_CONSTEXPR inline rat<IntType> operator+(const rat<IntType> &r) {
  return r;
}

template <typename IntType>
inline rat<IntType> operator-(const rat<IntType> &r) {
  return rat<IntType>(-r.numerator(), r.denominator());
}

// Arithmetic assignment operators
template <typename IntType>
rat<IntType> &rat<IntType>::operator+=(const rat<IntType> &r) {
  // This calculation avoids overflow, and minimises the number of expensive
  // calculations. Thanks to Nickolay Mladenov for this algorithm.
  //
  // Proof:
  // We have to compute a/b + c/d, where gcd(a,b)=1 and gcd(b,c)=1.
  // Let g = gcd(b,d), and b = b1*g, d=d1*g. Then gcd(b1,d1)=1
  //
  // The result is (a*d1 + c*b1) / (b1*d1*g).
  // Now we have to normalize this ratio.
  // Let's assume h | gcd((a*d1 + c*b1), (b1*d1*g)), and h > 1
  // If h | b1 then gcd(h,d1)=1 and hence h|(a*d1+c*b1) => h|a.
  // But since gcd(a,b1)=1 we have h=1.
  // Similarly h|d1 leads to h=1.
  // So we have that h | gcd((a*d1 + c*b1) , (b1*d1*g)) => h|g
  // Finally we have gcd((a*d1 + c*b1), (b1*d1*g)) = gcd((a*d1 + c*b1), g)
  // Which proves that instead of normalizing the result, it is better to
  // divide num and den by gcd((a*d1 + c*b1), g)

  // Protect against self-modification
  IntType r_num = r.num;
  IntType r_den = r.den;

  // Avoid repeated construction
  // IntType zero(0);

  if (den == r_den) {
    num += r_num;
    return *this;
  }

  IntType g = gcd(den, r_den);
  den /= g; // = b1 from the calculations above
  num = num * (r_den / g) + r_num * den;
  g = gcd(num, g);
  num /= g;
  den *= r_den / g;

  return *this;
}

template <typename IntType>
rat<IntType> &rat<IntType>::operator-=(const rat<IntType> &r) {
  // Protect against self-modification
  IntType r_num = r.num;
  IntType r_den = r.den;

  // Avoid repeated construction
  // IntType zero(0);

  if (den == r_den) {
    num -= r_num;
    return *this;
  }

  // This calculation avoids overflow, and minimises the number of expensive
  // calculations. It corresponds exactly to the += case above
  IntType g = gcd(den, r_den);
  den /= g;
  num = num * (r_den / g) - r_num * den;
  g = gcd(num, g);
  num /= g;
  den *= r_den / g;

  return *this;
}

template <typename IntType>
rat<IntType> &rat<IntType>::operator*=(const rat<IntType> &r) {
  // Protect against self-modification
  IntType r_num = r.num;
  IntType r_den = r.den;

  if (num == r_den) {
    num = r_num;
    return *this;
  }
  if (den == r_num) {
    den = r_den;
    return *this;
  }

  num *= r_num;
  den *= r_den;
  return *this;
}

template <typename IntType>
rat<IntType> &rat<IntType>::operator/=(const rat<IntType> &r) {
  // Protect against self-modification
  IntType r_num = r.num;
  IntType r_den = r.den;

  // Avoid repeated construction
  IntType zero(0);

  // Trap division by zero
  if (r_num == zero && num == zero) {
    den = zero;
    return *this;
  }
  if (r_den == zero && den == zero) {
    num = zero;
    return *this;
  }

  // Avoid overflow and preserve normalization
  IntType gcd1 = gcd(num, r_num);
  IntType gcd2 = gcd(r_den, den);
  num = (num / gcd1) * (r_den / gcd2);
  den = (den / gcd2) * (r_num / gcd1);

  if (den < zero) {
    num = -num;
    den = -den;
  }
  return *this;
}

// Mixed-mode operators
template <typename IntType>
inline rat<IntType> &rat<IntType>::operator*=(param_type i) {
  // Avoid repeated construction
  IntType zero(0);

  if (i == zero && den == zero) {
    num = zero;
    return *this;
  }

  // Avoid overflow and preserve normalization
  IntType gcd1 = gcd(i, den);
  num *= i / gcd1;
  den /= gcd1;

  return *this;
}

template <typename IntType>
rat<IntType> &rat<IntType>::operator/=(param_type i) {
  // Avoid repeated construction
  IntType const zero(0);

  if (i == zero && num == zero) {
    den = zero;
    return *this;
  }

  // Avoid overflow and preserve normalization
  IntType const gcd1 = gcd(num, i);
  num /= gcd1;
  den *= i / gcd1;

  if (den < zero) {
    num = -num;
    den = -den;
  }

  return *this;
}

// Comparison operators
template <typename IntType>
bool rat<IntType>::operator<(const rat<IntType> &r) const {
  if (den == r.den)
    return num < r.num;
  return num * r.den < den * r.num;
}

template <typename IntType> bool rat<IntType>::operator<(param_type i) const {
  return num < den * i;
}

template <typename IntType> bool rat<IntType>::operator>(param_type i) const {
  return operator==(i) ? false : !operator<(i);
}

template <typename IntType>
BOOST_CONSTEXPR inline bool is_NaN(const rat<IntType> &r) {
  // Avoid repeated construction
  IntType const zero(0);
  return r.denominator() == zero && r.numerator() == zero;
}

template <typename IntType>
BOOST_CONSTEXPR inline bool rat<IntType>::
operator==(const rat<IntType> &r) const {
  if (den == r.den)
    return num == r.num;
  return num * r.den == r.num * den;
}

template <typename IntType>
BOOST_CONSTEXPR inline bool rat<IntType>::operator==(param_type i) const {
  return num == i * den;
}

// Invariant check
// template <typename IntType>
// inline bool rat<IntType>::test_invariant() const
//{
//    if (this->den == int_type(0) ) return true;
//    return ( gcd(this->num, this->den) == int_type(1) );
//}

// Normalisation
template <typename IntType> void rat<IntType>::normalize() {
  // Avoid repeated construction
  IntType zero(0);

  if (den == zero)
    return;

  // Handle the case of zero separately, to avoid division by zero
  if (num == zero) {
    den = IntType(1);
    return;
  }

  IntType g = gcd(num, den);

  num /= g;
  den /= g;

  // Ensure that the denominator is positive
  if (den < zero) {
    num = -num;
    den = -den;
  }

  // ...But acknowledge that the previous step doesn't always work.
  // (Nominally, this should be done before the mutating steps, but this
  // member function is only called during the constructor, so we never have
  // to worry about zombie objects.)
  // if (den < zero)
  //     throw bad_rat( "bad rat: non-zero singular denominator" );

  // BOOST_ASSERT( this->test_invariant() );
}

#ifndef BOOST_NO_IOSTREAM
namespace detail {

// A utility class to reset the format flags for an istream at end
// of scope, even in case of exceptions
struct resetter {
  resetter(std::istream &is) : is_(is), f_(is.flags()) {}
  ~resetter() { is_.flags(f_); }
  std::istream &is_;
  std::istream::fmtflags f_; // old GNU c++ lib has no ios_base
};
}

// Input and output
template <typename IntType>
std::istream &operator>>(std::istream &is, rat<IntType> &r) {
  using std::ios;

  IntType n = IntType(0), d = IntType(1);
  char c = 0;
  detail::resetter sentry(is);

  if (is >> n) {
    if (is.get(c)) {
      if (c == '/') {
        if (is >> std::noskipws >> d)
          r.assign(n, d);
      } else
        is.setstate(ios::failbit);
    }
  }

  return is;
}

// Add manipulators for output format?
template <typename IntType>
std::ostream &operator<<(std::ostream &os, const rat<IntType> &r) {
  using namespace std;

  // The slash directly precedes the denominator, which has no prefixes.
  ostringstream ss;

  ss.copyfmt(os);
  ss.tie(NULL);
  ss.exceptions(ios::goodbit);
  ss.width(0);
  ss << noshowpos << noshowbase << '/' << r.denominator();

  // The numerator holds the showpos, internal, and showbase flags.
  string const tail = ss.str();
  streamsize const w = os.width() - static_cast<streamsize>(tail.size());

  ss.clear();
  ss.str("");
  ss.flags(os.flags());
  ss << setw(w < 0 || (os.flags() & ios::adjustfield) != ios::internal ? 0 : w)
     << r.numerator();
  return os << ss.str() + tail;
}
#endif // BOOST_NO_IOSTREAM

// Type conversion
template <typename T, typename IntType>
BOOST_CONSTEXPR inline T rat_cast(const rat<IntType> &src) {
  return static_cast<T>(src.numerator()) / static_cast<T>(src.denominator());
}

// Do not use any abs() defined on IntType - it isn't worth it, given the
// difficulties involved (Koenig lookup required, there may not *be* an abs()
// defined, etc etc).
template <typename IntType> inline rat<IntType> abs(const rat<IntType> &r) {
  return r.numerator() >= IntType(0) ? r : -r;
}

} // namespace boost

#endif // BOOST_RAT_HPP


#include <Eigen/Dense>
#include <iostream>

using namespace std;

int main()
{
  Eigen::ArrayXf v(6);
  v << 1, 2, 3, 4, 5, 6;
  cout << "v.head(3) =" << endl << v.head(3) << endl << endl;
  cout << "v.tail<3>() = " << endl << v.tail<3>() << endl << endl;
  v.segment(1,4) *= 2;
  cout << "after 'v.segment(1,4) *= 2', v =" << endl << v << endl;
}


#include "fem.hpp"
#include <iostream>

#include <deal.II/grid/tria.h>
#include <deal.II/dofs/dof_handler.h>
#include <deal.II/grid/grid_generator.h>

#include <deal.II/grid/tria_accessor.h>
#include <deal.II/grid/tria_iterator.h>
#include <deal.II/dofs/dof_accessor.h>

#include <deal.II/dofs/dof_tools.h>
#include <deal.II/fe/fe_values.h>
#include <deal.II/base/quadrature_lib.h>

#include <deal.II/base/function.h>
#include <deal.II/numerics/vector_tools.h>
#include <deal.II/numerics/matrix_tools.h>

#include <deal.II/lac/precondition.h>
#include <deal.II/lac/full_matrix.h>
#include <deal.II/lac/solver_cg.h>

using namespace dealii;

Fem::Fem()
  : fe(1) //bi-linear basis functions
  , dof_handler(triangulation)
{}

void Fem::make_grid()
{
  GridGenerator::hyper_cube(triangulation, -1, 1); // a square [-1,1] x [-1,1]
  triangulation.refine_global(5); // final grid has 32 times 32 (= 1024) cells
}

void Fem::setup_system()
{
  dof_handler.distribute_dofs(fe);

  DynamicSparsityPattern dsp(dof_handler.n_dofs());
  DoFTools::make_sparsity_pattern(dof_handler, dsp);
  sparsity_pattern.copy_from(dsp);

  system_matrix.reinit(sparsity_pattern);
  solution.reinit(dof_handler.n_dofs());
  system_rhs.reinit(dof_handler.n_dofs());
}

void Fem::assemble_system()
{
  QGauss<2> quadrature_formula(fe.degree + 1);
  FEValues<2> fe_values(fe,
                        quadrature_formula,
                        update_values | update_gradients | update_JxW_values);
  const unsigned int dofs_per_cell = fe.dofs_per_cell;
  const unsigned int n_q_points    = quadrature_formula.size();

  FullMatrix<double> cell_matrix(dofs_per_cell, dofs_per_cell);
  Vector<double>     cell_rhs(dofs_per_cell);

  std::vector<types::global_dof_index> local_dof_indices(dofs_per_cell);

  for (const auto &cell : dof_handler.active_cell_iterators())
    {
      fe_values.reinit(cell);

      cell_matrix = 0;
      cell_rhs    = 0;

      for (unsigned int q_index = 0; q_index < n_q_points; ++q_index)
        {
          for (unsigned int i = 0; i < dofs_per_cell; ++i)
            for (unsigned int j = 0; j < dofs_per_cell; ++j)
              cell_matrix(i, j) +=
                (fe_values.shape_grad(i, q_index) * // grad phi_i(x_q)
                 fe_values.shape_grad(j, q_index) * // grad phi_j(x_q)
                 fe_values.JxW(q_index));           // dx
          for (unsigned int i = 0; i < dofs_per_cell; ++i)
            cell_rhs(i) += (fe_values.shape_value(i, q_index) * // phi_i(x_q)
                            1 *                                 // f(x_q)
                            fe_values.JxW(q_index));            // dx
        }
      cell->get_dof_indices(local_dof_indices);

      for (unsigned int i = 0; i < dofs_per_cell; ++i)
        for (unsigned int j = 0; j < dofs_per_cell; ++j)
          system_matrix.add(local_dof_indices[i],
                            local_dof_indices[j],
                            cell_matrix(i, j));

      for (unsigned int i = 0; i < dofs_per_cell; ++i)
        system_rhs(local_dof_indices[i]) += cell_rhs(i);
    }

  // boundary conditions
  std::map<types::global_dof_index, double> boundary_values;
  VectorTools::interpolate_boundary_values(dof_handler,
                                           0,
                                           Functions::ZeroFunction<2>(),
                                           boundary_values);
  MatrixTools::apply_boundary_values(boundary_values,
                                     system_matrix,
                                     solution,
                                     system_rhs);
}

// solve with Conjugate Gradients method
void Fem::solve()
{
  SolverControl solver_control(1000, 1e-12);
  SolverCG<Vector<double>> solver(solver_control);
  solver.solve(system_matrix, solution, system_rhs, PreconditionIdentity());
}

void Fem::output_results() const
{
  DataOut<2> data_out;
  data_out.attach_dof_handler(dof_handler);
  data_out.add_data_vector(solution, "solution");
  data_out.build_patches();
  std::ofstream output("solution.vtk");
  data_out.write_vtk(output);
}

void Fem::run()
{
  make_grid();
  setup_system();
  assemble_system();
  solve();
  output_results();
  std::cout << "FEM results available in `solution.vtk`. Try visualizing with Paraview." << std::endl;
}