#include <test/jtx/directory.h>

#include <xrpl/ledger/Sandbox.h>

namespace xrpl::test::jtx {

/** Directory operations. */
namespace directory {

auto
bumpLastPage(
    Env& env,
    std::uint64_t newLastPage,
    Keylet directory,
    std::function<bool(ApplyView&, uint256, std::uint64_t)> adjust) -> Expected<void, Error>
{
    Expected<void, Error> res{};
    env.app().openLedger().modify([&](OpenView& view, beast::Journal j) -> bool {
        Sandbox sb(&view, tapNONE);

        // Find the root page
        auto sleRoot = sb.peek(directory);
        if (!sleRoot)
        {
            res = Unexpected<Error>(DirectoryRootNotFound);
            return false;
        }

        // Find last page
        auto const lastIndex = sleRoot->getFieldU64(sfIndexPrevious);
        if (lastIndex == 0)
        {
            res = Unexpected<Error>(DirectoryTooSmall);
            return false;
        }

        if (sb.exists(keylet::page(directory, newLastPage)))
        {
            res = Unexpected<Error>(DirectoryPageDuplicate);
            return false;
        }

        if (lastIndex >= newLastPage)
        {
            res = Unexpected<Error>(InvalidLastPage);
            return false;
        }

        auto slePage = sb.peek(keylet::page(directory, lastIndex));
        if (!slePage)
        {
            res = Unexpected<Error>(DirectoryPageNotFound);
            return false;
        }

        // Copy its data and delete the page
        auto indexes = slePage->getFieldV256(sfIndexes);
        auto prevIndex = slePage->at(~sfIndexPrevious);
        auto owner = slePage->at(~sfOwner);
        sb.erase(slePage);

        // Create new page to replace slePage
        auto sleNew = std::make_shared<SLE>(keylet::page(directory, newLastPage));
        sleNew->setFieldH256(sfRootIndex, directory.key);
        sleNew->setFieldV256(sfIndexes, indexes);
        if (owner)
            sleNew->setAccountID(sfOwner, *owner);
        if (prevIndex)
            sleNew->setFieldU64(sfIndexPrevious, *prevIndex);
        sb.insert(sleNew);

        // Adjust root previous and previous node's next
        sleRoot->setFieldU64(sfIndexPrevious, newLastPage);
        if (prevIndex.value_or(0) == 0)
        {
            sleRoot->setFieldU64(sfIndexNext, newLastPage);
        }
        else
        {
            auto slePrev = sb.peek(keylet::page(directory, *prevIndex));
            if (!slePrev)
            {
                res = Unexpected<Error>(DirectoryPageNotFound);
                return false;
            }
            slePrev->setFieldU64(sfIndexNext, newLastPage);
            sb.update(slePrev);
        }
        sb.update(sleRoot);

        // Fixup page numbers in the objects referred by indexes
        if (adjust)
        {
            for (auto const key : indexes)
            {
                if (!adjust(sb, key, newLastPage))
                {
                    res = Unexpected<Error>(AdjustmentError);
                    return false;
                }
            }
        }

        sb.apply(view);
        return true;
    });

    return res;
}

bool
adjustOwnerNode(ApplyView& view, uint256 key, std::uint64_t page)
{
    auto sle = view.peek({ltANY, key});
    if (sle && sle->isFieldPresent(sfOwnerNode))
    {
        sle->setFieldU64(sfOwnerNode, page);
        view.update(sle);
        return true;
    }

    return false;
}

}  // namespace directory

}  // namespace xrpl::test::jtx
