#pragma once

#include <xrpl/beast/utility/Journal.h>
#include <xrpl/core/ServiceRegistry.h>
#include <xrpl/ledger/ApplyViewImpl.h>
#include <xrpl/protocol/STTx.h>
#include <xrpl/protocol/XRPAmount.h>

#include <optional>

namespace xrpl {

/** State information when applying a tx. */
class ApplyContext
{
public:
    explicit ApplyContext(
        ServiceRegistry& registry,
        OpenView& base,
        std::optional<uint256 const> const& parentBatchId,
        STTx const& tx,
        TER preclaimResult,
        XRPAmount baseFee,
        ApplyFlags flags,
        beast::Journal journal = beast::Journal{beast::Journal::getNullSink()});

    explicit ApplyContext(
        ServiceRegistry& registry,
        OpenView& base,
        STTx const& tx,
        TER preclaimResult,
        XRPAmount baseFee,
        ApplyFlags flags,
        beast::Journal journal = beast::Journal{beast::Journal::getNullSink()})
        : ApplyContext(registry, base, std::nullopt, tx, preclaimResult, baseFee, flags, journal)
    {
        XRPL_ASSERT((flags & tapBATCH) == 0, "Batch apply flag should not be set");
    }

    ServiceRegistry& registry;
    STTx const& tx;
    TER const preclaimResult;
    XRPAmount const baseFee;
    beast::Journal const journal;

    ApplyView&
    view()
    {
        return *view_;
    }

    ApplyView const&
    view() const
    {
        return *view_;
    }

    // VFALCO Unfortunately this is necessary
    RawView&
    rawView()
    {
        return *view_;
    }

    ApplyFlags const&
    flags() const
    {
        return flags_;
    }

    /** Sets the DeliveredAmount field in the metadata */
    void
    deliver(STAmount const& amount)
    {
        view_->deliver(amount);
    }

    /** Discard changes and start fresh. */
    void
    discard();

    /** Apply the transaction result to the base. */
    std::optional<TxMeta> apply(TER);

    /** Get the number of unapplied changes. */
    std::size_t
    size();

    /** Visit unapplied changes. */
    void
    visit(
        std::function<void(
            uint256 const& key,
            bool isDelete,
            std::shared_ptr<SLE const> const& before,
            std::shared_ptr<SLE const> const& after)> const& func);

    void
    destroyXRP(XRPAmount const& fee)
    {
        view_->rawDestroyXRP(fee);
    }

    /** Applies all invariant checkers one by one.

        @param result the result generated by processing this transaction.
        @param fee the fee charged for this transaction
        @return the result code that should be returned for this transaction.
     */
    TER
    checkInvariants(TER const result, XRPAmount const fee);

private:
    static TER
    failInvariantCheck(TER const result);

    template <std::size_t... Is>
    TER
    checkInvariantsHelper(TER const result, XRPAmount const fee, std::index_sequence<Is...>);

    OpenView& base_;
    ApplyFlags flags_;
    std::optional<ApplyViewImpl> view_;

    // The ID of the batch transaction we are executing under, if seated.
    std::optional<uint256 const> parentBatchId_;
};

}  // namespace xrpl
