// Copyright (c) 2018 The Bitcoin developers
// Copyright (c) 2018-2019 Bitcoin Association
// Distributed under the Open BSV software license, see the accompanying file LICENSE.

#include "test/test_bitcoin.h"
#include "script/limitedstack.h"
#include <boost/test/unit_test.hpp>

typedef std::vector<uint8_t> valtype;

BOOST_FIXTURE_TEST_SUITE(limited_stack_vector_tests, BasicTestingSetup)


BOOST_AUTO_TEST_CASE(limitedstack_push_test) {
    ////////// LimitedStack Push back check //////////
    {
        valtype vtype;
        vtype.push_back(0xab);
        LimitedStack limitedStack(100);
        int push_count = 3;

        // Push back
        for (int i = 0; i < push_count; i++) {
            limitedStack.push_back(vtype);
        }

        BOOST_CHECK_EQUAL(limitedStack.size(), push_count);
        BOOST_CHECK_EQUAL(limitedStack.getCombinedStackSize(), (push_count*LimitedVector::ELEMENT_OVERHEAD) + 3);
        BOOST_CHECK_THROW(limitedStack.push_back(vtype), std::runtime_error);
    }
}

BOOST_AUTO_TEST_CASE(limitedstack_insert_test) {
    ////////// LimitedStack Insert check //////////
    {
        LimitedStack limitedStack(100);
        BOOST_CHECK_EQUAL(limitedStack.size(), 0);

        limitedStack.push_back({0xab}); // There needs to be at least one element for insert method to work.

        BOOST_CHECK_EQUAL(limitedStack.size(), 1);
        BOOST_CHECK_EQUAL(limitedStack.getCombinedStackSize(), limitedStack.size()*LimitedVector::ELEMENT_OVERHEAD + 1);

        LimitedVector limitedVector = limitedStack.stacktop(-1);

        // Insert
        limitedStack.insert(-1, limitedVector);
        limitedStack.insert(-1, limitedVector);

        BOOST_CHECK_EQUAL(limitedStack.size(), 3);
        BOOST_CHECK_EQUAL(limitedStack.getCombinedStackSize(), (limitedStack.size()*LimitedVector::ELEMENT_OVERHEAD) + 3);
        BOOST_CHECK_THROW(limitedStack.insert(-1, limitedVector), std::runtime_error);
    }
}

BOOST_AUTO_TEST_CASE(limitedstack_erase_test) {
    ////////// LimitedStack erase(int first, int last) check //////////
    {
        LimitedStack limitedStack({{0xab}, {0xcd}, {0xef}, {0xab}}, 200);

        BOOST_CHECK_EQUAL(limitedStack.size(), 4);

        limitedStack.erase(-3, -1);

        BOOST_CHECK_EQUAL(limitedStack.size(), 2);
        BOOST_CHECK_EQUAL(limitedStack.at(0).GetElement().at(0), 0xab);
        BOOST_CHECK_EQUAL(limitedStack.at(1).GetElement().at(0), 0xab);
    }

    ////////// LimitedStack erase(int index) check //////////
    {
        LimitedStack limitedStack({{0xab}, {0xcd}, {0xef}, {0xab}}, 200);
        BOOST_CHECK_EQUAL(limitedStack.size(), 4);
        limitedStack.erase(-3);

        BOOST_CHECK_EQUAL(limitedStack.size(), 3);
        BOOST_CHECK_EQUAL(limitedStack.at(0).GetElement().at(0), 0xab);
        BOOST_CHECK_EQUAL(limitedStack.at(1).GetElement().at(0), 0xef);
        BOOST_CHECK_EQUAL(limitedStack.at(2).GetElement().at(0), 0xab);
    }
}

BOOST_AUTO_TEST_CASE(limitedstack_empty_test) {
    ////////// LimitedStack empty() check //////////
    {
        LimitedStack limitedStack(200);
        BOOST_CHECK_EQUAL(limitedStack.empty(), true);
        limitedStack.push_back({0xab});
        BOOST_CHECK_EQUAL(limitedStack.size(), 1);
        BOOST_CHECK_EQUAL(limitedStack.empty(), false);
    }
}

BOOST_AUTO_TEST_CASE(limitedstack_op_sqbr_test) {
    ////////// LimitedStack operator[] check //////////
    {
        LimitedStack limitedStack({{0xab, 0xcd}}, 100);
        BOOST_CHECK_EQUAL(limitedStack.front()[0], 0xab);
        BOOST_CHECK_EQUAL(limitedStack.front()[1], 0xcd);
    }
}

BOOST_AUTO_TEST_CASE(limitedstack_front_back_test) {
    ////////// LimitedStack front() and back() check //////////
    {
        LimitedStack limitedStack(200);
        limitedStack.push_back({0xab, 0xcd});
        limitedStack.push_back({0xff, 0xfe});
        limitedStack.push_back({0xef, 0x12});
        BOOST_CHECK_EQUAL(limitedStack.front()[0], 0xab);
        BOOST_CHECK_EQUAL(limitedStack.front()[1], 0xcd);
        BOOST_CHECK_EQUAL(limitedStack.back()[0], 0xef);
        BOOST_CHECK_EQUAL(limitedStack.back()[1], 0x12);
    }
}

BOOST_AUTO_TEST_CASE(limitedstack_swap_test) {
    ////////// LimitedStack Swap check //////////
    {
        valtype vtype;
        LimitedStack limitedStack(100);

        BOOST_CHECK_EQUAL(limitedStack.size(), 0);

        limitedStack.push_back({0xab});
        limitedStack.push_back({0xcd});
        uint64_t size_combined_before_swap = limitedStack.getCombinedStackSize();
        size_t size_before_swap = limitedStack.size();
        limitedStack.swapElements(0, 1);

        BOOST_CHECK_EQUAL(limitedStack.at(0).GetElement().at(0), 0xcd);
        BOOST_CHECK_EQUAL(limitedStack.at(1).GetElement().at(0), 0xab);
        BOOST_CHECK_EQUAL(size_combined_before_swap, limitedStack.getCombinedStackSize());
        BOOST_CHECK_EQUAL(size_before_swap, limitedStack.size());
    }
}

BOOST_AUTO_TEST_CASE(limitedstack_child_test) {
    ////////// LimitedStack Child check //////////
    {
        valtype vtype;
        LimitedStack limitedStack(100);
        LimitedStack limitedStack_child = limitedStack.makeChildStack();
        vtype.clear();
        vtype.push_back({0xab});
        limitedStack.push_back(vtype);

        // Check that combined size increases if parent is increased
        limitedStack.push_back(vtype);
        BOOST_CHECK_EQUAL(limitedStack.getCombinedStackSize(), 2 * (vtype.size() + LimitedVector::ELEMENT_OVERHEAD));
        BOOST_CHECK_EQUAL(limitedStack_child.getCombinedStackSize(), 2 * (vtype.size() + LimitedVector::ELEMENT_OVERHEAD));

        // Check that combined size increases if child is increased
        limitedStack_child.push_back(vtype);
        BOOST_CHECK_EQUAL(limitedStack.getCombinedStackSize(), 3 * (vtype.size() + LimitedVector::ELEMENT_OVERHEAD));
        BOOST_CHECK_EQUAL(limitedStack_child.getCombinedStackSize(), 3 * (vtype.size() + LimitedVector::ELEMENT_OVERHEAD));
    }
}

BOOST_AUTO_TEST_CASE(limitedstack_movetoptostack_test) {
    ////////// LimitedStack MoveTopToStack check //////////
    {
        LimitedStack limitedStack_parent(100);
        LimitedStack limitedStack_child = limitedStack_parent.makeChildStack();
        limitedStack_parent.push_back({0xab, 0xcd});
        limitedStack_child.push_back({0xef, 0x12});

        uint64_t size_child =  limitedStack_child.getCombinedStackSize();
        uint64_t size_parent = limitedStack_parent.getCombinedStackSize();

        limitedStack_child.moveTopToStack(limitedStack_parent);

        BOOST_CHECK_EQUAL(limitedStack_parent.getCombinedStackSize(), size_parent);
        BOOST_CHECK_EQUAL(limitedStack_parent.getCombinedStackSize(), size_child);
        BOOST_CHECK_EQUAL(limitedStack_child.getCombinedStackSize(), size_parent);
        BOOST_CHECK_EQUAL(limitedStack_child.getCombinedStackSize(), size_child);

        BOOST_CHECK_EQUAL(limitedStack_child.at(0).GetElement().at(0), 0xef);
        BOOST_CHECK_EQUAL(limitedStack_child.at(0).GetElement().at(1), 0x12);
        BOOST_CHECK_EQUAL(limitedStack_child.at(1).GetElement().at(0), 0xab);
        BOOST_CHECK_EQUAL(limitedStack_child.at(1).GetElement().at(1), 0xcd);

        BOOST_CHECK_EQUAL(limitedStack_child.getCombinedStackSize(), 2 * (limitedStack_child.size() + LimitedVector::ELEMENT_OVERHEAD));
        BOOST_CHECK_EQUAL(limitedStack_parent.size(), 0);
    }
}

BOOST_AUTO_TEST_CASE(limitedstack_movetovaltypes_test) {
    ////////// LimitedStack MoveToValtypes check //////////
    {
        LimitedStack limitedStack(100);
        std::vector<valtype> valtypeVector;

        valtype vtype0({0xab, 0xcd});
        valtype vtype1({0xef, 0x12});

        limitedStack.push_back(vtype0);
        limitedStack.push_back(vtype1);

        limitedStack.MoveToValtypes(valtypeVector);

        BOOST_CHECK_EQUAL(valtypeVector[0][0], vtype0.at(0));
        BOOST_CHECK_EQUAL(valtypeVector[0][1], vtype0.at(1));
        BOOST_CHECK_EQUAL(valtypeVector[1][0], vtype1.at(0));
        BOOST_CHECK_EQUAL(valtypeVector[1][1], vtype1.at(1));
        BOOST_CHECK_EQUAL(limitedStack.size(), 0);
    }
}

BOOST_AUTO_TEST_CASE(limitedvector_append_test) {
    ////////// LimitedVector append check //////////
    {
        valtype vtype;
        LimitedStack limitedStack(100);
        vtype.push_back({0xab});
        vtype.push_back({0xcd});

        limitedStack.push_back({0xef});
        limitedStack.push_back(vtype);

        LimitedVector &limitedVector1 = limitedStack.stacktop(-2);
        int size_before = limitedVector1.size();
        LimitedVector limitedVector2 = limitedStack.stacktop(-1);

        limitedVector1.append(limitedVector2);

        BOOST_CHECK_EQUAL(limitedVector1.size(), size_before + 2);
        BOOST_CHECK_EQUAL(limitedVector1.GetElement().at(0), 0xef);
        BOOST_CHECK_EQUAL(limitedVector1.GetElement().at(1), 0xab);
        BOOST_CHECK_EQUAL(limitedVector1.GetElement().at(2), 0xcd);
    }
}

BOOST_AUTO_TEST_CASE(limitedvector_padright_test) {
    ////////// LimitedVector padRight check //////////
    {
        // Pad > size
        {
            valtype vtype;
            LimitedStack limitedStack(100);
            int pad_size = 10;
            limitedStack.push_back({});
            LimitedVector &limitedVector = limitedStack.stacktop(-1);
            size_t size_before = limitedVector.size();

            BOOST_CHECK_LT(size_before, pad_size);
            limitedVector.padRight(pad_size, 0);
            BOOST_CHECK_EQUAL(limitedVector.size(), pad_size);
        }

        // Pad < size
        {
            LimitedStack limitedStack(100);
            int pad_size = 10;
            valtype vtype(pad_size + 2, 0xcd);

            limitedStack.push_back(vtype);
            LimitedVector limitedVector = limitedStack.stacktop(-1);

            limitedVector.padRight(pad_size, 0);
            BOOST_CHECK_EQUAL(limitedVector.size(), pad_size + 2);
        }
    }
}

BOOST_AUTO_TEST_SUITE_END()
