/*
 * Copyright (c) 2022-2024 Philippe Proulx <pproulx@efficios.com>
 *
 * SPDX-License-Identifier: MIT
 */

#include <string>
#include <unordered_set>

#include "common/assert.h"
#include "cpp-common/bt2c/contains.hpp"
#include "cpp-common/bt2c/exc.hpp"
#include "cpp-common/bt2c/json-val-req.hpp"
#include "cpp-common/bt2c/logging.hpp"

#include "../../../metadata/json-strings.hpp"
#include "val-req.hpp"

namespace ctf {
namespace src {
namespace {

/*
 * CTF 2 JSON alignment value requirement.
 */
class AlignValReq final : public bt2c::JsonValHasTypeReq
{
public:
    explicit AlignValReq(const bt2c::Logger& parentLogger) noexcept :
        bt2c::JsonValHasTypeReq {bt2c::ValType::UInt, parentLogger}
    {
    }

    static SP shared(const bt2c::Logger& parentLogger)
    {
        return std::make_shared<AlignValReq>(parentLogger);
    }

protected:
    static bool _isPowOfTwo(const unsigned long long val) noexcept
    {
        return ((val & (val - 1)) == 0) && val > 0;
    }

    void _validate(const bt2c::JsonVal& jsonVal) const override
    {
        const auto val = *jsonVal.asUInt();

        if (!this->_isPowOfTwo(val)) {
            BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_THROW_SPEC(
                this->_logger(), bt2c::Error, jsonVal.loc(), "{} is not a power of two.", val);
        }
    }
};

/*
 * CTF 2 JSON byte order value requirement.
 */
class ByteOrderValReq final : public bt2c::JsonStrValInSetReq
{
public:
    explicit ByteOrderValReq(const bt2c::Logger& parentLogger) :
        bt2c::JsonStrValInSetReq {
            bt2c::JsonStrValInSetReq::Set {jsonstr::bigEndian, jsonstr::littleEndian}, parentLogger}
    {
    }

    static SP shared(const bt2c::Logger& parentLogger)
    {
        return std::make_shared<ByteOrderValReq>(parentLogger);
    }

private:
    void _validate(const bt2c::JsonVal& jsonVal) const override
    {
        try {
            bt2c::JsonStrValInSetReq::_validate(jsonVal);
        } catch (const bt2c::Error&) {
            BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_RETHROW_SPEC(this->_logger(), jsonVal.loc(),
                                                              "Invalid byte order.");
        }
    }
};

/*
 * CTF 2 JSON bit order value requirement.
 */
class BitOrderValReq final : public bt2c::JsonStrValInSetReq
{
public:
    explicit BitOrderValReq(const bt2c::Logger& parentLogger) :
        bt2c::JsonStrValInSetReq {bt2c::JsonStrValInSetReq::Set {jsonstr::ftl, jsonstr::ltf},
                                  parentLogger}
    {
    }

    static SP shared(const bt2c::Logger& parentLogger)
    {
        return std::make_shared<BitOrderValReq>(parentLogger);
    }

private:
    void _validate(const bt2c::JsonVal& jsonVal) const override
    {
        try {
            bt2c::JsonStrValInSetReq::_validate(jsonVal);
        } catch (const bt2c::Error&) {
            BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_RETHROW_SPEC(this->_logger(), jsonVal.loc(),
                                                              "Invalid bit order.");
        }
    }
};

/*
 * CTF 2 JSON UUID value requirement.
 */
class UuidValReq final : public bt2c::JsonArrayValReq
{
public:
    explicit UuidValReq(const bt2c::Logger& parentLogger) :
        bt2c::JsonArrayValReq {16, bt2c::JsonUIntValInRangeReq::shared(0, 255, parentLogger),
                               parentLogger}
    {
    }

    static SP shared(const bt2c::Logger& parentLogger)
    {
        return std::make_shared<UuidValReq>(parentLogger);
    }

private:
    void _validate(const bt2c::JsonVal& jsonVal) const override
    {
        try {
            bt2c::JsonArrayValReq::_validate(jsonVal);
        } catch (const bt2c::Error&) {
            BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_RETHROW_SPEC(this->_logger(), jsonVal.loc(),
                                                              "Invalid UUID.");
        }
    }
};

/*
 * CTF 2 JSON field location path element value requirement.
 */
class FieldLocPathElemValReq final : public bt2c::JsonValReq
{
public:
    explicit FieldLocPathElemValReq(const bt2c::Logger& parentLogger) :
        bt2c::JsonValReq {parentLogger}
    {
    }

    static SP shared(const bt2c::Logger& parentLogger)
    {
        return std::make_shared<FieldLocPathElemValReq>(parentLogger);
    }

private:
    void _validate(const bt2c::JsonVal& jsonVal) const override
    {
        if (jsonVal.isNull() || jsonVal.isStr()) {
            /* Valid */
            return;
        }

        BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_THROW_SPEC(this->_logger(), bt2c::Error, jsonVal.loc(),
                                                        "Expecting a string or `null`.");
    }
};

/*
 * CTF 2 JSON field location value requirement.
 */
class FieldLocValReq final : public bt2c::JsonObjValReq
{
public:
    explicit FieldLocValReq(const bt2c::Logger& parentLogger) :
        /* clang-format off */
        bt2c::JsonObjValReq {{
            {jsonstr::origin, {
                bt2c::JsonStrValInSetReq::shared({
                    jsonstr::pktHeader,
                    jsonstr::pktCtx,
                    jsonstr::eventRecordHeader,
                    jsonstr::eventRecordCommonCtx,
                    jsonstr::eventRecordSpecCtx,
                    jsonstr::eventRecordPayload,
                }, parentLogger)
            }},
            {jsonstr::path, {
                bt2c::JsonArrayValReq::shared(1, bt2s::nullopt,
                                              FieldLocPathElemValReq::shared(parentLogger), parentLogger),
                true
            }},
        }, parentLogger}
    /* clang-format on */
    {
    }

    static SP shared(const bt2c::Logger& parentLogger)
    {
        return std::make_shared<FieldLocValReq>(parentLogger);
    }

private:
    void _validate(const bt2c::JsonVal& jsonVal) const override
    {
        try {
            bt2c::JsonObjValReq::_validate(jsonVal);

            /* Validate that the last path element is not `null` */
            {
                const auto& jsonLastPathElem =
                    **(jsonVal.asObj()[jsonstr::path]->asArray().end() - 1);

                if (jsonLastPathElem.isNull()) {
                    BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_THROW_SPEC(this->_logger(), bt2c::Error,
                                                                    jsonLastPathElem.loc(),
                                                                    "Path ends with `null`.");
                }
            }
        } catch (const bt2c::Error&) {
            BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_RETHROW_SPEC(this->_logger(), jsonVal.loc(),
                                                              "Invalid field location.");
        }
    }
};

/*
 * CTF 2 JSON user attributes value requirement.
 */
class UserAttrsValReq final : public bt2c::JsonObjValReq
{
public:
    explicit UserAttrsValReq(const bt2c::Logger& parentLogger) :
        bt2c::JsonObjValReq {{}, true, parentLogger}
    {
    }

    static SP shared(const bt2c::Logger& parentLogger)
    {
        return std::make_shared<UserAttrsValReq>(parentLogger);
    }

private:
    void _validate(const bt2c::JsonVal& jsonVal) const override
    {
        try {
            bt2c::JsonObjValReq::_validate(jsonVal);
        } catch (const bt2c::Error&) {
            BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_RETHROW_SPEC(this->_logger(), jsonVal.loc(),
                                                              "Invalid user attributes.");
        }
    }
};

/*
 * CTF 2 JSON trace environment value requirement.
 */
class TraceEnvValReq final : public bt2c::JsonObjValReq
{
public:
    explicit TraceEnvValReq(const bt2c::Logger& parentLogger) :
        bt2c::JsonObjValReq {{}, true, parentLogger}
    {
    }

    static SP shared(const bt2c::Logger& parentLogger)
    {
        return std::make_shared<TraceEnvValReq>(parentLogger);
    }

private:
    void _validate(const bt2c::JsonVal& jsonVal) const override
    {
        try {
            bt2c::JsonObjValReq::_validate(jsonVal);

            /* Validate types of entries */
            for (auto& keyJsonValPair : jsonVal.asObj()) {
                auto& jsonEntry = keyJsonValPair.second;

                if (!jsonEntry->isUInt() && !jsonEntry->isSInt() && !jsonEntry->isStr()) {
                    BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_THROW_SPEC(
                        this->_logger(), bt2c::Error, jsonEntry->loc(),
                        "Entry `{}`: expecting an integer or a string.", keyJsonValPair.first);
                }
            }
        } catch (const bt2c::Error&) {
            BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_RETHROW_SPEC(this->_logger(), jsonVal.loc(),
                                                              "Invalid trace environment.");
        }
    }
};

/*
 * CTF 2 JSON extensions value requirement.
 */
class ExtValReq final : public bt2c::JsonObjValReq
{
public:
    explicit ExtValReq(const bt2c::Logger& parentLogger) :
        bt2c::JsonObjValReq {{}, true, parentLogger}
    {
    }

    static SP shared(const bt2c::Logger& parentLogger)
    {
        return std::make_shared<ExtValReq>(parentLogger);
    }

private:
    void _validate(const bt2c::JsonVal& jsonVal) const override
    {
        try {
            bt2c::JsonObjValReq::_validate(jsonVal);
        } catch (const bt2c::Error&) {
            BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_RETHROW_SPEC(this->_logger(), jsonVal.loc(),
                                                              "Invalid extensions.");
        }

        if (jsonVal.asObj().size() > 0) {
            /* Never valid */
            BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_THROW_SPEC(
                this->_logger(), bt2c::Error, jsonVal.loc(),
                "This version of the `ctf` plugin doesn't support any CTF 2 extension.");
        }
    }
};

/*
 * CTF 2 JSON roles value requirement.
 */
class RolesValReq final : public bt2c::JsonArrayValReq
{
public:
    /*
     * Builds a CTF 2 JSON roles value requirement: _validate()
     * validates that a given JSON array value only contains the roles
     * `validRoles`.
     */
    explicit RolesValReq(bt2c::JsonStrValInSetReq::Set validRoles,
                         const bt2c::Logger& parentLogger) :
        bt2c::JsonArrayValReq {
            bt2c::JsonStrValInSetReq::shared(std::move(validRoles), parentLogger), parentLogger}
    {
    }

    static SP shared(bt2c::JsonStrValInSetReq::Set validRoles, const bt2c::Logger& parentLogger)
    {
        return std::make_shared<RolesValReq>(std::move(validRoles), parentLogger);
    }

private:
    void _validate(const bt2c::JsonVal& jsonVal) const override
    {
        try {
            bt2c::JsonArrayValReq::_validate(jsonVal);
        } catch (const bt2c::Error&) {
            BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_RETHROW_SPEC(this->_logger(), jsonVal.loc(),
                                                              "Invalid roles.");
        }
    }
};

/*
 * Adds a JSON object value property requirement having the key `key` to
 * `propReqs`, passing `valReq` and `isRequired` to its constructor.
 */
void addToPropReqs(bt2c::JsonObjValReq::PropReqs& propReqs, std::string&& key,
                   bt2c::JsonValReq::SP valReq, const bool isRequired = false)
{
    propReqs.emplace(
        std::make_pair(std::move(key), bt2c::JsonObjValPropReq {std::move(valReq), isRequired}));
}

/*
 * Returns the pair (suitable for insertion into a
 * `bt2c::JsonObjValReq::PropReqs` instance) for the CTF 2 JSON type
 * object property requirement.
 */
bt2c::JsonObjValReq::PropReqsEntry objTypePropReqEntry(std::string&& type,
                                                       const bt2c::Logger& parentLogger)
{
    return {jsonstr::type, {bt2c::JsonStrValInSetReq::shared(std::move(type), parentLogger), true}};
}

/*
 * Returns the pair (suitable for insertion into a
 * `bt2c::JsonObjValReq::PropReqs` instance) for the CTF 2 attributes
 * object property requirement.
 */
bt2c::JsonObjValReq::PropReqsEntry attrsPropReqEntry(const bt2c::Logger& parentLogger)
{
    return {jsonstr::attrs, {UserAttrsValReq::shared(parentLogger)}};
}

/*
 * Returns the pair (suitable for insertion into a
 * `bt2c::JsonObjValReq::PropReqs` instance) for the CTF 2 extensions object
 * property requirement.
 */
bt2c::JsonObjValReq::PropReqsEntry extPropReqEntry(const bt2c::Logger& parentLogger)
{
    return {jsonstr::extensions, {ExtValReq::shared(parentLogger)}};
}

/*
 * CTF 2 JSON field class value abstract requirement.
 *
 * All derived classes are required to implement a static typeStr()
 * method which returns the type string of the field class.
 */
class FcValReq : public bt2c::JsonObjValReq
{
protected:
    /*
     * Builds a CTF 2 JSON field class value requirement of type `type`,
     * adding `propReqs` to the base JSON object value property
     * requirements.
     */
    explicit FcValReq(std::string&& type, PropReqs&& propReqs, const bt2c::Logger& parentLogger) :
        bt2c::JsonObjValReq {
            this->_buildPropReqs(std::move(type), std::move(propReqs), parentLogger), parentLogger}
    {
    }

    /*
     * Builds a CTF 2 JSON field class value requirement of type `type`.
     */
    explicit FcValReq(std::string&& type, const bt2c::Logger& parentLogger) :
        FcValReq {std::move(type), {}, parentLogger}
    {
    }

private:
    static PropReqs _buildPropReqs(std::string&& type, PropReqs&& propReqs,
                                   const bt2c::Logger& parentLogger)
    {
        propReqs.insert(objTypePropReqEntry(std::move(type), parentLogger));
        propReqs.insert(attrsPropReqEntry(parentLogger));
        propReqs.insert(extPropReqEntry(parentLogger));
        return std::move(propReqs);
    }
};

/*
 * CTF 2 JSON fixed-length bit array field class value requirement.
 */
class FixedLenBitArrayFcValReq : public FcValReq
{
protected:
    /*
     * Builds a CTF 2 JSON fixed-length bit array field class value
     * requirement of type `type`, adding `propReqs` to the base JSON
     * object value property requirements.
     */
    explicit FixedLenBitArrayFcValReq(std::string&& type, PropReqs&& propReqs,
                                      const bt2c::Logger& parentLogger) :
        FcValReq {std::move(type), this->_buildPropReqs(std::move(propReqs), parentLogger),
                  parentLogger}
    {
    }

    /*
     * Builds a CTF 2 JSON fixed-length bit array field class value
     * requirement of type `type`.
     */
    explicit FixedLenBitArrayFcValReq(std::string&& type, const bt2c::Logger& parentLogger) :
        FixedLenBitArrayFcValReq {std::move(type), {}, parentLogger}
    {
    }

public:
    explicit FixedLenBitArrayFcValReq(const bt2c::Logger& parentLogger) :
        FixedLenBitArrayFcValReq {this->typeStr(), parentLogger}
    {
    }

    static SP shared(const bt2c::Logger& parentLogger)
    {
        return std::make_shared<FixedLenBitArrayFcValReq>(parentLogger);
    }

    static const char *typeStr() noexcept
    {
        return jsonstr::fixedLenBitArray;
    }

private:
    void _validate(const bt2c::JsonVal& jsonVal) const override
    {
        try {
            FcValReq::_validate(jsonVal);
        } catch (const bt2c::Error&) {
            BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_RETHROW_SPEC(
                this->_logger(), jsonVal.loc(), "Invalid fixed-length bit array field class.");
        }
    }

    static PropReqs _buildPropReqs(PropReqs&& propReqs, const bt2c::Logger& parentLogger)
    {
        addToPropReqs(propReqs, jsonstr::len,
                      bt2c::JsonUIntValInRangeReq::shared(1, 64, parentLogger), true);
        addToPropReqs(propReqs, jsonstr::byteOrder, ByteOrderValReq::shared(parentLogger), true);
        addToPropReqs(propReqs, jsonstr::bitOrder, BitOrderValReq::shared(parentLogger));
        addToPropReqs(propReqs, jsonstr::align, AlignValReq::shared(parentLogger));
        return std::move(propReqs);
    }
};

/*
 * CTF 2 JSON fixed-length bit map field class flags value requirement.
 *
 * An instance of this class validates that a given JSON value is a CTF
 * 2 fixed-length bit map field class flags object.
 */
class FixedLenBitMapFcFlagsValReq final : public bt2c::JsonObjValReq
{
public:
    explicit FixedLenBitMapFcFlagsValReq(const bt2c::Logger& parentLogger) :
        bt2c::JsonObjValReq {{}, true, parentLogger}, _mRangeSetReq {parentLogger}
    {
    }

    static SP shared(const bt2c::Logger& parentLogger)
    {
        return std::make_shared<FixedLenBitMapFcFlagsValReq>(parentLogger);
    }

private:
    void _validate(const bt2c::JsonVal& jsonVal) const override
    {
        try {
            bt2c::JsonObjValReq::_validate(jsonVal);

            /* Require at least one flag */
            if (jsonVal.asObj().size() < 1) {
                BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_THROW_SPEC(
                    this->_logger(), bt2c::Error, jsonVal.loc(), "Expecting at least one flag.");
            }

            /* Validate range sets */
            for (auto& keyJsonValPair : jsonVal.asObj()) {
                try {
                    _mRangeSetReq.validate(*keyJsonValPair.second);
                } catch (const bt2c::Error&) {
                    BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_RETHROW_SPEC(
                        this->_logger(), jsonVal.loc(), "Invalid flag `{}`.", keyJsonValPair.first);
                }
            }
        } catch (const bt2c::Error&) {
            BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_RETHROW_SPEC(
                this->_logger(), jsonVal.loc(), "Invalid enumeration field class mappings.");
        }
    }

    Ctf2JsonIntRangeSetValReqBase<bt2c::JsonUIntValReq> _mRangeSetReq;
};

/*
 * CTF 2 JSON fixed-length bit map field class value requirement.
 */
class FixedLenBitMapFcValReq final : public FixedLenBitArrayFcValReq
{
public:
    explicit FixedLenBitMapFcValReq(const bt2c::Logger& parentLogger) :
        /* clang-format off */
        FixedLenBitArrayFcValReq {this->typeStr(), {
            {jsonstr::flags, {FixedLenBitMapFcFlagsValReq::shared(parentLogger), true}},
        }, parentLogger}
    /* clang-format on */
    {
    }

    static SP shared(const bt2c::Logger& parentLogger)
    {
        return std::make_shared<FixedLenBitMapFcValReq>(parentLogger);
    }

    static const char *typeStr() noexcept
    {
        return jsonstr::fixedLenBitMap;
    }

private:
    void _validate(const bt2c::JsonVal& jsonVal) const override
    {
        try {
            FcValReq::_validate(jsonVal);

            /*
             * Validate that the upper value of each flag bit range is
             * less than the length of instances.
             */
            const auto len = jsonVal.asObj().rawUIntVal(jsonstr::len);

            for (auto& keyJsonValPair : jsonVal.asObj()[jsonstr::flags]->asObj()) {
                for (auto& jsonRange : keyJsonValPair.second->asArray()) {
                    auto& jsonRangeUpper = jsonRange->asArray()[1].asUInt();

                    if (*jsonRangeUpper >= len) {
                        BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_THROW_SPEC(
                            this->_logger(), bt2c::Error, jsonRangeUpper.loc(),
                            "Flag `{}`: bit index {} is greater than or equal to "
                            "the value of the `{}` property ({} bits).",
                            keyJsonValPair.first, *jsonRangeUpper, jsonstr::len, len);
                    }
                }
            }
        } catch (const bt2c::Error&) {
            BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_RETHROW_SPEC(
                this->_logger(), jsonVal.loc(), "Invalid fixed-length bit map field class.");
        }
    }
};

/*
 * CTF 2 JSON fixed-length boolean field class value requirement.
 */
class FixedLenBoolFcValReq final : public FixedLenBitArrayFcValReq
{
public:
    explicit FixedLenBoolFcValReq(const bt2c::Logger& parentLogger) :
        FixedLenBitArrayFcValReq {this->typeStr(), parentLogger}
    {
    }

    static SP shared(const bt2c::Logger& parentLogger)
    {
        return std::make_shared<FixedLenBoolFcValReq>(parentLogger);
    }

    static const char *typeStr() noexcept
    {
        return jsonstr::fixedLenBool;
    }

private:
    void _validate(const bt2c::JsonVal& jsonVal) const override
    {
        try {
            FcValReq::_validate(jsonVal);
        } catch (const bt2c::Error&) {
            BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_RETHROW_SPEC(
                this->_logger(), jsonVal.loc(), "Invalid fixed-length boolean field class.");
        }
    }
};

/*
 * Returns the pair (suitable for insertion into a
 * `bt2c::JsonObjValReq::PropReqs` instance) for the CTF 2 integer field
 * class preferred display base object property requirement.
 */
bt2c::JsonObjValReq::PropReqsEntry intFcPrefDispBasePropReqEntry(const bt2c::Logger& parentLogger)
{
    return {jsonstr::prefDispBase,
            {bt2c::JsonUIntValInSetReq::shared({2, 8, 10, 16}, parentLogger)}};
}

/*
 * CTF 2 JSON fixed-length integer field class value abstract
 * requirement.
 */
class FixedLenIntFcValReq : public FixedLenBitArrayFcValReq
{
protected:
    /*
     * Builds a CTF 2 JSON fixed-length integer field class value
     * requirement of type `type`, adding `propReqs` to the base JSON
     * object value property requirements.
     */
    explicit FixedLenIntFcValReq(std::string&& type, PropReqs&& propReqs,
                                 const bt2c::Logger& parentLogger) :
        FixedLenBitArrayFcValReq {
            std::move(type), this->_buildPropReqs(std::move(propReqs), parentLogger), parentLogger}
    {
    }

private:
    static PropReqs _buildPropReqs(PropReqs&& propReqs, const bt2c::Logger& parentLogger)
    {
        propReqs.insert(intFcPrefDispBasePropReqEntry(parentLogger));
        return std::move(propReqs);
    }
};

/*
 * Returns the pair (suitable for insertion into a
 * `bt2c::JsonObjValReq::PropReqs` instance) for the CTF 2 unsigned
 * integer field class roles object property requirement.
 */
bt2c::JsonObjValReq::PropReqsEntry uIntFcRolesPropReqEntry(const bt2c::Logger& parentLogger)
{
    /* clang-format off */
    return {jsonstr::roles, {
        RolesValReq::shared({
            jsonstr::dataStreamClsId,
            jsonstr::dataStreamId,
            jsonstr::defClkTs,
            jsonstr::discEventRecordCounterSnap,
            jsonstr::eventRecordClsId,
            jsonstr::pktContentLen,
            jsonstr::pktEndDefClkTs,
            jsonstr::pktMagicNumber,
            jsonstr::pktSeqNum,
            jsonstr::pktTotalLen,
        }, parentLogger)
    }};
    /* clang-format on */
}

/*
 * CTF 2 JSON integer field class mappings value requirement.
 *
 * An instance of this class validates that a given JSON value is
 * a CTF 2 integer field class mappings object, each integer value
 * within the integer ranges satisfying an instance of `JsonIntValReqT`.
 */
template <typename JsonIntValReqT>
class IntFcMappingsValReq final : public bt2c::JsonObjValReq
{
public:
    explicit IntFcMappingsValReq(const bt2c::Logger& parentLogger) :
        bt2c::JsonObjValReq {{}, true, parentLogger}, _mRangeSetReq {parentLogger}
    {
    }

    static SP shared(const bt2c::Logger& parentLogger)
    {
        return std::make_shared<IntFcMappingsValReq>(parentLogger);
    }

private:
    void _validate(const bt2c::JsonVal& jsonVal) const override
    {
        try {
            bt2c::JsonObjValReq::_validate(jsonVal);

            /* Validate range sets */
            for (auto& keyJsonValPair : jsonVal.asObj()) {
                try {
                    _mRangeSetReq.validate(*keyJsonValPair.second);
                } catch (const bt2c::Error&) {
                    BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_RETHROW_SPEC(
                        this->_logger(), jsonVal.loc(), "Invalid mapping `{}`.",
                        keyJsonValPair.first);
                }
            }
        } catch (const bt2c::Error&) {
            BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_RETHROW_SPEC(
                this->_logger(), jsonVal.loc(), "Invalid enumeration field class mappings.");
        }
    }

    Ctf2JsonIntRangeSetValReqBase<JsonIntValReqT> _mRangeSetReq;
};

/*
 * Returns the pair (suitable for insertion into a
 * `bt2c::JsonObjValReq::PropReqs` instance) for the CTF 2 integer field
 * class mappings object property requirement.
 */
template <typename JsonIntValReqT>
bt2c::JsonObjValReq::PropReqsEntry intFcMappingsPropReqEntry(const bt2c::Logger& parentLogger)
{
    return {jsonstr::mappings, {IntFcMappingsValReq<JsonIntValReqT>::shared(parentLogger)}};
}

/*
 * CTF 2 JSON fixed-length unsigned integer field class value
 * requirement.
 */
class FixedLenUIntFcValReq final : public FixedLenIntFcValReq
{
public:
    /*
     * Builds a CTF 2 JSON fixed-length unsigned integer field class
     * value requirement.
     */
    explicit FixedLenUIntFcValReq(const bt2c::Logger& parentLogger) :
        FixedLenIntFcValReq {this->typeStr(), this->_buildPropReqs(parentLogger), parentLogger}
    {
    }

    static SP shared(const bt2c::Logger& parentLogger)
    {
        return std::make_shared<FixedLenUIntFcValReq>(parentLogger);
    }

    static const char *typeStr() noexcept
    {
        return jsonstr::fixedLenUInt;
    }

private:
    static PropReqs _buildPropReqs(const bt2c::Logger& parentLogger)
    {
        PropReqs propReqs;

        propReqs.insert(intFcMappingsPropReqEntry<bt2c::JsonUIntValReq>(parentLogger));
        propReqs.insert(uIntFcRolesPropReqEntry(parentLogger));
        return propReqs;
    }

    void _validate(const bt2c::JsonVal& jsonVal) const override
    {
        try {
            FcValReq::_validate(jsonVal);
        } catch (const bt2c::Error&) {
            BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_RETHROW_SPEC(
                this->_logger(), jsonVal.loc(),
                "Invalid fixed-length unsigned integer field class.");
        }
    }
};

/*
 * CTF 2 JSON fixed-length signed integer field class value requirement.
 */
class FixedLenSIntFcValReq final : public FixedLenIntFcValReq
{
public:
    /*
     * Builds a CTF 2 JSON fixed-length signed integer field class
     * value requirement.
     */
    explicit FixedLenSIntFcValReq(const bt2c::Logger& parentLogger) :
        FixedLenIntFcValReq {this->typeStr(),
                             {
                                 intFcMappingsPropReqEntry<bt2c::JsonSIntValReq>(parentLogger),
                             },
                             parentLogger}
    {
    }

    static SP shared(const bt2c::Logger& parentLogger)
    {
        return std::make_shared<FixedLenSIntFcValReq>(parentLogger);
    }

    static const char *typeStr() noexcept
    {
        return jsonstr::fixedLenSInt;
    }

private:
    void _validate(const bt2c::JsonVal& jsonVal) const override
    {
        try {
            FcValReq::_validate(jsonVal);
        } catch (const bt2c::Error&) {
            BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_RETHROW_SPEC(
                this->_logger(), jsonVal.loc(), "Invalid fixed-length signed integer field class.");
        }
    }
};

/*
 * CTF 2 JSON fixed-length floating-point number field class value
 * requirement.
 */
class FixedLenFloatFcValReq final : public FixedLenBitArrayFcValReq
{
public:
    explicit FixedLenFloatFcValReq(const bt2c::Logger& parentLogger) :
        FixedLenBitArrayFcValReq {this->typeStr(), parentLogger}
    {
    }

    static SP shared(const bt2c::Logger& parentLogger)
    {
        return std::make_shared<FixedLenFloatFcValReq>(parentLogger);
    }

    static const char *typeStr() noexcept
    {
        return jsonstr::fixedLenFloat;
    }

private:
    void _validate(const bt2c::JsonVal& jsonVal) const override
    {
        try {
            FcValReq::_validate(jsonVal);
        } catch (const bt2c::Error&) {
            BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_RETHROW_SPEC(
                this->_logger(), jsonVal.loc(),
                "Invalid fixed-length floating-point number field class.");
        }
    }
};

/*
 * CTF 2 JSON variable-length integer field class value abstract
 * requirement.
 */
class VarLenIntFcValReq : public FcValReq
{
protected:
    /*
     * Builds a CTF 2 JSON variable-length integer field class value
     * requirement of type `type`, adding `propReqs` to the base JSON
     * object value property requirements.
     */
    explicit VarLenIntFcValReq(std::string&& type, PropReqs&& propReqs,
                               const bt2c::Logger& parentLogger) :
        FcValReq {std::move(type), this->_buildPropReqs(std::move(propReqs), parentLogger),
                  parentLogger}
    {
    }

private:
    static PropReqs _buildPropReqs(PropReqs&& propReqs, const bt2c::Logger& parentLogger)
    {
        propReqs.insert(intFcPrefDispBasePropReqEntry(parentLogger));
        return std::move(propReqs);
    }
};

/*
 * CTF 2 JSON variable-length unsigned integer field class value
 * requirement.
 */
class VarLenUIntFcValReq : public VarLenIntFcValReq
{
public:
    /*
     * Builds a CTF 2 JSON variable-length unsigned integer field class
     * value requirement.
     */
    explicit VarLenUIntFcValReq(const bt2c::Logger& parentLogger) :
        VarLenIntFcValReq {this->typeStr(), this->_buildPropReqs(parentLogger), parentLogger}
    {
    }

    static SP shared(const bt2c::Logger& parentLogger)
    {
        return std::make_shared<VarLenUIntFcValReq>(parentLogger);
    }

    static const char *typeStr() noexcept
    {
        return jsonstr::varLenUInt;
    }

private:
    static PropReqs _buildPropReqs(const bt2c::Logger& parentLogger)
    {
        PropReqs propReqs;

        propReqs.insert(intFcMappingsPropReqEntry<bt2c::JsonUIntValReq>(parentLogger));
        propReqs.insert(uIntFcRolesPropReqEntry(parentLogger));
        return propReqs;
    }

    void _validate(const bt2c::JsonVal& jsonVal) const override
    {
        try {
            FcValReq::_validate(jsonVal);
        } catch (const bt2c::Error&) {
            BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_RETHROW_SPEC(
                this->_logger(), jsonVal.loc(),
                "Invalid variable-length unsigned integer field class.");
        }
    }
};

/*
 * CTF 2 JSON variable-length signed integer field class
 * value requirement.
 */
class VarLenSIntFcValReq : public VarLenIntFcValReq
{
public:
    /*
     * Builds a CTF 2 JSON variable-length unsigned integer field class
     * value requirement.
     */
    explicit VarLenSIntFcValReq(const bt2c::Logger& parentLogger) :
        VarLenIntFcValReq {this->typeStr(),
                           {
                               intFcMappingsPropReqEntry<bt2c::JsonSIntValReq>(parentLogger),
                           },
                           parentLogger}
    {
    }

    static SP shared(const bt2c::Logger& parentLogger)
    {
        return std::make_shared<VarLenSIntFcValReq>(parentLogger);
    }

    static const char *typeStr() noexcept
    {
        return jsonstr::varLenSInt;
    }

private:
    void _validate(const bt2c::JsonVal& jsonVal) const override
    {
        try {
            FcValReq::_validate(jsonVal);
        } catch (const bt2c::Error&) {
            BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_RETHROW_SPEC(
                this->_logger(), jsonVal.loc(),
                "Invalid variable-length signed integer field class.");
        }
    }
};

/*
 * CTF 2 JSON string encoding value requirement.
 */
class StrEncodingValReq final : public bt2c::JsonStrValInSetReq
{
public:
    explicit StrEncodingValReq(const bt2c::Logger& parentLogger) :
        bt2c::JsonStrValInSetReq {bt2c::JsonStrValInSetReq::Set {
                                      jsonstr::utf8,
                                      jsonstr::utf16Be,
                                      jsonstr::utf16Le,
                                      jsonstr::utf32Be,
                                      jsonstr::utf32Le,
                                  },
                                  parentLogger}
    {
    }

    static SP shared(const bt2c::Logger& parentLogger)
    {
        return std::make_shared<StrEncodingValReq>(parentLogger);
    }

private:
    void _validate(const bt2c::JsonVal& jsonVal) const override
    {
        try {
            bt2c::JsonStrValInSetReq::_validate(jsonVal);
        } catch (const bt2c::Error&) {
            BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_RETHROW_SPEC(this->_logger(), jsonVal.loc(),
                                                              "Invalid string encoding.");
        }
    }
};

/*
 * CTF 2 JSON string field class value requirement.
 */
class StrFcValReq : public FcValReq
{
protected:
    explicit StrFcValReq(std::string&& type, PropReqs&& propReqs,
                         const bt2c::Logger& parentLogger) :
        FcValReq {std::move(type), this->_buildPropReqs(std::move(propReqs), parentLogger),
                  parentLogger}
    {
    }

    void _validate(const bt2c::JsonVal& jsonVal) const override
    {
        try {
            FcValReq::_validate(jsonVal);
        } catch (const bt2c::Error&) {
            BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_RETHROW_SPEC(this->_logger(), jsonVal.loc(),
                                                              "Invalid string field class.");
        }
    }

private:
    static PropReqs _buildPropReqs(PropReqs&& propReqs, const bt2c::Logger& parentLogger)
    {
        addToPropReqs(propReqs, jsonstr::encoding, StrEncodingValReq::shared(parentLogger));
        return std::move(propReqs);
    }
};

/*
 * CTF 2 JSON null-terminated string field class value requirement.
 */
class NullTerminatedStrFcValReq final : public StrFcValReq
{
public:
    explicit NullTerminatedStrFcValReq(const bt2c::Logger& parentLogger) :
        StrFcValReq {this->typeStr(), {}, parentLogger}
    {
    }

    static SP shared(const bt2c::Logger& parentLogger)
    {
        return std::make_shared<NullTerminatedStrFcValReq>(parentLogger);
    }

    static const char *typeStr() noexcept
    {
        return jsonstr::nullTerminatedStr;
    }

private:
    void _validate(const bt2c::JsonVal& jsonVal) const override
    {
        try {
            FcValReq::_validate(jsonVal);
        } catch (const bt2c::Error&) {
            BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_RETHROW_SPEC(
                this->_logger(), jsonVal.loc(), "Invalid null-terminated string field class.");
        }
    }
};

/*
 * Returns the pair (suitable for insertion into a
 * `bt2c::JsonObjValReq::PropReqs` instance) for the CTF 2 static-length
 * field class length object property requirement.
 */
bt2c::JsonObjValReq::PropReqsEntry staticLenFcLenPropReqEntry(const bt2c::Logger& parentLogger)
{
    return {jsonstr::len,
            {bt2c::JsonValHasTypeReq::shared(bt2c::ValType::UInt, parentLogger), true}};
}

/*
 * Returns the pair (suitable for insertion into a
 * `bt2c::JsonObjValReq::PropReqs` instance) for the CTF 2
 * dynamic-length field class length field location object
 * property requirement.
 */
bt2c::JsonObjValReq::PropReqsEntry dynLenFcLenFieldLocPropReqEntry(const bt2c::Logger& parentLogger)
{
    return {jsonstr::lenFieldLoc, {FieldLocValReq::shared(parentLogger), true}};
}

/*
 * CTF 2 JSON static-length string field class value requirement.
 */
class StaticLenStrFcValReq final : public StrFcValReq
{
public:
    explicit StaticLenStrFcValReq(const bt2c::Logger& parentLogger) :
        StrFcValReq {this->typeStr(), {staticLenFcLenPropReqEntry(parentLogger)}, parentLogger}
    {
    }

    static SP shared(const bt2c::Logger& parentLogger)
    {
        return std::make_shared<StaticLenStrFcValReq>(parentLogger);
    }

    static const char *typeStr() noexcept
    {
        return jsonstr::staticLenStr;
    }

private:
    void _validate(const bt2c::JsonVal& jsonVal) const override
    {
        try {
            StrFcValReq::_validate(jsonVal);
        } catch (const bt2c::Error&) {
            BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_RETHROW_SPEC(
                this->_logger(), jsonVal.loc(), "Invalid static-length string field class.");
        }
    }
};

/*
 * CTF 2 JSON dynamic-length string field class value requirement.
 */
class DynLenStrFcValReq final : public StrFcValReq
{
public:
    explicit DynLenStrFcValReq(const bt2c::Logger& parentLogger) :
        StrFcValReq {this->typeStr(), {dynLenFcLenFieldLocPropReqEntry(parentLogger)}, parentLogger}
    {
    }

    static SP shared(const bt2c::Logger& parentLogger)
    {
        return std::make_shared<DynLenStrFcValReq>(parentLogger);
    }

    static const char *typeStr() noexcept
    {
        return jsonstr::dynLenStr;
    }

private:
    void _validate(const bt2c::JsonVal& jsonVal) const override
    {
        try {
            StrFcValReq::_validate(jsonVal);
        } catch (const bt2c::Error&) {
            BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_RETHROW_SPEC(
                this->_logger(), jsonVal.loc(), "Invalid dynamic-length string field class.");
        }
    }
};

/*
 * CTF 2 JSON BLOB field class value abstract requirement.
 */
class BlobFcValReq : public FcValReq
{
protected:
    /*
     * Builds a CTF 2 JSON BLOB field class value requirement of type
     * `type`, adding `propReqs` to the base JSON object value property
     * requirements.
     */
    explicit BlobFcValReq(std::string&& type, PropReqs&& propReqs,
                          const bt2c::Logger& parentLogger) :
        FcValReq {std::move(type), this->_buildPropReqs(std::move(propReqs), parentLogger),
                  parentLogger}
    {
    }

    /*
     * Builds a CTF 2 JSON BLOB field class value requirement of type
     * `type`.
     */
    explicit BlobFcValReq(std::string&& type, const bt2c::Logger& parentLogger) :
        BlobFcValReq {std::move(type), {}, parentLogger}
    {
    }

private:
    static PropReqs _buildPropReqs(PropReqs&& propReqs, const bt2c::Logger& parentLogger)
    {
        addToPropReqs(propReqs, jsonstr::mediaType,
                      bt2c::JsonValHasTypeReq::shared(bt2c::ValType::Str, parentLogger));
        return std::move(propReqs);
    }
};

/*
 * CTF 2 JSON static-length BLOB field class value requirement.
 */
class StaticLenBlobFcValReq final : public BlobFcValReq
{
public:
    explicit StaticLenBlobFcValReq(const bt2c::Logger& parentLogger) :
        BlobFcValReq {this->typeStr(), this->_buildPropReqs(parentLogger), parentLogger}
    {
    }

    static SP shared(const bt2c::Logger& parentLogger)
    {
        return std::make_shared<StaticLenBlobFcValReq>(parentLogger);
    }

    static const char *typeStr() noexcept
    {
        return jsonstr::staticLenBlob;
    }

private:
    static PropReqs _buildPropReqs(const bt2c::Logger& parentLogger)
    {
        PropReqs propReqs;

        propReqs.insert(staticLenFcLenPropReqEntry(parentLogger));
        propReqs.insert(
            {jsonstr::roles, RolesValReq::shared({jsonstr::metadataStreamUuid}, parentLogger)});
        return propReqs;
    }

    void _validate(const bt2c::JsonVal& jsonVal) const override
    {
        try {
            FcValReq::_validate(jsonVal);

            const auto jsonRoles = jsonVal.asObj()[jsonstr::roles];

            if (jsonRoles && !jsonRoles->asArray().isEmpty()) {
                /* The only valid role is the metadata stream UUID */
                auto& jsonLen = jsonVal.asObj()[jsonstr::len]->asUInt();

                if (*jsonLen != 16) {
                    BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_THROW_SPEC(
                        this->_logger(), bt2c::Error, jsonLen.loc(),
                        "`{}` property: expecting 16, not {}, because the field class has the `{}` role.",
                        jsonstr::len, *jsonLen, jsonstr::metadataStreamUuid);
                }
            }
        } catch (const bt2c::Error&) {
            BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_RETHROW_SPEC(
                this->_logger(), jsonVal.loc(), "Invalid static-length BLOB field class.");
        }
    }
};

/*
 * CTF 2 JSON dynamic-length BLOB field class value requirement.
 */
class DynLenBlobFcValReq final : public BlobFcValReq
{
public:
    explicit DynLenBlobFcValReq(const bt2c::Logger& parentLogger) :
        BlobFcValReq {this->typeStr(),
                      {dynLenFcLenFieldLocPropReqEntry(parentLogger)},
                      parentLogger}
    {
    }

    static SP shared(const bt2c::Logger& parentLogger)
    {
        return std::make_shared<DynLenBlobFcValReq>(parentLogger);
    }

    static const char *typeStr() noexcept
    {
        return jsonstr::dynLenBlob;
    }

private:
    void _validate(const bt2c::JsonVal& jsonVal) const override
    {
        try {
            FcValReq::_validate(jsonVal);
        } catch (const bt2c::Error&) {
            BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_RETHROW_SPEC(
                this->_logger(), jsonVal.loc(), "Invalid dynamic-length BLOB field class.");
        }
    }
};

class AnyFullBlownFcValReq;

/*
 * CTF 2 field classes are recursive, in that some field classes may
 * contain other field classes.
 *
 * To make it possible to build a `AnyFullBlownFcValReq` instance
 * without a shared pointer, the constructor of compound field class
 * requirements accepts a `const AnyFullBlownFcValReq&` (raw reference)
 * parameter. The raw reference must therefore remain valid as long as
 * the compound field class using it exists.
 *
 * Because JSON value requirements work with shared pointers to
 * `const Ctf2JsonValReq` (`bt2c::JsonValReq::SP`), this
 * `AnyFcValReqWrapper` class simply wraps such a
 * `const AnyFullBlownFcValReq *` value: its _validate() method forwards
 * the call. An `AnyFcValReqWrapper` instance doesn't own the
 * raw pointer.
 */
class AnyFcValReqWrapper final : public bt2c::JsonValReq
{
public:
    explicit AnyFcValReqWrapper(const AnyFullBlownFcValReq& anyFcValReq,
                                const bt2c::Logger& parentLogger) :
        bt2c::JsonValReq {parentLogger},
        _mAnyFullBlownFcValReq {&anyFcValReq}
    {
    }

    static SP shared(const AnyFullBlownFcValReq& anyFullBlownFcValReq,
                     const bt2c::Logger& parentLogger)
    {
        return std::make_shared<AnyFcValReqWrapper>(anyFullBlownFcValReq, parentLogger);
    }

private:
    void _validate(const bt2c::JsonVal& jsonVal) const override;

    const AnyFullBlownFcValReq *_mAnyFullBlownFcValReq;
};

/*
 * Returns the pair (suitable for insertion into a
 * `bt2c::JsonObjValReq::PropReqs` instance) for the CTF 2 field class
 * object property requirement having the key `key`.
 */
bt2c::JsonObjValReq::PropReqsEntry
anyFcPropReqEntry(std::string key, const AnyFullBlownFcValReq& anyFullBlownFcValReq,
                  const bool isRequired, const bt2c::Logger& parentLogger)
{
    return {std::move(key),
            {AnyFcValReqWrapper::shared(anyFullBlownFcValReq, parentLogger), isRequired}};
}

/*
 * Calls the other anyFcPropReqEntry() with `isRequired` set to `false`.
 */
bt2c::JsonObjValReq::PropReqsEntry
anyFcPropReqEntry(std::string key, const AnyFullBlownFcValReq& anyFullBlownFcValReq,
                  const bt2c::Logger& parentLogger)
{
    return anyFcPropReqEntry(std::move(key), anyFullBlownFcValReq, false, parentLogger);
}

/*
 * Returns the pair (suitable for insertion into a
 * `bt2c::JsonObjValReq::PropReqs` instance) for the CTF 2 object name
 * object property requirement.
 */
bt2c::JsonObjValReq::PropReqsEntry namePropReqEntry(const bool isRequired,
                                                    const bt2c::Logger& parentLogger)
{
    return {jsonstr::name,
            {bt2c::JsonValHasTypeReq::shared(bt2c::ValType::Str, parentLogger), isRequired}};
}

/*
 * CTF 2 JSON structure field member class value requirement.
 */
class StructFieldMemberClsValReq final : public bt2c::JsonObjValReq
{
public:
    explicit StructFieldMemberClsValReq(const AnyFullBlownFcValReq& anyFullBlownFcValReq,
                                        const bt2c::Logger& parentLogger) :
        /* clang-format off */
        bt2c::JsonObjValReq {{
            namePropReqEntry(true, parentLogger),
            anyFcPropReqEntry(jsonstr::fc, anyFullBlownFcValReq, parentLogger),
            attrsPropReqEntry(parentLogger),
            extPropReqEntry(parentLogger),
        }, parentLogger}
    /* clang-format on */
    {
    }

    static SP shared(const AnyFullBlownFcValReq& anyFullBlownFcValReq,
                     const bt2c::Logger& parentLogger)
    {
        return std::make_shared<StructFieldMemberClsValReq>(anyFullBlownFcValReq, parentLogger);
    }

private:
    void _validate(const bt2c::JsonVal& jsonVal) const override
    {
        try {
            bt2c::JsonObjValReq::_validate(jsonVal);
        } catch (const bt2c::Error&) {
            BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_RETHROW_SPEC(
                this->_logger(), jsonVal.loc(), "Invalid structure field member class.");
        }
    }
};

/*
 * Returns the pair (suitable for insertion into a
 * `bt2c::JsonObjValReq::PropReqs` instance) for the CTF 2 minimum
 * alignment object property requirement.
 */
bt2c::JsonObjValReq::PropReqsEntry minAlignPropReqEntry(const bt2c::Logger& parentLogger)
{
    return {jsonstr::minAlign, AlignValReq::shared(parentLogger)};
}

class UniqueEntryNamesValidator final
{
public:
    explicit UniqueEntryNamesValidator(const bt2c::Logger& parentLogger) : _mLogger {parentLogger}
    {
    }

    /*
     * Validates that, within the JSON array value having the key
     * `propName` within the JSON object value `jsonVal`, the `name`
     * property of each (JSON object value) element, if it exists,
     * is unique.
     *
     * Throws `TextParseError` on failure, using `elemName` to name the
     * element having a duplicate name.
     */
    void validate(const bt2c::JsonVal& jsonVal, const char * const propName,
                  const char * const elemName) const
    {
        const auto jsonEntries = jsonVal.asObj()[propName];

        if (!jsonEntries) {
            /* Empty */
            return;
        }

        /* Use a set to accumulate unique names */
        std::unordered_set<std::string> names;

        for (auto& jsonEntry : jsonEntries->asArray()) {
            const auto jsonName = jsonEntry->asObj()[jsonstr::name];

            if (!jsonName) {
                /* No `name` property */
                continue;
            }

            auto& jsonNameStr = jsonName->asStr();

            if (bt2c::contains(names, *jsonNameStr)) {
                /* Already in set */
                BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_THROW(bt2c::Error, jsonName->loc(),
                                                           "Duplicate {} name `{}`.", elemName,
                                                           (*jsonNameStr));
            }

            /* Add to set */
            names.insert(*jsonNameStr);
        }
    }

private:
    bt2c::Logger _mLogger;
};

/*
 * CTF 2 JSON structure field class value requirement.
 */
class StructFcValReq final : public FcValReq
{
public:
    explicit StructFcValReq(const AnyFullBlownFcValReq& anyFullBlownFcValReq,
                            const bt2c::Logger& parentLogger) :
        /* clang-format off */
        FcValReq {this->typeStr(), {
            {jsonstr::memberClasses, {
                bt2c::JsonArrayValReq::shared(
                    StructFieldMemberClsValReq::shared(anyFullBlownFcValReq, parentLogger),
                    parentLogger
                )
            }},
            minAlignPropReqEntry(parentLogger),
        }, parentLogger},
        _mUniqueEntryNamesValidator {parentLogger}
    /* clang-format on */
    {
    }

    static SP shared(const AnyFullBlownFcValReq& anyFullBlownFcValReq,
                     const bt2c::Logger& parentLogger)
    {
        return std::make_shared<StructFcValReq>(anyFullBlownFcValReq, parentLogger);
    }

    static const char *typeStr() noexcept
    {
        return jsonstr::structure;
    }

private:
    void _validate(const bt2c::JsonVal& jsonVal) const override
    {
        try {
            FcValReq::_validate(jsonVal);

            /* Validate that member class names are unique */
            _mUniqueEntryNamesValidator.validate(jsonVal, jsonstr::memberClasses,
                                                 "structure field member class");
        } catch (const bt2c::Error&) {
            BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_RETHROW_SPEC(this->_logger(), jsonVal.loc(),
                                                              "Invalid structure field class.");
        }
    }

    UniqueEntryNamesValidator _mUniqueEntryNamesValidator;
};

/*
 * CTF 2 JSON array field class value abstract requirement.
 */
class ArrayFcValReq : public FcValReq
{
protected:
    /*
     * Builds a CTF 2 JSON array field class value requirement of type
     * `type`, adding `propReqs` to the base JSON object value property
     * requirements.
     */
    explicit ArrayFcValReq(std::string&& type, const AnyFullBlownFcValReq& anyFullBlownFcValReq,
                           PropReqs&& propReqs, const bt2c::Logger& parentLogger) :
        FcValReq {std::move(type),
                  this->_buildPropReqs(anyFullBlownFcValReq, std::move(propReqs), parentLogger),
                  parentLogger}
    {
    }

    /*
     * Builds a CTF 2 JSON array field class value requirement of type
     * `type`.
     */
    explicit ArrayFcValReq(std::string&& type, const AnyFullBlownFcValReq& anyFullBlownFcValReq,
                           const bt2c::Logger& parentLogger) :
        ArrayFcValReq {std::move(type), anyFullBlownFcValReq, {}, parentLogger}
    {
    }

private:
    static PropReqs _buildPropReqs(const AnyFullBlownFcValReq& anyFullBlownFcValReq,
                                   PropReqs&& propReqs, const bt2c::Logger& parentLogger)
    {
        propReqs.insert(anyFcPropReqEntry(jsonstr::elemFc, anyFullBlownFcValReq, parentLogger));
        propReqs.insert(minAlignPropReqEntry(parentLogger));
        return std::move(propReqs);
    }
};

/*
 * CTF 2 JSON static-length array field class value requirement.
 */
class StaticLenArrayFcValReq final : public ArrayFcValReq
{
public:
    explicit StaticLenArrayFcValReq(const AnyFullBlownFcValReq& anyFullBlownFcValReq,
                                    const bt2c::Logger& parentLogger) :
        /* clang-format off */
        ArrayFcValReq {this->typeStr(), anyFullBlownFcValReq, {
            staticLenFcLenPropReqEntry(parentLogger)
        }, parentLogger}
    /* clang-format on */
    {
    }

    static SP shared(const AnyFullBlownFcValReq& anyFullBlownFcValReq,
                     const bt2c::Logger& parentLogger)
    {
        return std::make_shared<StaticLenArrayFcValReq>(anyFullBlownFcValReq, parentLogger);
    }

    static const char *typeStr() noexcept
    {
        return jsonstr::staticLenArray;
    }

private:
    void _validate(const bt2c::JsonVal& jsonVal) const override
    {
        try {
            FcValReq::_validate(jsonVal);
        } catch (const bt2c::Error&) {
            BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_RETHROW_SPEC(
                this->_logger(), jsonVal.loc(), "Invalid static-length array field class.");
        }
    }
};

/*
 * CTF 2 JSON dynamic-length array field class value requirement.
 */
class DynLenArrayFcValReq final : public ArrayFcValReq
{
public:
    explicit DynLenArrayFcValReq(const AnyFullBlownFcValReq& anyFullBlownFcValReq,
                                 const bt2c::Logger& parentLogger) :
        /* clang-format off */
        ArrayFcValReq {this->typeStr(), anyFullBlownFcValReq, {
            dynLenFcLenFieldLocPropReqEntry(parentLogger)
        }, parentLogger}
    /* clang-format on */
    {
    }

    static SP shared(const AnyFullBlownFcValReq& anyFullBlownFcValReq,
                     const bt2c::Logger& parentLogger)
    {
        return std::make_shared<DynLenArrayFcValReq>(anyFullBlownFcValReq, parentLogger);
    }

    static const char *typeStr() noexcept
    {
        return jsonstr::dynLenArray;
    }

private:
    void _validate(const bt2c::JsonVal& jsonVal) const override
    {
        try {
            FcValReq::_validate(jsonVal);
        } catch (const bt2c::Error&) {
            BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_RETHROW_SPEC(
                this->_logger(), jsonVal.loc(), "Invalid dynamic-length array field class.");
        }
    }
};

/*
 * Returns the pair (suitable for insertion into a
 * `bt2c::JsonObjValReq::PropReqs` instance) for the CTF 2 selector
 * field location object property requirement.
 */
bt2c::JsonObjValReq::PropReqsEntry selFieldLocPropReqEntry(const bt2c::Logger& parentLogger)
{
    return {jsonstr::selFieldLoc, {FieldLocValReq::shared(parentLogger), true}};
}

/*
 * Returns the pair (suitable for insertion into a
 * `bt2c::JsonObjValReq::PropReqs` instance) for the CTF 2 selector
 * field ranges object property requirement.
 */
bt2c::JsonObjValReq::PropReqsEntry selFieldRangesPropReqEntry(const bool isRequired,
                                                              const bt2c::Logger& parentLogger)
{
    return {jsonstr::selFieldRanges,
            {ctf::src::Ctf2JsonIntRangeSetValReq::shared(parentLogger), isRequired}};
}

/*
 * CTF 2 JSON optional field class value requirement.
 */
class OptionalFcValReq final : public FcValReq
{
public:
    explicit OptionalFcValReq(const AnyFullBlownFcValReq& anyFullBlownFcValReq,
                              const bt2c::Logger& parentLogger) :
        /* clang-format off */
        FcValReq {this->typeStr(), {
            anyFcPropReqEntry(jsonstr::fc, anyFullBlownFcValReq, parentLogger),
            selFieldLocPropReqEntry(parentLogger),
            selFieldRangesPropReqEntry(false, parentLogger),
        }, parentLogger}
    /* clang-format on */
    {
    }

    static SP shared(const AnyFullBlownFcValReq& anyFullBlownFcValReq,
                     const bt2c::Logger& parentLogger)
    {
        return std::make_shared<OptionalFcValReq>(anyFullBlownFcValReq, parentLogger);
    }

    static const char *typeStr() noexcept
    {
        return jsonstr::optional;
    }

private:
    void _validate(const bt2c::JsonVal& jsonVal) const override
    {
        try {
            FcValReq::_validate(jsonVal);
        } catch (const bt2c::Error&) {
            BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_RETHROW_SPEC(this->_logger(), jsonVal.loc(),
                                                              "Invalid optional field class.");
        }
    }
};

/*
 * CTF 2 JSON variant field class option class value requirement.
 */
class VariantFcOptValReq final : public bt2c::JsonObjValReq
{
public:
    explicit VariantFcOptValReq(const AnyFullBlownFcValReq& anyFullBlownFcValReq,
                                const bt2c::Logger& parentLogger) :
        /* clang-format off */
        bt2c::JsonObjValReq {{
            namePropReqEntry(false, parentLogger),
            anyFcPropReqEntry(jsonstr::fc, anyFullBlownFcValReq, parentLogger),
            selFieldRangesPropReqEntry(true, parentLogger),
            attrsPropReqEntry(parentLogger),
            extPropReqEntry(parentLogger),
        }, parentLogger}
    /* clang-format on */
    {
    }

    static SP shared(const AnyFullBlownFcValReq& anyFullBlownFcValReq,
                     const bt2c::Logger& parentLogger)
    {
        return std::make_shared<VariantFcOptValReq>(anyFullBlownFcValReq, parentLogger);
    }

private:
    void _validate(const bt2c::JsonVal& jsonVal) const override
    {
        /*
         * Not checking for integer range overlaps here because we don't
         * know the signedness of those ranges yet (depends on the
         * effective selector field class(es)).
         *
         * This will be easier to do once we know the signedness,
         * comparing only integers having the same type.
         */
        try {
            bt2c::JsonObjValReq::_validate(jsonVal);
        } catch (const bt2c::Error&) {
            BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_RETHROW_SPEC(
                this->_logger(), jsonVal.loc(), "Invalid variant field class option.");
        }
    }
};

/*
 * CTF 2 JSON variant field class value requirement.
 */
class VariantFcValReq final : public FcValReq
{
public:
    explicit VariantFcValReq(const AnyFullBlownFcValReq& anyFullBlownFcValReq,
                             const bt2c::Logger& parentLogger) :
        /* clang-format off */
        FcValReq {this->typeStr(), {
            {jsonstr::opts, {
                bt2c::JsonArrayValReq::shared(1, bt2s::nullopt,
                                              VariantFcOptValReq::shared(anyFullBlownFcValReq, parentLogger),
                                              parentLogger),
                true
            }},
            selFieldLocPropReqEntry(parentLogger),
        }, parentLogger},
        _mUniqueEntryNamesValidator {parentLogger}
    /* clang-format on */
    {
    }

    static SP shared(const AnyFullBlownFcValReq& anyFullBlownFcValReq,
                     const bt2c::Logger& parentLogger)
    {
        return std::make_shared<VariantFcValReq>(anyFullBlownFcValReq, parentLogger);
    }

    static const char *typeStr() noexcept
    {
        return jsonstr::variant;
    }

private:
    void _validate(const bt2c::JsonVal& jsonVal) const override
    {
        try {
            FcValReq::_validate(jsonVal);

            /* Validate that option names are unique */
            _mUniqueEntryNamesValidator.validate(jsonVal, jsonstr::opts,
                                                 "variant field class option");
        } catch (const bt2c::Error&) {
            BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_RETHROW_SPEC(this->_logger(), jsonVal.loc(),
                                                              "Invalid variant field class.");
        }
    }

    UniqueEntryNamesValidator _mUniqueEntryNamesValidator;
};

/*
 * CTF 2 JSON (any) full-blown field class value requirement.
 */
class AnyFullBlownFcValReq final : public bt2c::JsonObjValReq
{
public:
    explicit AnyFullBlownFcValReq(const bt2c::Logger& parentLogger) :
        /* clang-format off */
        bt2c::JsonObjValReq {{
            {
                jsonstr::type,
                {
                    bt2c::JsonStrValInSetReq::shared({
                        FixedLenBitArrayFcValReq::typeStr(),
                        FixedLenBoolFcValReq::typeStr(),
                        FixedLenBitMapFcValReq::typeStr(),
                        FixedLenUIntFcValReq::typeStr(),
                        FixedLenSIntFcValReq::typeStr(),
                        FixedLenFloatFcValReq::typeStr(),
                        VarLenUIntFcValReq::typeStr(),
                        VarLenSIntFcValReq::typeStr(),
                        NullTerminatedStrFcValReq::typeStr(),
                        StaticLenStrFcValReq::typeStr(),
                        DynLenStrFcValReq::typeStr(),
                        StaticLenBlobFcValReq::typeStr(),
                        DynLenBlobFcValReq::typeStr(),
                        StructFcValReq::typeStr(),
                        StaticLenArrayFcValReq::typeStr(),
                        DynLenArrayFcValReq::typeStr(),
                        OptionalFcValReq::typeStr(),
                        VariantFcValReq::typeStr(),
                    }, parentLogger), true
                }
            }
        }, true, parentLogger},
        _mFlBitArrayFcValReq {parentLogger},
        _mFlBoolFcValReq {parentLogger},
        _mFlBitMapFcValReq {parentLogger},
        _mFlUIntFcValReq {parentLogger},
        _mFlSIntFcValReq {parentLogger},
        _mFlFloatFcValReq {parentLogger},
        _mVlUIntFcValReq {parentLogger},
        _mVlSIntFcValReq {parentLogger},
        _mNtStrFcValReq {parentLogger},
        _mStaticLenStrFcValReq {parentLogger},
        _mDynLenStrFcValReq {parentLogger},
        _mStaticLenBlobFcValReq {parentLogger},
        _mDynLenBlobFcValReq {parentLogger},
        _mStructFcValReq {*this, parentLogger},
        _mStaticLenArrayFcValReq {*this, parentLogger},
        _mDynLenArrayFcValReq {*this, parentLogger},
        _mOptionalFcValReq {*this, parentLogger},
        _mVariantFcValReq {*this, parentLogger}
    /* clang-format on */
    {
        this->_addToFcValReqs(_mFlBitArrayFcValReq);
        this->_addToFcValReqs(_mFlBoolFcValReq);
        this->_addToFcValReqs(_mFlBitMapFcValReq);
        this->_addToFcValReqs(_mFlUIntFcValReq);
        this->_addToFcValReqs(_mFlSIntFcValReq);
        this->_addToFcValReqs(_mFlFloatFcValReq);
        this->_addToFcValReqs(_mVlUIntFcValReq);
        this->_addToFcValReqs(_mVlSIntFcValReq);
        this->_addToFcValReqs(_mNtStrFcValReq);
        this->_addToFcValReqs(_mStaticLenStrFcValReq);
        this->_addToFcValReqs(_mDynLenStrFcValReq);
        this->_addToFcValReqs(_mStaticLenBlobFcValReq);
        this->_addToFcValReqs(_mDynLenBlobFcValReq);
        this->_addToFcValReqs(_mStructFcValReq);
        this->_addToFcValReqs(_mStaticLenArrayFcValReq);
        this->_addToFcValReqs(_mDynLenArrayFcValReq);
        this->_addToFcValReqs(_mOptionalFcValReq);
        this->_addToFcValReqs(_mVariantFcValReq);
    }

private:
    template <typename JsonValReqT>
    void _addToFcValReqs(const JsonValReqT& valReq)
    {
        const auto typeStr = JsonValReqT::typeStr();

        BT_ASSERT(!bt2c::contains(_mFcValReqs, typeStr));
        _mFcValReqs.insert(std::make_pair(typeStr, &valReq));
    }

    void _validate(const bt2c::JsonVal& jsonVal) const override
    {
        try {
            bt2c::JsonObjValReq::_validate(jsonVal);
        } catch (const bt2c::Error&) {
            BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_RETHROW_SPEC(this->_logger(), jsonVal.loc(),
                                                              "Invalid field class.");
        }

        /*
         * This part doesn't need to be catched because the specific
         * _validate() method already appends a message like "Invalid
         * xyz field class:" to the exception.
         */
        const auto it = _mFcValReqs.find(*jsonVal.asObj()[jsonstr::type]->asStr());

        BT_ASSERT(it != _mFcValReqs.end());
        it->second->validate(jsonVal);
    }

    /* Subrequirements */
    FixedLenBitArrayFcValReq _mFlBitArrayFcValReq;
    FixedLenBoolFcValReq _mFlBoolFcValReq;
    FixedLenBitMapFcValReq _mFlBitMapFcValReq;
    FixedLenUIntFcValReq _mFlUIntFcValReq;
    FixedLenSIntFcValReq _mFlSIntFcValReq;
    FixedLenFloatFcValReq _mFlFloatFcValReq;
    VarLenUIntFcValReq _mVlUIntFcValReq;
    VarLenSIntFcValReq _mVlSIntFcValReq;
    NullTerminatedStrFcValReq _mNtStrFcValReq;
    StaticLenStrFcValReq _mStaticLenStrFcValReq;
    DynLenStrFcValReq _mDynLenStrFcValReq;
    StaticLenBlobFcValReq _mStaticLenBlobFcValReq;
    DynLenBlobFcValReq _mDynLenBlobFcValReq;
    StructFcValReq _mStructFcValReq;
    StaticLenArrayFcValReq _mStaticLenArrayFcValReq;
    DynLenArrayFcValReq _mDynLenArrayFcValReq;
    OptionalFcValReq _mOptionalFcValReq;
    VariantFcValReq _mVariantFcValReq;

    /*
     * Field class type string to JSON field class requirement.
     *
     * Values are owned by the members above.
     */
    std::unordered_map<std::string, const bt2c::JsonValReq *> _mFcValReqs;
};

void AnyFcValReqWrapper::_validate(const bt2c::JsonVal& jsonVal) const
{
    /* Check for field class alias name first (JSON string) */
    if (jsonVal.isStr()) {
        /*
         * Always valid: Ctf2FcBuilder::buildFcFromJsonVal() will
         * validate that the field class alias exists.
         */
        return;
    }

    /* Delegate to AnyFullBlownFcValReq::validate() */
    _mAnyFullBlownFcValReq->validate(jsonVal);
}

/*
 * CTF 2 JSON fragment value abstract requirement.
 */
class FragmentValReq : public bt2c::JsonObjValReq
{
protected:
    /*
     * Builds a CTF 2 JSON fragment value requirement of type `type`,
     * adding `propReqs` to the base JSON object value property
     * requirements.
     */
    explicit FragmentValReq(std::string&& type, PropReqs&& propReqs,
                            const bt2c::Logger& parentLogger) :
        bt2c::JsonObjValReq {
            this->_buildPropReqs(std::move(type), std::move(propReqs), parentLogger), parentLogger}
    {
    }

    /*
     * Builds a CTF 2 JSON fragment value requirement of type `type`.
     */
    explicit FragmentValReq(std::string&& type, const bt2c::Logger& parentLogger) :
        FragmentValReq {std::move(type), {}, parentLogger}
    {
    }

private:
    static PropReqs _buildPropReqs(std::string&& type, PropReqs&& propReqs,
                                   const bt2c::Logger& parentLogger)
    {
        propReqs.insert(objTypePropReqEntry(std::move(type), parentLogger));
        propReqs.insert(attrsPropReqEntry(parentLogger));
        propReqs.insert(extPropReqEntry(parentLogger));
        return std::move(propReqs);
    }
};

/*
 * CTF 2 preamble fragment value requirement.
 */
class PreambleFragmentValReq final : public FragmentValReq
{
public:
    explicit PreambleFragmentValReq(const bt2c::Logger& parentLogger) :
        /* clang-format off */
        FragmentValReq {this->typeStr(), {
            {jsonstr::version, {bt2c::JsonUIntValInSetReq::shared(2, parentLogger), true}},
            {jsonstr::uuid, {UuidValReq::shared(parentLogger)}},
        }, parentLogger}
    /* clang-format on */
    {
    }

    static SP shared(const bt2c::Logger& parentLogger)
    {
        return std::make_shared<PreambleFragmentValReq>(parentLogger);
    }

    static const char *typeStr() noexcept
    {
        return jsonstr::preamble;
    }

private:
    void _validate(const bt2c::JsonVal& jsonVal) const override
    {
        try {
            FragmentValReq::_validate(jsonVal);
        } catch (const bt2c::Error&) {
            BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_RETHROW_SPEC(this->_logger(), jsonVal.loc(),
                                                              "Invalid preamble fragment.");
        }
    }
};

/*
 * CTF 2 field class alias fragment value requirement.
 */
class FcAliasFragmentValReq final : public FragmentValReq
{
public:
    explicit FcAliasFragmentValReq(const bt2c::Logger& parentLogger) :
        /* clang-format off */
        FragmentValReq {this->typeStr(), {
            namePropReqEntry(true, parentLogger),
            anyFcPropReqEntry(jsonstr::fc, _mAnyFullBlownFcValReq, true, parentLogger),
        }, parentLogger},
        _mAnyFullBlownFcValReq {parentLogger}
    /* clang-format on */
    {
    }

    static SP shared(const bt2c::Logger& parentLogger)
    {
        return std::make_shared<FcAliasFragmentValReq>(parentLogger);
    }

    static const char *typeStr() noexcept
    {
        return jsonstr::fcAlias;
    }

private:
    void _validate(const bt2c::JsonVal& jsonVal) const override
    {
        try {
            FragmentValReq::_validate(jsonVal);
        } catch (const bt2c::Error&) {
            BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_RETHROW_SPEC(
                this->_logger(), jsonVal.loc(), "Invalid field class alias fragment.");
        }
    }

private:
    AnyFullBlownFcValReq _mAnyFullBlownFcValReq;
};

/*
 * CTF 2 JSON clock offset value requirement.
 */
class ClkOffsetValReq final : public bt2c::JsonObjValReq
{
public:
    explicit ClkOffsetValReq(const bt2c::Logger& parentLogger) :
        /* clang-format off */
        bt2c::JsonObjValReq {{
            {jsonstr::seconds, {bt2c::JsonAnyIntValReq::shared(parentLogger)}},
            {jsonstr::cycles, {bt2c::JsonValHasTypeReq::shared(bt2c::ValType::UInt, parentLogger)}},
        }, parentLogger}
    /* clang-format on */
    {
    }

    static SP shared(const bt2c::Logger& parentLogger)
    {
        return std::make_shared<ClkOffsetValReq>(parentLogger);
    }

private:
    void _validate(const bt2c::JsonVal& jsonVal) const override
    {
        try {
            bt2c::JsonObjValReq::_validate(jsonVal);
        } catch (const bt2c::Error&) {
            BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_RETHROW_SPEC(this->_logger(), jsonVal.loc(),
                                                              "Invalid clock offset.");
        }
    }
};

/*
 * Returns the pair (suitable for insertion into a
 * `bt2c::JsonObjValReq::PropReqs` instance) for the CTF 2 object
 * namespace object property requirement.
 */
bt2c::JsonObjValReq::PropReqsEntry nsPropReqEntry(const bt2c::Logger& parentLogger)
{
    return {jsonstr::ns, {bt2c::JsonValHasTypeReq::shared(bt2c::ValType::Str, parentLogger)}};
}

/*
 * Returns the pair (suitable for insertion into a
 * `bt2c::JsonObjValReq::PropReqs` instance) for the CTF 2 object UID
 * object property requirement.
 */
bt2c::JsonObjValReq::PropReqsEntry uidPropReqEntry(const bool isRequired,
                                                   const bt2c::Logger& parentLogger)
{
    return {jsonstr::uid,
            {bt2c::JsonValHasTypeReq::shared(bt2c::ValType::Str, parentLogger), isRequired}};
}

/*
 * CTF 2 JSON clock origin object value requirement.
 */
class ClkOriginObjValReq final : public bt2c::JsonObjValReq
{
public:
    explicit ClkOriginObjValReq(const bt2c::Logger& parentLogger) :
        /* clang-format off */
        bt2c::JsonObjValReq {{
            nsPropReqEntry(parentLogger),
            namePropReqEntry(true, parentLogger),
            uidPropReqEntry(true, parentLogger),
        }, parentLogger}
    /* clang-format on */
    {
    }

    static SP shared(const bt2c::Logger& parentLogger)
    {
        return std::make_shared<ClkOffsetValReq>(parentLogger);
    }
};

/*
 * CTF 2 JSON clock class origin property requirement.
 */
class ClkClsOriginValReq final : public bt2c::JsonValReq
{
public:
    explicit ClkClsOriginValReq(const bt2c::Logger& parentLogger) :
        bt2c::JsonValReq {parentLogger}, _mObjReq {parentLogger}
    {
    }

    static SP shared(const bt2c::Logger& parentLogger)
    {
        return std::make_shared<ClkClsOriginValReq>(parentLogger);
    }

private:
    void _validate(const bt2c::JsonVal& jsonVal) const override
    {
        try {
            /* Check for `unix-epoch` string */
            if (jsonVal.isStr()) {
                if (*jsonVal.asStr() != jsonstr::unixEpoch) {
                    BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_THROW_SPEC(
                        this->_logger(), bt2c::Error, jsonVal.loc(), "Expecting `{}`.",
                        jsonstr::unixEpoch);
                }
            } else {
                /* Must be a valid clock origin object */
                if (!jsonVal.isObj()) {
                    /* Make a clear message about the expected type */
                    BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_THROW_SPEC(
                        this->_logger(), bt2c::Error, jsonVal.loc(),
                        "Expecting a string or an object.");
                }

                /* Delegate to ClkOriginObjValReq::validate() */
                _mObjReq.validate(jsonVal);
            }
        } catch (const bt2c::Error&) {
            BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_RETHROW_SPEC(this->_logger(), jsonVal.loc(),
                                                              "Invalid clock origin.");
        }
    }

private:
    ClkOriginObjValReq _mObjReq;
};

/*
 * CTF 2 JSON clock class fragment value requirement.
 */
class ClkClsFragmentValReq final : public FragmentValReq
{
public:
    explicit ClkClsFragmentValReq(const bt2c::Logger& parentLogger) :
        /* clang-format off */
        FragmentValReq {this->typeStr(), {
            {jsonstr::id, {bt2c::JsonValHasTypeReq::shared(bt2c::ValType::Str, parentLogger), true}},
            nsPropReqEntry(parentLogger),
            namePropReqEntry(false, parentLogger),
            uidPropReqEntry(false, parentLogger),
            {jsonstr::freq, {bt2c::JsonUIntValInRangeReq::shared(1, bt2s::nullopt, parentLogger), true}},
            {jsonstr::descr, {bt2c::JsonValHasTypeReq::shared(bt2c::ValType::Str, parentLogger)}},
            {jsonstr::origin, {ClkClsOriginValReq::shared(parentLogger)}},
            {jsonstr::offsetFromOrigin, {ClkOffsetValReq::shared(parentLogger)}},
            {jsonstr::precision, {bt2c::JsonValHasTypeReq::shared(bt2c::ValType::UInt, parentLogger)}},
            {jsonstr::accuracy, {bt2c::JsonValHasTypeReq::shared(bt2c::ValType::UInt, parentLogger)}},
        }, parentLogger}
    /* clang-format on */
    {
    }

    static SP shared(const bt2c::Logger& parentLogger)
    {
        return std::make_shared<ClkClsFragmentValReq>(parentLogger);
    }

    static const char *typeStr() noexcept
    {
        return jsonstr::clkCls;
    }

private:
    void _validate(const bt2c::JsonVal& jsonVal) const override
    {
        try {
            FragmentValReq::_validate(jsonVal);

            /*
             * Validate that `seconds` within `offset-from-origin`, if
             * it exists, is less than `frequency`.
             */
            auto& jsonObj = jsonVal.asObj();

            if (const auto jsonOffset = jsonObj[jsonstr::offsetFromOrigin]) {
                if (const auto jsonCycles = jsonOffset->asObj()[jsonstr::cycles]) {
                    const auto cycles = *jsonCycles->asUInt();
                    const auto freq = *jsonObj[jsonstr::freq]->asUInt();

                    if (cycles >= freq) {
                        BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_THROW_SPEC(
                            this->_logger(), bt2c::Error, jsonCycles->loc(),
                            "Invalid `{}` property of `{}` property: "
                            "value {} is greater than the value of the `{}` property ({}).",
                            jsonstr::cycles, jsonstr::offsetFromOrigin, cycles, jsonstr::freq,
                            freq);
                    }
                }
            }
        } catch (const bt2c::Error&) {
            BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_RETHROW_SPEC(this->_logger(), jsonVal.loc(),
                                                              "Invalid clock class fragment.");
        }
    }
};

/*
 * CTF 2 JSON trace class fragment value requirement.
 */
class TraceClsFragmentValReq final : public FragmentValReq
{
public:
    explicit TraceClsFragmentValReq(const AnyFullBlownFcValReq& anyFullBlownFcValReq,
                                    const bt2c::Logger& parentLogger) :
        /* clang-format off */
        FragmentValReq {this->typeStr(), {
            nsPropReqEntry(parentLogger),
            namePropReqEntry(false, parentLogger),
            uidPropReqEntry(false, parentLogger),
            {jsonstr::env, {TraceEnvValReq::shared(parentLogger)}},
            anyFcPropReqEntry(jsonstr::pktHeaderFc, anyFullBlownFcValReq, parentLogger),
        }, parentLogger}
    /* clang-format on */
    {
    }

    static SP shared(const AnyFullBlownFcValReq& anyFullBlownFcValReq,
                     const bt2c::Logger& parentLogger)
    {
        return std::make_shared<TraceClsFragmentValReq>(anyFullBlownFcValReq, parentLogger);
    }

    static const char *typeStr() noexcept
    {
        return jsonstr::traceCls;
    }

private:
    void _validate(const bt2c::JsonVal& jsonVal) const override
    {
        try {
            FragmentValReq::_validate(jsonVal);
        } catch (const bt2c::Error&) {
            BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_RETHROW_SPEC(this->_logger(), jsonVal.loc(),
                                                              "Invalid trace class fragment.");
        }
    }
};

/*
 * Returns the pair (suitable for insertion into a
 * `bt2c::JsonObjValReq::PropReqs` instance) for the CTF 2 object
 * numeric ID object property requirement.
 */
bt2c::JsonObjValReq::PropReqsEntry idPropReqEntry(const bt2c::Logger& parentLogger)
{
    return {jsonstr::id, {bt2c::JsonValHasTypeReq::shared(bt2c::ValType::UInt, parentLogger)}};
}

/*
 * CTF 2 JSON data stream class fragment value requirement.
 */
class DataStreamClsFragmentValReq final : public FragmentValReq
{
public:
    explicit DataStreamClsFragmentValReq(const AnyFullBlownFcValReq& anyFullBlownFcValReq,
                                         const bt2c::Logger& parentLogger) :
        /* clang-format off */
        FragmentValReq {this->typeStr(), {
            idPropReqEntry(parentLogger),
            nsPropReqEntry(parentLogger),
            namePropReqEntry(false, parentLogger),
            uidPropReqEntry(false, parentLogger),
            {jsonstr::defClkClsId, {bt2c::JsonValHasTypeReq::shared(bt2c::ValType::Str, parentLogger)}},
            anyFcPropReqEntry(jsonstr::pktCtxFc, anyFullBlownFcValReq, parentLogger),
            anyFcPropReqEntry(jsonstr::eventRecordHeaderFc, anyFullBlownFcValReq, parentLogger),
            anyFcPropReqEntry(jsonstr::eventRecordCommonCtxFc, anyFullBlownFcValReq, parentLogger),
        }, parentLogger}
    /* clang-format on */
    {
    }

    static SP shared(const AnyFullBlownFcValReq& anyFullBlownFcValReq,
                     const bt2c::Logger& parentLogger)
    {
        return std::make_shared<DataStreamClsFragmentValReq>(anyFullBlownFcValReq, parentLogger);
    }

    static const char *typeStr() noexcept
    {
        return jsonstr::dataStreamCls;
    }

private:
    void _validate(const bt2c::JsonVal& jsonVal) const override
    {
        try {
            FragmentValReq::_validate(jsonVal);
        } catch (const bt2c::Error&) {
            BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_RETHROW_SPEC(
                this->_logger(), jsonVal.loc(), "Invalid data stream class fragment.");
        }
    }
};

/*
 * CTF 2 JSON event record class fragment value requirement.
 */
class EventRecordClsFragmentValReq final : public FragmentValReq
{
public:
    explicit EventRecordClsFragmentValReq(const AnyFullBlownFcValReq& anyFullBlownFcValReq,
                                          const bt2c::Logger& parentLogger) :
        /* clang-format off */
        FragmentValReq {this->typeStr(), {
            idPropReqEntry(parentLogger),
            nsPropReqEntry(parentLogger),
            namePropReqEntry(false, parentLogger),
            uidPropReqEntry(false, parentLogger),
            {jsonstr::dataStreamClsId, {bt2c::JsonValHasTypeReq::shared(bt2c::ValType::UInt, parentLogger)}},
            anyFcPropReqEntry(jsonstr::specCtxFc, anyFullBlownFcValReq, parentLogger),
            anyFcPropReqEntry(jsonstr::payloadFc, anyFullBlownFcValReq, parentLogger),
        }, parentLogger}
    /* clang-format on */
    {
    }

    static SP shared(const AnyFullBlownFcValReq& anyFullBlownFcValReq,
                     const bt2c::Logger& parentLogger)
    {
        return std::make_shared<EventRecordClsFragmentValReq>(anyFullBlownFcValReq, parentLogger);
    }

    static const char *typeStr() noexcept
    {
        return jsonstr::eventRecordCls;
    }

private:
    void _validate(const bt2c::JsonVal& jsonVal) const override
    {
        try {
            FragmentValReq::_validate(jsonVal);
        } catch (const bt2c::Error&) {
            BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_RETHROW_SPEC(
                this->_logger(), jsonVal.loc(), "Invalid event record class fragment.");
        }
    }
};

} /* namespace */

namespace internal {

/*
 * CTF 2 JSON (any) fragment value requirement (implementation).
 */
class Ctf2JsonAnyFragmentValReqImpl final : public bt2c::JsonObjValReq
{
public:
    explicit Ctf2JsonAnyFragmentValReqImpl(const bt2c::Logger& parentLogger) :
        /* clang-format off */
        bt2c::JsonObjValReq {{
            {
                jsonstr::type, {
                    bt2c::JsonStrValInSetReq::shared({
                        PreambleFragmentValReq::typeStr(),
                        FcAliasFragmentValReq::typeStr(),
                        TraceClsFragmentValReq::typeStr(),
                        ClkClsFragmentValReq::typeStr(),
                        DataStreamClsFragmentValReq::typeStr(),
                        EventRecordClsFragmentValReq::typeStr(),
                    }, parentLogger), true
                }
            }
        }, true, parentLogger},
        _mAnyFullBlownFcValReq {parentLogger},
        _preambleFragmentValReq {parentLogger},
        _fcAliasFragmentValReq {parentLogger},
        _traceClsFragmentValReq {_mAnyFullBlownFcValReq, parentLogger},
        _clkClsFragmentValReq {parentLogger},
        _dataStreamClsFragmentValReq {_mAnyFullBlownFcValReq, parentLogger},
        _eventRecordClsFragmentValReq {_mAnyFullBlownFcValReq, parentLogger}
    /* clang-format on */
    {
        this->_addToFcValReqs(_preambleFragmentValReq);
        this->_addToFcValReqs(_fcAliasFragmentValReq);
        this->_addToFcValReqs(_traceClsFragmentValReq);
        this->_addToFcValReqs(_clkClsFragmentValReq);
        this->_addToFcValReqs(_dataStreamClsFragmentValReq);
        this->_addToFcValReqs(_eventRecordClsFragmentValReq);
    }

    static SP shared(const bt2c::Logger& parentLogger)
    {
        return std::make_shared<Ctf2JsonAnyFragmentValReqImpl>(parentLogger);
    }

private:
    template <typename JsonValReqT>
    void _addToFcValReqs(const JsonValReqT& valReq)
    {
        const auto typeStr = JsonValReqT::typeStr();

        BT_ASSERT(!bt2c::contains(_fragValReqs, typeStr));
        _fragValReqs.insert(std::make_pair(typeStr, &valReq));
    }

    void _validate(const bt2c::JsonVal& jsonVal) const override
    {
        try {
            bt2c::JsonObjValReq::_validate(jsonVal);
        } catch (const bt2c::Error&) {
            BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_RETHROW_SPEC(this->_logger(), jsonVal.loc(),
                                                              "Invalid fragment.");
        }

        /*
         * This part doesn't need to be catched because the specific
         * _validate() method already appends a message like
         * "Invalid xyz fragment:" to the exception.
         */
        const auto it = _fragValReqs.find(*jsonVal.asObj()[jsonstr::type]->asStr());

        BT_ASSERT(it != _fragValReqs.end());
        it->second->validate(jsonVal);
    }

    /* Single any full-blown field class value requirement instance */
    AnyFullBlownFcValReq _mAnyFullBlownFcValReq;

    /* Subrequirements */
    PreambleFragmentValReq _preambleFragmentValReq;
    FcAliasFragmentValReq _fcAliasFragmentValReq;
    TraceClsFragmentValReq _traceClsFragmentValReq;
    ClkClsFragmentValReq _clkClsFragmentValReq;
    DataStreamClsFragmentValReq _dataStreamClsFragmentValReq;
    EventRecordClsFragmentValReq _eventRecordClsFragmentValReq;

    /*
     * Fragment type string to JSON fragment requirement.
     *
     * Values are owned by the members above.
     */
    std::unordered_map<std::string, const bt2c::JsonValReq *> _fragValReqs;
};

} /* namespace internal */

Ctf2JsonAnyFragmentValReq::Ctf2JsonAnyFragmentValReq(const bt2c::Logger& parentLogger) :
    bt2c::JsonValReq {parentLogger},
    _mImpl {new internal::Ctf2JsonAnyFragmentValReqImpl {parentLogger}}
{
}

Ctf2JsonAnyFragmentValReq::~Ctf2JsonAnyFragmentValReq()
{
}

void Ctf2JsonAnyFragmentValReq::_validate(const bt2c::JsonVal& jsonVal) const
{
    _mImpl->validate(jsonVal);
}

} /* namespace src */
} /* namespace ctf */
