#include <xrpl/protocol/Rules.h>
// Do not remove. Forces Rules.h to stay first, to verify it can compile
// without any hidden dependencies
#include <xrpl/basics/LocalValue.h>
#include <xrpl/basics/Number.h>
#include <xrpl/basics/base_uint.h>
#include <xrpl/basics/hardened_hash.h>
#include <xrpl/beast/hash/uhash.h>
#include <xrpl/beast/utility/instrumentation.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/STVector256.h>

#include <memory>
#include <optional>
#include <unordered_set>
#include <utility>

namespace xrpl {

namespace {
// Use a static inside a function to help prevent order-of-initialization issues
LocalValue<std::optional<Rules>>&
getCurrentTransactionRulesRef()
{
    static LocalValue<std::optional<Rules>> r;
    return r;
}
}  // namespace

std::optional<Rules> const&
getCurrentTransactionRules()
{
    return *getCurrentTransactionRulesRef();
}

void
setCurrentTransactionRules(std::optional<Rules> r)
{
    // Make global changes associated with the rules before the value is moved.
    // Push the appropriate setting, instead of having the class pull every time
    // the value is needed. That could get expensive fast.
    bool enableLargeNumbers =
        !r || (r->enabled(featureSingleAssetVault) || r->enabled(featureLendingProtocol));
    Number::setMantissaScale(enableLargeNumbers ? MantissaRange::large : MantissaRange::small);

    *getCurrentTransactionRulesRef() = std::move(r);
}

class Rules::Impl
{
private:
    std::unordered_set<uint256, hardened_hash<>> set_;
    std::optional<uint256> digest_;
    std::unordered_set<uint256, beast::uhash<>> const& presets_;

public:
    explicit Impl(std::unordered_set<uint256, beast::uhash<>> const& presets) : presets_(presets)
    {
    }

    Impl(
        std::unordered_set<uint256, beast::uhash<>> const& presets,
        std::optional<uint256> const& digest,
        STVector256 const& amendments)
        : digest_(digest), presets_(presets)
    {
        set_.reserve(amendments.size());
        set_.insert(amendments.begin(), amendments.end());
    }

    std::unordered_set<uint256, beast::uhash<>> const&
    presets() const
    {
        return presets_;
    }

    bool
    enabled(uint256 const& feature) const
    {
        if (presets_.contains(feature))
            return true;
        return set_.contains(feature);
    }

    bool
    operator==(Impl const& other) const
    {
        if (!digest_ && !other.digest_)
            return true;
        if (!digest_ || !other.digest_)
            return false;
        XRPL_ASSERT(
            presets_ == other.presets_,
            "xrpl::Rules::Impl::operator==(Impl) const : input presets do "
            "match");
        return *digest_ == *other.digest_;
    }
};

Rules::Rules(std::unordered_set<uint256, beast::uhash<>> const& presets)
    : impl_(std::make_shared<Impl>(presets))
{
}

Rules::Rules(
    std::unordered_set<uint256, beast::uhash<>> const& presets,
    std::optional<uint256> const& digest,
    STVector256 const& amendments)
    : impl_(std::make_shared<Impl>(presets, digest, amendments))
{
}

std::unordered_set<uint256, beast::uhash<>> const&
Rules::presets() const
{
    return impl_->presets();
}

bool
Rules::enabled(uint256 const& feature) const
{
    XRPL_ASSERT(impl_, "xrpl::Rules::enabled : initialized");

    return impl_->enabled(feature);
}

bool
Rules::operator==(Rules const& other) const
{
    XRPL_ASSERT(impl_ && other.impl_, "xrpl::Rules::operator==(Rules) const : both initialized");
    if (impl_.get() == other.impl_.get())
        return true;
    return *impl_ == *other.impl_;
}

bool
Rules::operator!=(Rules const& other) const
{
    return !(*this == other);
}

bool
isFeatureEnabled(uint256 const& feature)
{
    auto const& rules = getCurrentTransactionRules();
    return rules && rules->enabled(feature);
}

}  // namespace xrpl
