#pragma once

#include <xrpl/basics/base_uint.h>

#include <boost/container/flat_map.hpp>

#include <bitset>
#include <map>
#include <optional>
#include <string>

/**
 * @page Feature How to add new features
 *
 * Steps required to add new features to the code:
 *
 * 1) Add the appropriate XRPL_FEATURE or XRPL_FIX macro definition for the
 *    feature to features.macro with the feature's name, `Supported::no`, and
 *    `VoteBehavior::DefaultNo`.
 *
 * 2) Use the generated variable name as the parameter to `view.rules.enabled()`
 *    to control flow into new code that this feature limits. (featureName or
 *    fixName)
 *
 * 3) If the feature development is COMPLETE, and the feature is ready to be
 *    SUPPORTED, change the macro parameter in features.macro to Supported::yes.
 *
 * 4) In general, any newly supported amendments (`Supported::yes`) should have
 *    a `VoteBehavior::DefaultNo` indefinitely so that external governance can
 *    make the decision on when to activate it. High priority bug fixes can be
 *    an exception to this rule. In such cases, ensure the fix has been
 *    clearly communicated to the community using appropriate channels,
 *    then change the macro parameter in features.macro to
 *    `VoteBehavior::DefaultYes`. The communication process is beyond
 *    the scope of these instructions.

 * 5) If a supported feature (`Supported::yes`) was _ever_ in a released
 *     version, it can never be changed back to `Supported::no`, because
 *     it _may_ still become enabled at any time. This would cause newer
 *     versions of `rippled` to become amendment blocked.
 *     Instead, to prevent newer versions from voting on the feature, use
 *     `VoteBehavior::Obsolete`. Obsolete features can not be voted for
 *     by any versions of `rippled` built with that setting, but will still
 *     work correctly if they get enabled. If a feature remains obsolete
 *     for long enough that _all_ clients that could vote for it are
 *     amendment blocked, the feature can be removed from the code
 *     as if it was unsupported.
 *
 *
 * When a feature has been enabled for several years, the conditional code
 * may be removed, and the feature "retired". To retire a feature:
 *
 * 1) MOVE the macro definition in features.macro to the "retired features"
 *    section at the end of the file, and change the macro to XRPL_RETIRE.
 *
 * The feature must remain registered and supported indefinitely because it
 * may exist in the Amendments object on ledger. There is no need to vote
 * for it because there's nothing to vote for. If the feature definition is
 * removed completely from the code, any instances running that code will get
 * amendment blocked. Removing the feature from the ledger is beyond the scope
 * of these instructions.
 *
 */

namespace xrpl {

// Feature names must not exceed this length (in characters, excluding the null terminator).
static constexpr std::size_t maxFeatureNameSize = 63;
// Reserve this exact feature-name length (in characters/bytes, excluding the null terminator)
// so that a 32-byte uint256 (for example, in WASM or other interop contexts) can be used
// as a compact, fixed-size feature selector without conflicting with human-readable names.
static constexpr std::size_t reservedFeatureNameSize = 32;

// Both validFeatureNameSize and validFeatureName are consteval functions that can be used in
// static_asserts to validate feature names at compile time. They are only used inside
// enforceValidFeatureName in Feature.cpp, but are exposed here for testing. The expected
// parameter `auto fn` is a constexpr lambda which returns a const char*, making it available
// for compile-time evaluation. Read more in https://accu.org/journals/overload/30/172/wu/
consteval auto
validFeatureNameSize(auto fn) -> bool
{
    constexpr char const* n = fn();
    // Note, std::strlen is not constexpr, we need to implement our own here.
    constexpr std::size_t N = [](auto n) {
        std::size_t ret = 0;
        for (auto ptr = n; *ptr != '\0'; ret++, ++ptr)
            ;
        return ret;
    }(n);
    return N != reservedFeatureNameSize &&  //
        N <= maxFeatureNameSize;
}

consteval auto
validFeatureName(auto fn) -> bool
{
    constexpr char const* n = fn();
    // Prevent the use of visually confusable characters and enforce that feature names
    // are always valid ASCII. This is needed because C++ allows Unicode identifiers.
    // Characters below 0x20 are nonprintable control characters, and characters with the 0x80 bit
    // set are non-ASCII (e.g. UTF-8 encoding of Unicode), so both are disallowed.
    for (auto ptr = n; *ptr != '\0'; ++ptr)
    {
        if (*ptr & 0x80 || *ptr < 0x20)
            return false;
    }
    return true;
}

enum class VoteBehavior : int { Obsolete = -1, DefaultNo = 0, DefaultYes };
enum class AmendmentSupport : int { Retired = -1, Supported = 0, Unsupported };

/** All amendments libxrpl knows about. */
std::map<std::string, AmendmentSupport> const&
allAmendments();

namespace detail {

#pragma push_macro("XRPL_FEATURE")
#undef XRPL_FEATURE
#pragma push_macro("XRPL_FIX")
#undef XRPL_FIX
#pragma push_macro("XRPL_RETIRE_FEATURE")
#undef XRPL_RETIRE_FEATURE
#pragma push_macro("XRPL_RETIRE_FIX")
#undef XRPL_RETIRE_FIX

#define XRPL_FEATURE(name, supported, vote) +1
#define XRPL_FIX(name, supported, vote) +1
#define XRPL_RETIRE_FEATURE(name) +1
#define XRPL_RETIRE_FIX(name) +1

// This value SHOULD be equal to the number of amendments registered in
// Feature.cpp. Because it's only used to reserve storage, and determine how
// large to make the FeatureBitset, it MAY be larger. It MUST NOT be less than
// the actual number of amendments. A LogicError on startup will verify this.
static constexpr std::size_t numFeatures =
    (0 +
#include <xrpl/protocol/detail/features.macro>
    );

#undef XRPL_RETIRE_FEATURE
#pragma pop_macro("XRPL_RETIRE_FEATURE")
#undef XRPL_RETIRE_FIX
#pragma pop_macro("XRPL_RETIRE_FIX")
#undef XRPL_FIX
#pragma pop_macro("XRPL_FIX")
#undef XRPL_FEATURE
#pragma pop_macro("XRPL_FEATURE")

/** Amendments that this server supports and the default voting behavior.
   Whether they are enabled depends on the Rules defined in the validated
   ledger */
std::map<std::string, VoteBehavior> const&
supportedAmendments();

/** Amendments that this server won't vote for by default.

    This function is only used in unit tests.
*/
std::size_t
numDownVotedAmendments();

/** Amendments that this server will vote for by default.

    This function is only used in unit tests.
*/
std::size_t
numUpVotedAmendments();

}  // namespace detail

std::optional<uint256>
getRegisteredFeature(std::string const& name);

size_t
featureToBitsetIndex(uint256 const& f);

uint256
bitsetIndexToFeature(size_t i);

std::string
featureToName(uint256 const& f);

class FeatureBitset : private std::bitset<detail::numFeatures>
{
    using base = std::bitset<detail::numFeatures>;

    template <class... Fs>
    void
    initFromFeatures(uint256 const& f, Fs&&... fs)
    {
        set(f);
        if constexpr (sizeof...(fs) > 0)
            initFromFeatures(std::forward<Fs>(fs)...);
    }

public:
    using base::bitset;
    using base::operator==;

    using base::all;
    using base::any;
    using base::count;
    using base::flip;
    using base::none;
    using base::reset;
    using base::set;
    using base::size;
    using base::test;
    using base::operator[];
    using base::to_string;
    using base::to_ullong;
    using base::to_ulong;

    FeatureBitset() = default;

    explicit FeatureBitset(base const& b) : base(b)
    {
        XRPL_ASSERT(b.count() == count(), "xrpl::FeatureBitset::FeatureBitset(base) : count match");
    }

    template <class... Fs>
    explicit FeatureBitset(uint256 const& f, Fs&&... fs)
    {
        initFromFeatures(f, std::forward<Fs>(fs)...);
        XRPL_ASSERT(
            count() == (sizeof...(fs) + 1),
            "xrpl::FeatureBitset::FeatureBitset(uint256) : count and "
            "sizeof... do match");
    }

    template <class Col>
    explicit FeatureBitset(Col const& fs)
    {
        for (auto const& f : fs)
            set(featureToBitsetIndex(f));
        XRPL_ASSERT(
            fs.size() == count(),
            "xrpl::FeatureBitset::FeatureBitset(Container auto) : count and "
            "size do match");
    }

    auto
    operator[](uint256 const& f)
    {
        return base::operator[](featureToBitsetIndex(f));
    }

    auto
    operator[](uint256 const& f) const
    {
        return base::operator[](featureToBitsetIndex(f));
    }

    FeatureBitset&
    set(uint256 const& f, bool value = true)
    {
        base::set(featureToBitsetIndex(f), value);
        return *this;
    }

    FeatureBitset&
    reset(uint256 const& f)
    {
        base::reset(featureToBitsetIndex(f));
        return *this;
    }

    FeatureBitset&
    flip(uint256 const& f)
    {
        base::flip(featureToBitsetIndex(f));
        return *this;
    }

    FeatureBitset&
    operator&=(FeatureBitset const& rhs)
    {
        base::operator&=(rhs);
        return *this;
    }

    FeatureBitset&
    operator|=(FeatureBitset const& rhs)
    {
        base::operator|=(rhs);
        return *this;
    }

    FeatureBitset
    operator~() const
    {
        return FeatureBitset{base::operator~()};
    }

    friend FeatureBitset
    operator&(FeatureBitset const& lhs, FeatureBitset const& rhs)
    {
        return FeatureBitset{static_cast<base const&>(lhs) & static_cast<base const&>(rhs)};
    }

    friend FeatureBitset
    operator&(FeatureBitset const& lhs, uint256 const& rhs)
    {
        return lhs & FeatureBitset{rhs};
    }

    friend FeatureBitset
    operator&(uint256 const& lhs, FeatureBitset const& rhs)
    {
        return FeatureBitset{lhs} & rhs;
    }

    friend FeatureBitset
    operator|(FeatureBitset const& lhs, FeatureBitset const& rhs)
    {
        return FeatureBitset{static_cast<base const&>(lhs) | static_cast<base const&>(rhs)};
    }

    friend FeatureBitset
    operator|(FeatureBitset const& lhs, uint256 const& rhs)
    {
        return lhs | FeatureBitset{rhs};
    }

    friend FeatureBitset
    operator|(uint256 const& lhs, FeatureBitset const& rhs)
    {
        return FeatureBitset{lhs} | rhs;
    }

    friend FeatureBitset
    operator^(FeatureBitset const& lhs, FeatureBitset const& rhs)
    {
        return FeatureBitset{static_cast<base const&>(lhs) ^ static_cast<base const&>(rhs)};
    }

    friend FeatureBitset
    operator^(FeatureBitset const& lhs, uint256 const& rhs)
    {
        return lhs ^ FeatureBitset{rhs};
    }

    friend FeatureBitset
    operator^(uint256 const& lhs, FeatureBitset const& rhs)
    {
        return FeatureBitset{lhs} ^ rhs;
    }

    // set difference
    friend FeatureBitset
    operator-(FeatureBitset const& lhs, FeatureBitset const& rhs)
    {
        return lhs & ~rhs;
    }

    friend FeatureBitset
    operator-(FeatureBitset const& lhs, uint256 const& rhs)
    {
        return lhs - FeatureBitset{rhs};
    }

    friend FeatureBitset
    operator-(uint256 const& lhs, FeatureBitset const& rhs)
    {
        return FeatureBitset{lhs} - rhs;
    }
};

template <class F>
void
foreachFeature(FeatureBitset bs, F&& f)
{
    for (size_t i = 0; i < bs.size(); ++i)
        if (bs[i])
            f(bitsetIndexToFeature(i));
}

#pragma push_macro("XRPL_FEATURE")
#undef XRPL_FEATURE
#pragma push_macro("XRPL_FIX")
#undef XRPL_FIX
#pragma push_macro("XRPL_RETIRE_FEATURE")
#undef XRPL_RETIRE_FEATURE
#pragma push_macro("XRPL_RETIRE_FIX")
#undef XRPL_RETIRE_FIX

#define XRPL_FEATURE(name, supported, vote) extern uint256 const feature##name;
#define XRPL_FIX(name, supported, vote) extern uint256 const fix##name;
#define XRPL_RETIRE_FEATURE(name)
#define XRPL_RETIRE_FIX(name)

#include <xrpl/protocol/detail/features.macro>

#undef XRPL_RETIRE_FEATURE
#pragma pop_macro("XRPL_RETIRE_FEATURE")
#undef XRPL_RETIRE_FIX
#pragma pop_macro("XRPL_RETIRE_FIX")
#undef XRPL_FIX
#pragma pop_macro("XRPL_FIX")
#undef XRPL_FEATURE
#pragma pop_macro("XRPL_FEATURE")

}  // namespace xrpl
