#include <test/jtx.h>

#include <xrpld/core/ConfigSections.h>

#include <xrpl/ledger/ApplyViewImpl.h>
#include <xrpl/ledger/Ledger.h>
#include <xrpl/ledger/OpenView.h>
#include <xrpl/ledger/PaymentSandbox.h>
#include <xrpl/ledger/Sandbox.h>
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/ledger/helpers/TokenHelpers.h>
#include <xrpl/protocol/Feature.h>

#include <type_traits>

namespace xrpl {
namespace test {

class View_test : public beast::unit_test::suite
{
    // Convert a small integer to a key
    static Keylet
    k(std::uint64_t id)
    {
        return Keylet{ltACCOUNT_ROOT, uint256(id)};
    }

    // Create SLE with key and payload
    static std::shared_ptr<SLE>
    sle(std::uint64_t id, std::uint32_t seq = 1)
    {
        auto const le = std::make_shared<SLE>(k(id));
        le->setFieldU32(sfSequence, seq);
        return le;
    }

    // Return payload for SLE
    template <class T>
    static std::uint32_t
    seq(std::shared_ptr<T> const& le)
    {
        return le->getFieldU32(sfSequence);
    }

    // Set payload on SLE
    static void
    seq(std::shared_ptr<SLE> const& le, std::uint32_t seq)
    {
        le->setFieldU32(sfSequence, seq);
    }

    // Erase all state items
    static void
    wipe(OpenLedger& openLedger)
    {
        openLedger.modify([](OpenView& view, beast::Journal) {
            // HACK!
            std::optional<uint256> next;
            next.emplace(0);
            for (;;)
            {
                next = view.succ(*next);
                if (!next)
                    break;
                view.rawErase(std::make_shared<SLE>(*view.read(keylet::unchecked(*next))));
            }
            return true;
        });
    }

    static void
    wipe(Ledger& ledger)
    {
        // HACK!
        std::optional<uint256> next;
        next.emplace(0);
        for (;;)
        {
            next = ledger.succ(*next);
            if (!next)
                break;
            ledger.rawErase(std::make_shared<SLE>(*ledger.read(keylet::unchecked(*next))));
        }
    }

    // Test succ correctness
    void
    succ(ReadView const& v, std::uint32_t id, std::optional<std::uint32_t> answer)
    {
        auto const next = v.succ(k(id).key);
        if (answer)
        {
            if (BEAST_EXPECT(next); next.has_value())
                BEAST_EXPECT(*next == k(*answer).key);
        }
        else
        {
            BEAST_EXPECT(!next);
        }
    }

    template <class T>
    static std::shared_ptr<std::remove_const_t<T>>
    copy(std::shared_ptr<T> const& sp)
    {
        return std::make_shared<std::remove_const_t<T>>(*sp);
    }

    // Exercise Ledger implementation of ApplyView
    void
    testLedger()
    {
        testcase("Ledger");

        using namespace jtx;
        Env env(*this);
        Config config;
        std::shared_ptr<Ledger const> const genesis = std::make_shared<Ledger>(
            create_genesis,
            Rules{config.features},
            config.FEES.toFees(),
            std::vector<uint256>{},
            env.app().getNodeFamily());
        auto const ledger = std::make_shared<Ledger>(*genesis, env.app().timeKeeper().closeTime());
        wipe(*ledger);
        ReadView& v = *ledger;
        succ(v, 0, std::nullopt);
        ledger->rawInsert(sle(1, 1));
        BEAST_EXPECT(v.exists(k(1)));
        BEAST_EXPECT(seq(v.read(k(1))) == 1);
        succ(v, 0, 1);
        succ(v, 1, std::nullopt);
        ledger->rawInsert(sle(2, 2));
        BEAST_EXPECT(seq(v.read(k(2))) == 2);
        ledger->rawInsert(sle(3, 3));
        BEAST_EXPECT(seq(v.read(k(3))) == 3);
        auto s = copy(v.read(k(2)));
        seq(s, 4);
        ledger->rawReplace(s);
        BEAST_EXPECT(seq(v.read(k(2))) == 4);
        ledger->rawErase(sle(2));
        BEAST_EXPECT(!v.exists(k(2)));
        BEAST_EXPECT(v.exists(k(1)));
        BEAST_EXPECT(v.exists(k(3)));
    }

    void
    testMeta()
    {
        testcase("Meta");

        using namespace jtx;
        Env env(*this);
        wipe(env.app().openLedger());
        auto const open = env.current();
        ApplyViewImpl v(&*open, tapNONE);
        succ(v, 0, std::nullopt);
        v.insert(sle(1));
        BEAST_EXPECT(v.exists(k(1)));
        BEAST_EXPECT(seq(v.read(k(1))) == 1);
        BEAST_EXPECT(seq(v.peek(k(1))) == 1);
        succ(v, 0, 1);
        succ(v, 1, std::nullopt);
        v.insert(sle(2, 2));
        BEAST_EXPECT(seq(v.read(k(2))) == 2);
        v.insert(sle(3, 3));
        auto s = v.peek(k(3));
        BEAST_EXPECT(seq(s) == 3);
        s = v.peek(k(2));
        seq(s, 4);
        v.update(s);
        BEAST_EXPECT(seq(v.read(k(2))) == 4);
        v.erase(s);
        BEAST_EXPECT(!v.exists(k(2)));
        BEAST_EXPECT(v.exists(k(1)));
        BEAST_EXPECT(v.exists(k(3)));
    }

    // Exercise all succ paths
    void
    testMetaSucc()
    {
        testcase("Meta succ");

        using namespace jtx;
        Env env(*this);
        wipe(env.app().openLedger());
        auto const open = env.current();
        ApplyViewImpl v0(&*open, tapNONE);
        v0.insert(sle(1));
        v0.insert(sle(2));
        v0.insert(sle(4));
        v0.insert(sle(7));
        {
            Sandbox v1(&v0);
            v1.insert(sle(3));
            v1.insert(sle(5));
            v1.insert(sle(6));

            // v0: 12-4--7
            // v1: --3-56-

            succ(v0, 0, 1);
            succ(v0, 1, 2);
            succ(v0, 2, 4);
            succ(v0, 3, 4);
            succ(v0, 4, 7);
            succ(v0, 5, 7);
            succ(v0, 6, 7);
            succ(v0, 7, std::nullopt);

            succ(v1, 0, 1);
            succ(v1, 1, 2);
            succ(v1, 2, 3);
            succ(v1, 3, 4);
            succ(v1, 4, 5);
            succ(v1, 5, 6);
            succ(v1, 6, 7);
            succ(v1, 7, std::nullopt);

            v1.erase(v1.peek(k(4)));
            succ(v1, 3, 5);

            v1.erase(v1.peek(k(6)));
            succ(v1, 5, 7);
            succ(v1, 6, 7);

            // v0: 12----7
            // v1: --3-5--

            v1.apply(v0);
        }

        // v0: 123-5-7

        succ(v0, 0, 1);
        succ(v0, 1, 2);
        succ(v0, 2, 3);
        succ(v0, 3, 5);
        succ(v0, 4, 5);
        succ(v0, 5, 7);
        succ(v0, 6, 7);
        succ(v0, 7, std::nullopt);
    }

    void
    testStacked()
    {
        testcase("Stacked");

        using namespace jtx;
        Env env(*this);
        wipe(env.app().openLedger());
        auto const open = env.current();
        ApplyViewImpl v0(&*open, tapNONE);
        v0.rawInsert(sle(1, 1));
        v0.rawInsert(sle(2, 2));
        v0.rawInsert(sle(4, 4));

        {
            Sandbox v1(&v0);
            v1.erase(v1.peek(k(2)));
            v1.insert(sle(3, 3));
            auto s = v1.peek(k(4));
            seq(s, 5);
            v1.update(s);
            BEAST_EXPECT(seq(v1.read(k(1))) == 1);
            BEAST_EXPECT(!v1.exists(k(2)));
            BEAST_EXPECT(seq(v1.read(k(3))) == 3);
            BEAST_EXPECT(seq(v1.read(k(4))) == 5);
            {
                Sandbox v2(&v1);
                auto s2 = v2.peek(k(3));
                seq(s2, 6);
                v2.update(s2);
                v2.erase(v2.peek(k(4)));
                BEAST_EXPECT(seq(v2.read(k(1))) == 1);
                BEAST_EXPECT(!v2.exists(k(2)));
                BEAST_EXPECT(seq(v2.read(k(3))) == 6);
                BEAST_EXPECT(!v2.exists(k(4)));
                // discard v2
            }
            BEAST_EXPECT(seq(v1.read(k(1))) == 1);
            BEAST_EXPECT(!v1.exists(k(2)));
            BEAST_EXPECT(seq(v1.read(k(3))) == 3);
            BEAST_EXPECT(seq(v1.read(k(4))) == 5);

            {
                Sandbox v2(&v1);
                auto s2 = v2.peek(k(3));
                seq(s2, 6);
                v2.update(s2);
                v2.erase(v2.peek(k(4)));
                BEAST_EXPECT(seq(v2.read(k(1))) == 1);
                BEAST_EXPECT(!v2.exists(k(2)));
                BEAST_EXPECT(seq(v2.read(k(3))) == 6);
                BEAST_EXPECT(!v2.exists(k(4)));
                v2.apply(v1);
            }
            BEAST_EXPECT(seq(v1.read(k(1))) == 1);
            BEAST_EXPECT(!v1.exists(k(2)));
            BEAST_EXPECT(seq(v1.read(k(3))) == 6);
            BEAST_EXPECT(!v1.exists(k(4)));
            v1.apply(v0);
        }
        BEAST_EXPECT(seq(v0.read(k(1))) == 1);
        BEAST_EXPECT(!v0.exists(k(2)));
        BEAST_EXPECT(seq(v0.read(k(3))) == 6);
        BEAST_EXPECT(!v0.exists(k(4)));
    }

    // Verify contextual information
    void
    testContext()
    {
        testcase("Context");

        using namespace jtx;
        using namespace std::chrono;
        {
            Env env(*this);
            wipe(env.app().openLedger());
            auto const open = env.current();
            OpenView v0(open.get());
            BEAST_EXPECT(v0.seq() != 98);
            BEAST_EXPECT(v0.seq() == open->seq());
            BEAST_EXPECT(v0.parentCloseTime() != NetClock::time_point{99s});
            BEAST_EXPECT(v0.parentCloseTime() == open->parentCloseTime());
            {
                // shallow copy
                OpenView v1(v0);
                BEAST_EXPECT(v1.seq() == v0.seq());
                BEAST_EXPECT(v1.parentCloseTime() == v1.parentCloseTime());

                ApplyViewImpl v2(&v1, tapRETRY);
                BEAST_EXPECT(v2.parentCloseTime() == v1.parentCloseTime());
                BEAST_EXPECT(v2.seq() == v1.seq());
                BEAST_EXPECT(v2.flags() == tapRETRY);

                Sandbox v3(&v2);
                BEAST_EXPECT(v3.seq() == v2.seq());
                BEAST_EXPECT(v3.parentCloseTime() == v2.parentCloseTime());
                BEAST_EXPECT(v3.flags() == tapRETRY);
            }
            {
                ApplyViewImpl v1(&v0, tapRETRY);
                PaymentSandbox v2(&v1);
                BEAST_EXPECT(v2.seq() == v0.seq());
                BEAST_EXPECT(v2.parentCloseTime() == v0.parentCloseTime());
                BEAST_EXPECT(v2.flags() == tapRETRY);
                PaymentSandbox v3(&v2);
                BEAST_EXPECT(v3.seq() == v2.seq());
                BEAST_EXPECT(v3.parentCloseTime() == v2.parentCloseTime());
                BEAST_EXPECT(v3.flags() == v2.flags());
            }
        }
    }

    // Return a list of keys found via sles
    static std::vector<uint256>
    sles(ReadView const& ledger)
    {
        std::vector<uint256> v;
        v.reserve(32);
        for (auto const& sle : ledger.sles)
            v.push_back(sle->key());
        return v;
    }

    template <class... Args>
    static std::vector<uint256>
    list(Args... args)
    {
        return std::vector<uint256>({uint256(args)...});
    }

    void
    testUpperAndLowerBound()
    {
        testcase("Upper and lower bound");

        using namespace jtx;
        Env env(*this);
        Config config;
        std::shared_ptr<Ledger const> const genesis = std::make_shared<Ledger>(
            create_genesis,
            Rules{config.features},
            config.FEES.toFees(),
            std::vector<uint256>{},
            env.app().getNodeFamily());
        auto const ledger = std::make_shared<Ledger>(*genesis, env.app().timeKeeper().closeTime());

        auto setup = [&ledger](std::vector<int> const& vec) {
            wipe(*ledger);
            for (auto x : vec)
            {
                ledger->rawInsert(sle(x));
            }
        };
        {
            setup({1, 2, 3});
            BEAST_EXPECT(sles(*ledger) == list(1, 2, 3));
            auto e = ledger->stateMap().end();
            auto b1 = ledger->stateMap().begin();
            BEAST_EXPECT(ledger->stateMap().lower_bound(uint256(1)) == e);
            BEAST_EXPECT(ledger->stateMap().lower_bound(uint256(2)) == b1);
            ++b1;
            BEAST_EXPECT(ledger->stateMap().lower_bound(uint256(3)) == b1);
            ++b1;
            BEAST_EXPECT(ledger->stateMap().lower_bound(uint256(4)) == b1);
            BEAST_EXPECT(ledger->stateMap().lower_bound(uint256(5)) == b1);
            b1 = ledger->stateMap().begin();
            BEAST_EXPECT(ledger->stateMap().upper_bound(uint256(0)) == b1);
            ++b1;
            BEAST_EXPECT(ledger->stateMap().upper_bound(uint256(1)) == b1);
            ++b1;
            BEAST_EXPECT(ledger->stateMap().upper_bound(uint256(2)) == b1);
            BEAST_EXPECT(ledger->stateMap().upper_bound(uint256(3)) == e);
        }

        {
            setup({2, 4, 6});
            BEAST_EXPECT(sles(*ledger) == list(2, 4, 6));
            auto e = ledger->stateMap().end();
            auto b1 = ledger->stateMap().begin();
            BEAST_EXPECT(ledger->stateMap().lower_bound(uint256(1)) == e);
            BEAST_EXPECT(ledger->stateMap().lower_bound(uint256(2)) == e);
            BEAST_EXPECT(ledger->stateMap().lower_bound(uint256(3)) == b1);
            BEAST_EXPECT(ledger->stateMap().lower_bound(uint256(4)) == b1);
            ++b1;
            BEAST_EXPECT(ledger->stateMap().lower_bound(uint256(5)) == b1);
            BEAST_EXPECT(ledger->stateMap().lower_bound(uint256(6)) == b1);
            ++b1;
            BEAST_EXPECT(ledger->stateMap().lower_bound(uint256(7)) == b1);
            b1 = ledger->stateMap().begin();
            BEAST_EXPECT(ledger->stateMap().upper_bound(uint256(1)) == b1);
            ++b1;
            BEAST_EXPECT(ledger->stateMap().upper_bound(uint256(2)) == b1);
            BEAST_EXPECT(ledger->stateMap().upper_bound(uint256(3)) == b1);
            ++b1;

            BEAST_EXPECT(ledger->stateMap().upper_bound(uint256(4)) == b1);
            BEAST_EXPECT(ledger->stateMap().upper_bound(uint256(5)) == b1);
            BEAST_EXPECT(ledger->stateMap().upper_bound(uint256(6)) == e);
            BEAST_EXPECT(ledger->stateMap().upper_bound(uint256(7)) == e);
        }
        {
            setup({2, 3, 5, 6, 10, 15});
            BEAST_EXPECT(sles(*ledger) == list(2, 3, 5, 6, 10, 15));
            auto e = ledger->stateMap().end();
            auto b = ledger->stateMap().begin();
            BEAST_EXPECT(ledger->stateMap().lower_bound(uint256(1)) == e);
            BEAST_EXPECT(ledger->stateMap().lower_bound(uint256(2)) == e);
            BEAST_EXPECT(ledger->stateMap().lower_bound(uint256(3)) == b);
            ++b;
            BEAST_EXPECT(ledger->stateMap().lower_bound(uint256(4)) == b);
            BEAST_EXPECT(ledger->stateMap().lower_bound(uint256(5)) == b);
            ++b;
            BEAST_EXPECT(ledger->stateMap().lower_bound(uint256(6)) == b);
            ++b;
            BEAST_EXPECT(ledger->stateMap().lower_bound(uint256(7)) == b);
            BEAST_EXPECT(ledger->stateMap().lower_bound(uint256(8)) == b);
            BEAST_EXPECT(ledger->stateMap().lower_bound(uint256(9)) == b);
            BEAST_EXPECT(ledger->stateMap().lower_bound(uint256(10)) == b);
            ++b;
            BEAST_EXPECT(ledger->stateMap().lower_bound(uint256(11)) == b);
            BEAST_EXPECT(ledger->stateMap().lower_bound(uint256(12)) == b);
            BEAST_EXPECT(ledger->stateMap().lower_bound(uint256(13)) == b);
            BEAST_EXPECT(ledger->stateMap().lower_bound(uint256(14)) == b);
            BEAST_EXPECT(ledger->stateMap().lower_bound(uint256(15)) == b);
            ++b;
            BEAST_EXPECT(ledger->stateMap().lower_bound(uint256(16)) == b);
            b = ledger->stateMap().begin();
            BEAST_EXPECT(ledger->stateMap().upper_bound(uint256(0)) == b);
            BEAST_EXPECT(ledger->stateMap().upper_bound(uint256(1)) == b);
            ++b;
            BEAST_EXPECT(ledger->stateMap().upper_bound(uint256(2)) == b);
            ++b;
            BEAST_EXPECT(ledger->stateMap().upper_bound(uint256(3)) == b);
            BEAST_EXPECT(ledger->stateMap().upper_bound(uint256(4)) == b);
            ++b;
            BEAST_EXPECT(ledger->stateMap().upper_bound(uint256(5)) == b);
            ++b;
            BEAST_EXPECT(ledger->stateMap().upper_bound(uint256(6)) == b);
            BEAST_EXPECT(ledger->stateMap().upper_bound(uint256(7)) == b);
            BEAST_EXPECT(ledger->stateMap().upper_bound(uint256(8)) == b);
            BEAST_EXPECT(ledger->stateMap().upper_bound(uint256(9)) == b);
            ++b;
            BEAST_EXPECT(ledger->stateMap().upper_bound(uint256(10)) == b);
            BEAST_EXPECT(ledger->stateMap().upper_bound(uint256(11)) == b);
            BEAST_EXPECT(ledger->stateMap().upper_bound(uint256(12)) == b);
            BEAST_EXPECT(ledger->stateMap().upper_bound(uint256(13)) == b);
            BEAST_EXPECT(ledger->stateMap().upper_bound(uint256(14)) == b);
            ++b;
            BEAST_EXPECT(ledger->stateMap().upper_bound(uint256(15)) == e);
            BEAST_EXPECT(ledger->stateMap().upper_bound(uint256(16)) == e);
        }
        {
            // some full trees, some empty trees, etc
            setup({0,  1,  2,  3,  4,  5,  6,  7,  8,  9,  10, 11, 12, 13, 14, 15, 16, 20, 25, 30,
                   32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 66, 100});
            BEAST_EXPECT(
                sles(*ledger) ==
                list(
                    0,
                    1,
                    2,
                    3,
                    4,
                    5,
                    6,
                    7,
                    8,
                    9,
                    10,
                    11,
                    12,
                    13,
                    14,
                    15,
                    16,
                    20,
                    25,
                    30,
                    32,
                    33,
                    34,
                    35,
                    36,
                    37,
                    38,
                    39,
                    40,
                    41,
                    42,
                    43,
                    44,
                    45,
                    46,
                    47,
                    48,
                    66,
                    100));
            auto b = ledger->stateMap().begin();
            auto e = ledger->stateMap().end();
            BEAST_EXPECT(ledger->stateMap().lower_bound(uint256(0)) == e);
            BEAST_EXPECT(ledger->stateMap().lower_bound(uint256(1)) == b);
            BEAST_EXPECT(ledger->stateMap().lower_bound(uint256(5))->key() == uint256(4));
            BEAST_EXPECT(ledger->stateMap().lower_bound(uint256(15))->key() == uint256(14));
            BEAST_EXPECT(ledger->stateMap().lower_bound(uint256(16))->key() == uint256(15));
            BEAST_EXPECT(ledger->stateMap().lower_bound(uint256(19))->key() == uint256(16));
            BEAST_EXPECT(ledger->stateMap().lower_bound(uint256(20))->key() == uint256(16));
            BEAST_EXPECT(ledger->stateMap().lower_bound(uint256(24))->key() == uint256(20));
            BEAST_EXPECT(ledger->stateMap().lower_bound(uint256(31))->key() == uint256(30));
            BEAST_EXPECT(ledger->stateMap().lower_bound(uint256(32))->key() == uint256(30));
            BEAST_EXPECT(ledger->stateMap().lower_bound(uint256(40))->key() == uint256(39));
            BEAST_EXPECT(ledger->stateMap().lower_bound(uint256(47))->key() == uint256(46));
            BEAST_EXPECT(ledger->stateMap().lower_bound(uint256(48))->key() == uint256(47));
            BEAST_EXPECT(ledger->stateMap().lower_bound(uint256(64))->key() == uint256(48));

            BEAST_EXPECT(ledger->stateMap().lower_bound(uint256(90))->key() == uint256(66));
            BEAST_EXPECT(ledger->stateMap().lower_bound(uint256(96))->key() == uint256(66));
            BEAST_EXPECT(ledger->stateMap().lower_bound(uint256(100))->key() == uint256(66));

            BEAST_EXPECT(ledger->stateMap().upper_bound(uint256(0))->key() == uint256(1));
            BEAST_EXPECT(ledger->stateMap().upper_bound(uint256(5))->key() == uint256(6));
            BEAST_EXPECT(ledger->stateMap().upper_bound(uint256(15))->key() == uint256(16));
            BEAST_EXPECT(ledger->stateMap().upper_bound(uint256(16))->key() == uint256(20));
            BEAST_EXPECT(ledger->stateMap().upper_bound(uint256(18))->key() == uint256(20));
            BEAST_EXPECT(ledger->stateMap().upper_bound(uint256(20))->key() == uint256(25));
            BEAST_EXPECT(ledger->stateMap().upper_bound(uint256(31))->key() == uint256(32));
            BEAST_EXPECT(ledger->stateMap().upper_bound(uint256(32))->key() == uint256(33));
            BEAST_EXPECT(ledger->stateMap().upper_bound(uint256(47))->key() == uint256(48));
            BEAST_EXPECT(ledger->stateMap().upper_bound(uint256(48))->key() == uint256(66));
            BEAST_EXPECT(ledger->stateMap().upper_bound(uint256(53))->key() == uint256(66));
            BEAST_EXPECT(ledger->stateMap().upper_bound(uint256(66))->key() == uint256(100));
            BEAST_EXPECT(ledger->stateMap().upper_bound(uint256(70))->key() == uint256(100));
            BEAST_EXPECT(ledger->stateMap().upper_bound(uint256(85))->key() == uint256(100));
            BEAST_EXPECT(ledger->stateMap().upper_bound(uint256(98))->key() == uint256(100));
            BEAST_EXPECT(ledger->stateMap().upper_bound(uint256(100)) == e);
            BEAST_EXPECT(ledger->stateMap().upper_bound(uint256(155)) == e);
        }
    }

    void
    testSles()
    {
        testcase("Sles");

        using namespace jtx;
        Env env(*this);
        Config config;
        std::shared_ptr<Ledger const> const genesis = std::make_shared<Ledger>(
            create_genesis,
            Rules{config.features},
            config.FEES.toFees(),
            std::vector<uint256>{},
            env.app().getNodeFamily());
        auto const ledger = std::make_shared<Ledger>(*genesis, env.app().timeKeeper().closeTime());
        auto setup123 = [&ledger, this]() {
            // erase middle element
            wipe(*ledger);
            ledger->rawInsert(sle(1));
            ledger->rawInsert(sle(2));
            ledger->rawInsert(sle(3));
            BEAST_EXPECT(sles(*ledger) == list(1, 2, 3));
        };
        {
            setup123();
            OpenView view(ledger.get());
            view.rawErase(sle(1));
            view.rawInsert(sle(4));
            view.rawInsert(sle(5));
            BEAST_EXPECT(sles(view) == list(2, 3, 4, 5));
            auto b = view.sles.begin();
            BEAST_EXPECT(view.sles.upper_bound(uint256(1)) == b);
            ++b;
            BEAST_EXPECT(view.sles.upper_bound(uint256(2)) == b);
            ++b;
            BEAST_EXPECT(view.sles.upper_bound(uint256(3)) == b);
            ++b;
            BEAST_EXPECT(view.sles.upper_bound(uint256(4)) == b);
            ++b;
            BEAST_EXPECT(view.sles.upper_bound(uint256(5)) == b);
        }
        {
            setup123();
            OpenView view(ledger.get());
            view.rawErase(sle(1));
            view.rawErase(sle(2));
            view.rawInsert(sle(4));
            view.rawInsert(sle(5));
            BEAST_EXPECT(sles(view) == list(3, 4, 5));
            auto b = view.sles.begin();
            BEAST_EXPECT(view.sles.upper_bound(uint256(1)) == b);
            BEAST_EXPECT(view.sles.upper_bound(uint256(2)) == b);
            ++b;
            BEAST_EXPECT(view.sles.upper_bound(uint256(3)) == b);
            ++b;
            BEAST_EXPECT(view.sles.upper_bound(uint256(4)) == b);
            ++b;
            BEAST_EXPECT(view.sles.upper_bound(uint256(5)) == b);
        }
        {
            setup123();
            OpenView view(ledger.get());
            view.rawErase(sle(1));
            view.rawErase(sle(2));
            view.rawErase(sle(3));
            view.rawInsert(sle(4));
            view.rawInsert(sle(5));
            BEAST_EXPECT(sles(view) == list(4, 5));
            auto b = view.sles.begin();
            BEAST_EXPECT(view.sles.upper_bound(uint256(1)) == b);
            BEAST_EXPECT(view.sles.upper_bound(uint256(2)) == b);
            BEAST_EXPECT(view.sles.upper_bound(uint256(3)) == b);
            ++b;
            BEAST_EXPECT(view.sles.upper_bound(uint256(4)) == b);
            ++b;
            BEAST_EXPECT(view.sles.upper_bound(uint256(5)) == b);
        }
        {
            setup123();
            OpenView view(ledger.get());
            view.rawErase(sle(3));
            view.rawInsert(sle(4));
            view.rawInsert(sle(5));
            BEAST_EXPECT(sles(view) == list(1, 2, 4, 5));
            auto b = view.sles.begin();
            ++b;
            BEAST_EXPECT(view.sles.upper_bound(uint256(1)) == b);
            ++b;
            BEAST_EXPECT(view.sles.upper_bound(uint256(2)) == b);
            BEAST_EXPECT(view.sles.upper_bound(uint256(3)) == b);
            ++b;
            BEAST_EXPECT(view.sles.upper_bound(uint256(4)) == b);
            ++b;
            BEAST_EXPECT(view.sles.upper_bound(uint256(5)) == b);
        }
        {
            setup123();
            OpenView view(ledger.get());
            view.rawReplace(sle(1, 10));
            view.rawReplace(sle(3, 30));
            BEAST_EXPECT(sles(view) == list(1, 2, 3));
            BEAST_EXPECT(seq(view.read(k(1))) == 10);
            BEAST_EXPECT(seq(view.read(k(2))) == 1);
            BEAST_EXPECT(seq(view.read(k(3))) == 30);

            view.rawErase(sle(3));
            BEAST_EXPECT(sles(view) == list(1, 2));
            auto b = view.sles.begin();
            ++b;
            BEAST_EXPECT(view.sles.upper_bound(uint256(1)) == b);
            ++b;
            BEAST_EXPECT(view.sles.upper_bound(uint256(2)) == b);
            BEAST_EXPECT(view.sles.upper_bound(uint256(3)) == b);
            BEAST_EXPECT(view.sles.upper_bound(uint256(4)) == b);
            BEAST_EXPECT(view.sles.upper_bound(uint256(5)) == b);

            view.rawInsert(sle(5));
            view.rawInsert(sle(4));
            view.rawInsert(sle(3));
            BEAST_EXPECT(sles(view) == list(1, 2, 3, 4, 5));
            b = view.sles.begin();
            ++b;
            BEAST_EXPECT(view.sles.upper_bound(uint256(1)) == b);
            ++b;
            BEAST_EXPECT(view.sles.upper_bound(uint256(2)) == b);
            ++b;
            BEAST_EXPECT(view.sles.upper_bound(uint256(3)) == b);
            ++b;
            BEAST_EXPECT(view.sles.upper_bound(uint256(4)) == b);
            ++b;
            BEAST_EXPECT(view.sles.upper_bound(uint256(5)) == b);
        }
    }

    void
    testFlags()
    {
        testcase("Flags");

        using namespace jtx;
        Env env(*this);

        auto const alice = Account("alice");
        auto const bob = Account("bob");
        auto const carol = Account("carol");
        auto const gw = Account("gateway");
        auto const USD = gw["USD"];
        auto const EUR = gw["EUR"];

        env.fund(XRP(10000), alice, bob, carol, gw);
        env.close();
        env.trust(USD(100), alice, bob, carol);
        {
            // Global freezing.
            env(pay(gw, alice, USD(50)));
            env(offer(alice, XRP(5), USD(5)));

            // Now freeze gw.
            env(fset(gw, asfGlobalFreeze));
            env.close();
            env(offer(alice, XRP(4), USD(5)), ter(tecFROZEN));
            env.close();

            // Alice's USD balance should be zero if frozen.
            BEAST_EXPECT(
                USD(0) ==
                accountHolds(
                    *env.closed(), alice, USD.currency, gw, fhZERO_IF_FROZEN, env.journal));

            // Thaw gw and try again.
            env(fclear(gw, asfGlobalFreeze));
            env.close();
            env(offer("alice", XRP(4), USD(5)));
        }
        {
            // Local freezing.
            env(pay(gw, bob, USD(50)));
            env.close();

            // Now gw freezes bob's USD trust line.
            env(trust(gw, USD(100), bob, tfSetFreeze));
            env.close();

            // Bob's balance should be zero if frozen.
            BEAST_EXPECT(
                USD(0) ==
                accountHolds(*env.closed(), bob, USD.currency, gw, fhZERO_IF_FROZEN, env.journal));

            // gw thaws bob's trust line.  bob gets his money back.
            env(trust(gw, USD(100), bob, tfClearFreeze));
            env.close();
            BEAST_EXPECT(
                USD(50) ==
                accountHolds(*env.closed(), bob, USD.currency, gw, fhZERO_IF_FROZEN, env.journal));
        }
        {
            // accountHolds().
            env(pay(gw, carol, USD(50)));
            env.close();

            // carol has no EUR.
            BEAST_EXPECT(
                EUR(0) ==
                accountHolds(
                    *env.closed(), carol, EUR.currency, gw, fhZERO_IF_FROZEN, env.journal));

            // But carol does have USD.
            BEAST_EXPECT(
                USD(50) ==
                accountHolds(
                    *env.closed(), carol, USD.currency, gw, fhZERO_IF_FROZEN, env.journal));

            // carol's XRP balance should be her holdings minus her reserve.
            auto const carolsXRP = accountHolds(
                *env.closed(), carol, xrpCurrency(), xrpAccount(), fhZERO_IF_FROZEN, env.journal);
            // carol's XRP balance:              10000
            // base reserve:                      -200
            // 1 trust line times its reserve: 1 * -50
            //                                 -------
            // carol's available balance:         9750
            BEAST_EXPECT(carolsXRP == XRP(9750));

            // carol should be able to spend *more* than her XRP balance on
            // a fee by eating into her reserve.
            env(noop(carol), fee(carolsXRP + XRP(10)));
            env.close();

            // carol's XRP balance should now show as zero.
            BEAST_EXPECT(
                XRP(0) ==
                accountHolds(
                    *env.closed(), carol, xrpCurrency(), gw, fhZERO_IF_FROZEN, env.journal));
        }
        {
            // accountFunds().
            // Gateways have whatever funds they claim to have.
            auto const gwUSD =
                accountFunds(*env.closed(), gw, USD(314159), fhZERO_IF_FROZEN, env.journal);
            BEAST_EXPECT(gwUSD == USD(314159));

            // carol has funds from the gateway.
            auto carolsUSD =
                accountFunds(*env.closed(), carol, USD(0), fhZERO_IF_FROZEN, env.journal);
            BEAST_EXPECT(carolsUSD == USD(50));

            // If carol's funds are frozen she has no funds...
            env(fset(gw, asfGlobalFreeze));
            env.close();
            carolsUSD = accountFunds(*env.closed(), carol, USD(0), fhZERO_IF_FROZEN, env.journal);
            BEAST_EXPECT(carolsUSD == USD(0));

            // ... unless the query ignores the FROZEN state.
            carolsUSD = accountFunds(*env.closed(), carol, USD(0), fhIGNORE_FREEZE, env.journal);
            BEAST_EXPECT(carolsUSD == USD(50));

            // Just to be tidy, thaw gw.
            env(fclear(gw, asfGlobalFreeze));
            env.close();
        }
    }

    void
    testTransferRate()
    {
        testcase("Transfer rate");

        using namespace jtx;
        Env env(*this);

        auto const gw1 = Account("gw1");

        env.fund(XRP(10000), gw1);
        env.close();

        auto rdView = env.closed();
        // Test with no rate set on gw1.
        BEAST_EXPECT(transferRate(*rdView, gw1) == parityRate);

        env(rate(gw1, 1.02));
        env.close();

        rdView = env.closed();
        BEAST_EXPECT(transferRate(*rdView, gw1) == Rate{1020000000});
    }

    void
    testAreCompatible()
    {
        // This test requires incompatible ledgers.  The good news we can
        // construct and manage two different Env instances at the same
        // time.  So we can use two Env instances to produce mutually
        // incompatible ledgers.
        testcase("Are compatible");

        using namespace jtx;
        auto const alice = Account("alice");
        auto const bob = Account("bob");

        // The first Env.
        Env eA(*this, envconfig(), nullptr, beast::severities::kDisabled);

        eA.fund(XRP(10000), alice);
        eA.close();
        auto const rdViewA3 = eA.closed();

        eA.fund(XRP(10000), bob);
        eA.close();
        auto const rdViewA4 = eA.closed();

        // The two Env's can't share the same ports, so modify the config
        // of the second Env to use higher port numbers
        Env eB{*this, envconfig(), nullptr, beast::severities::kDisabled};

        // Make ledgers that are incompatible with the first ledgers.  Note
        // that bob is funded before alice.
        eB.fund(XRP(10000), bob);
        eB.close();
        auto const rdViewB3 = eB.closed();

        eB.fund(XRP(10000), alice);
        eB.close();
        auto const rdViewB4 = eB.closed();

        // Check for compatibility.
        auto jStream = eA.journal.error();
        BEAST_EXPECT(areCompatible(*rdViewA3, *rdViewA4, jStream, ""));
        BEAST_EXPECT(areCompatible(*rdViewA4, *rdViewA3, jStream, ""));
        BEAST_EXPECT(areCompatible(*rdViewA4, *rdViewA4, jStream, ""));
        BEAST_EXPECT(!areCompatible(*rdViewA3, *rdViewB4, jStream, ""));
        BEAST_EXPECT(!areCompatible(*rdViewA4, *rdViewB3, jStream, ""));
        BEAST_EXPECT(!areCompatible(*rdViewA4, *rdViewB4, jStream, ""));

        // Try the other interface.
        // Note that the different interface has different outcomes.
        auto const& iA3 = rdViewA3->header();
        auto const& iA4 = rdViewA4->header();

        BEAST_EXPECT(areCompatible(iA3.hash, iA3.seq, *rdViewA4, jStream, ""));
        BEAST_EXPECT(areCompatible(iA4.hash, iA4.seq, *rdViewA3, jStream, ""));
        BEAST_EXPECT(areCompatible(iA4.hash, iA4.seq, *rdViewA4, jStream, ""));
        BEAST_EXPECT(!areCompatible(iA3.hash, iA3.seq, *rdViewB4, jStream, ""));
        BEAST_EXPECT(areCompatible(iA4.hash, iA4.seq, *rdViewB3, jStream, ""));
        BEAST_EXPECT(!areCompatible(iA4.hash, iA4.seq, *rdViewB4, jStream, ""));
    }

    void
    testRegressions()
    {
        testcase("Regressions");

        using namespace jtx;

        // Create a ledger with 1 item, put a
        // ApplyView on that, then another ApplyView,
        // erase the item, apply.
        {
            Env env(*this);
            Config config;
            std::shared_ptr<Ledger const> const genesis = std::make_shared<Ledger>(
                create_genesis,
                Rules{config.features},
                config.FEES.toFees(),
                std::vector<uint256>{},
                env.app().getNodeFamily());
            auto const ledger =
                std::make_shared<Ledger>(*genesis, env.app().timeKeeper().closeTime());
            wipe(*ledger);
            ledger->rawInsert(sle(1));
            ReadView& v0 = *ledger;
            ApplyViewImpl v1(&v0, tapNONE);
            {
                Sandbox v2(&v1);
                v2.erase(v2.peek(k(1)));
                v2.apply(v1);
            }
            BEAST_EXPECT(!v1.exists(k(1)));
        }

        // Make sure OpenLedger::empty works
        {
            Env env(*this);
            BEAST_EXPECT(env.app().openLedger().empty());
            env.fund(XRP(10000), Account("test"));
            BEAST_EXPECT(!env.app().openLedger().empty());
        }
    }

    void
    run() override
    {
        // This had better work, or else
        BEAST_EXPECT(k(0).key < k(1).key);

        testLedger();
        testMeta();
        testMetaSucc();
        testStacked();
        testContext();
        testSles();
        testUpperAndLowerBound();
        testFlags();
        testTransferRate();
        testAreCompatible();
        testRegressions();
    }
};

class GetAmendments_test : public beast::unit_test::suite
{
    void
    testGetAmendments()
    {
        using namespace jtx;
        Env env{*this, envconfig(validator, "")};

        // Start out with no amendments.
        auto majorities = getMajorityAmendments(*env.closed());
        BEAST_EXPECT(majorities.empty());

        // Now close ledgers until the amendments show up.
        int i = 0;
        for (i = 0; i <= 256; ++i)
        {
            env.close();
            majorities = getMajorityAmendments(*env.closed());
            if (!majorities.empty())
                break;
        }

        // There should be at least 3 amendments.  Don't do exact comparison
        // to avoid maintenance as more amendments are added in the future.
        BEAST_EXPECT(i == 254);
        BEAST_EXPECT(majorities.size() >= 2);

        // None of the amendments should be enabled yet.
        auto enabledAmendments = getEnabledAmendments(*env.closed());
        BEAST_EXPECT(enabledAmendments.empty());

        // Now wait 2 weeks modulo 256 ledgers for the amendments to be
        // enabled.  Speed the process by closing ledgers every 80 minutes,
        // which should get us to just past 2 weeks after 256 ledgers.
        for (i = 0; i <= 256; ++i)
        {
            using namespace std::chrono_literals;
            env.close(80min);
            enabledAmendments = getEnabledAmendments(*env.closed());
            if (!enabledAmendments.empty())
                break;
        }
        BEAST_EXPECT(i == 255);
        BEAST_EXPECT(enabledAmendments.size() >= 2);
    }

    void
    run() override
    {
        testGetAmendments();
    }
};

BEAST_DEFINE_TESTSUITE(View, ledger, xrpl);
BEAST_DEFINE_TESTSUITE(GetAmendments, ledger, xrpl);

}  // namespace test
}  // namespace xrpl
