// Copyright (c) 2014-2026 Savoir-faire Linux Inc.
// SPDX-License-Identifier: MIT
#pragma once

#include "infohash.h"
#include "crypto.h"
#include "utils.h"
#include "sockaddr.h"

#include <msgpack.hpp>

#include <string>
#include <string_view>
#include <sstream>
#include <bitset>
#include <vector>
#include <iostream>
#include <algorithm>
#include <functional>
#include <memory>
#include <chrono>
#include <set>

#ifdef OPENDHT_JSONCPP
#include <json/json.h>
#endif

namespace dht {
using namespace std::literals;

static constexpr auto VALUE_KEY_ID("id");
static const std::string VALUE_KEY_DAT("dat");
static const std::string VALUE_KEY_PRIO("p");
static const std::string VALUE_KEY_PUSHTYPE("pt");
static const std::string VALUE_KEY_SIGNATURE("sig");

static const std::string VALUE_KEY_SEQ("seq");
static const std::string VALUE_KEY_DATA("data");
static const std::string VALUE_KEY_OWNER("owner");
static const std::string VALUE_KEY_TYPE("type");
static const std::string VALUE_KEY_TO("to");
static const std::string VALUE_KEY_BODY("body");
static const std::string VALUE_KEY_USERTYPE("utype");

struct Value;
struct Query;

/**
 * A storage policy is applied once to every incoming value storage requests.
 * If the policy returns false, the value is dropped.
 *
 * @param key: the key where the storage is requested.
 * @param value: the value to be stored. The value can be edited by the storage policy.
 * @param from: id of the requesting node.
 * @param form_addr: network address of the incoming request.
 * @param from_len: network address lendth of the incoming request.
 */
using StorePolicy
    = std::function<bool(InfoHash key, std::shared_ptr<Value>& value, const InfoHash& from, const SockAddr& addr)>;

/**
 * An edition policy is applied once to every incoming value storage requests,
 * if a value already exists for this key and value id.
 * If the policy returns false, the edition request is ignored.
 * The default behavior is to deny edition (see {ValueType::DEFAULT_EDIT_POLICY}).
 * Some {ValueType}s may override this behavior (e.g. SignedValue).
 *
 * @param key: the key where the value is stored.
 * @param old_val: the previously stored value.
 * @param new_val: the new value to be stored. The value can be edited by the edit policy.
 * @param from: id of the requesting node.
 * @param form_addr: network address of the incoming request.
 * @param from_len: network address lendth of the incoming request.
 */
using EditPolicy = std::function<bool(InfoHash key,
                                      const std::shared_ptr<Value>& old_val,
                                      std::shared_ptr<Value>& new_val,
                                      const InfoHash& from,
                                      const SockAddr& addr)>;

static constexpr const size_t MAX_VALUE_SIZE {1024 * 64};
static constexpr const duration DEFAULT_VALUE_EXPIRATION {std::chrono::minutes(10)};

struct ValueType
{
    typedef uint16_t Id;

    OPENDHT_PUBLIC static bool DEFAULT_STORE_POLICY(InfoHash,
                                                    const std::shared_ptr<Value>& v,
                                                    const InfoHash&,
                                                    const SockAddr&);
    static inline bool DEFAULT_EDIT_POLICY(
        InfoHash, const std::shared_ptr<Value>&, std::shared_ptr<Value>&, const InfoHash&, const SockAddr&)
    {
        return false;
    }

    ValueType() {}

    ValueType(Id id, std::string name, duration e = DEFAULT_VALUE_EXPIRATION)
        : id(id)
        , name(name)
        , expiration(e)
    {}

    ValueType(Id id, std::string name, duration e, StorePolicy sp, EditPolicy ep = DEFAULT_EDIT_POLICY)
        : id(id)
        , name(name)
        , expiration(e)
        , storePolicy(sp)
        , editPolicy(ep)
    {}

    virtual ~ValueType() {}

    bool operator==(const ValueType& o) { return id == o.id; }

    // Generic value type
    OPENDHT_PUBLIC static const ValueType USER_DATA;

    Id id {0};
    std::string name {};
    duration expiration {DEFAULT_VALUE_EXPIRATION};
    StorePolicy storePolicy {DEFAULT_STORE_POLICY};
    EditPolicy editPolicy {DEFAULT_EDIT_POLICY};
};

class TypeStore
{
public:
    void registerType(const ValueType& type) { types[type.id] = type; }
    const ValueType& getType(ValueType::Id type_id) const
    {
        const auto& t_it = types.find(type_id);
        return (t_it == types.end()) ? ValueType::USER_DATA : t_it->second;
    }

private:
    std::map<ValueType::Id, ValueType> types {};
};

/**
 * A "value" is data potentially stored on the Dht, with some metadata.
 *
 * It can be an IP:port announced for a service, a public key, or any kind of
 * light user-defined data (recommended: less than 512 bytes).
 *
 * Values are stored at a given InfoHash in the Dht, but also have a
 * unique ID to distinguish between values stored at the same location.
 */
struct OPENDHT_PUBLIC Value
{
    enum class Field : int {
        None = 0,
        Id,        /* Value::id */
        ValueType, /* Value::type */
        OwnerPk,   /* Value::owner */
        SeqNum,    /* Value::seq */
        UserType,  /* Value::user_type */

        COUNT /* the total number of fields */
    };

    typedef uint64_t Id;
    static const constexpr Id INVALID_ID {0};

    class Filter : public std::function<bool(const Value&)>
    {
    public:
        Filter() {}

        template<typename Functor>
        Filter(Functor f)
            : std::function<bool(const Value&)>::function(f)
        {}

        inline Filter chain(Filter&& f2)
        {
            auto f1 = *this;
            return chain(std::move(f1), std::move(f2));
        }
        inline Filter chainOr(Filter&& f2)
        {
            auto f1 = *this;
            return chainOr(std::move(f1), std::move(f2));
        }
        static inline Filter chain(Filter&& f1, Filter&& f2)
        {
            if (not f1)
                return std::move(f2);
            if (not f2)
                return std::move(f1);
            return [f1 = std::move(f1), f2 = std::move(f2)](const Value& v) {
                return f1(v) and f2(v);
            };
        }
        static inline Filter chain(const Filter& f1, const Filter& f2)
        {
            if (not f1)
                return f2;
            if (not f2)
                return f1;
            return [f1, f2](const Value& v) {
                return f1(v) and f2(v);
            };
        }
        static inline Filter chainAll(std::vector<Filter>&& set)
        {
            if (set.empty())
                return {};
            return [set = std::move(set)](const Value& v) {
                for (const auto& f : set)
                    if (f and not f(v))
                        return false;
                return true;
            };
        }
        static inline Filter chain(std::initializer_list<Filter> l)
        {
            return chainAll(std::vector<Filter>(l.begin(), l.end()));
        }
        static inline Filter chainOr(Filter&& f1, Filter&& f2)
        {
            if (not f1 or not f2)
                return {};
            return [f1 = std::move(f1), f2 = std::move(f2)](const Value& v) {
                return f1(v) or f2(v);
            };
        }
        static inline Filter notFilter(Filter&& f)
        {
            if (not f)
                return [](const Value&) {
                    return false;
                };
            return [f = std::move(f)](const Value& v) {
                return not f(v);
            };
        }
        std::vector<Sp<Value>> filter(const std::vector<Sp<Value>>& values)
        {
            if (not(*this))
                return values;
            std::vector<Sp<Value>> ret;
            for (const auto& v : values)
                if ((*this)(v))
                    ret.emplace_back(v);
            return ret;
        }
    };

    /* Sneaky functions disguised in classes */

    static inline Filter AllFilter() { return {}; }

    static inline Filter TypeFilter(const ValueType& t)
    {
        return [tid = t.id](const Value& v) {
            return v.type == tid;
        };
    }
    static inline Filter TypeFilter(const ValueType::Id& tid)
    {
        return [tid](const Value& v) {
            return v.type == tid;
        };
    }

    static inline Filter IdFilter(const Id id)
    {
        return [id](const Value& v) {
            return v.id == id;
        };
    }

    static inline Filter RecipientFilter(const InfoHash& r)
    {
        return [r](const Value& v) {
            return v.recipient == r;
        };
    }

    static inline Filter OwnerFilter(const crypto::PublicKey& pk) { return OwnerFilter(pk.getId()); }

    static inline Filter OwnerFilter(const InfoHash& pkh)
    {
        return [pkh](const Value& v) {
            return v.owner and v.owner->getId() == pkh;
        };
    }

    static inline Filter SeqNumFilter(uint16_t seq_no)
    {
        return [seq_no](const Value& v) {
            return v.seq == seq_no;
        };
    }

    static inline Filter UserTypeFilter(std::string ut)
    {
        return [ut = std::move(ut)](const Value& v) {
            return v.user_type == ut;
        };
    }

    class SerializableBase
    {
    public:
        SerializableBase() {}
        virtual ~SerializableBase() {};
        virtual const ValueType& getType() const = 0;
        virtual void unpackValue(const Value& v) = 0;
        virtual Value packValue() const = 0;
    };

    template<typename Derived, typename Base = SerializableBase>
    class Serializable : public Base
    {
    public:
        using Base::Base;

        virtual const ValueType& getType() const { return Derived::TYPE; }

        virtual void unpackValue(const Value& v)
        {
            auto msg = msgpack::unpack((const char*) v.data.data(), v.data.size());
            msg.get().convert(*static_cast<Derived*>(this));
        }

        virtual Value packValue() const { return Value {getType(), static_cast<const Derived&>(*this)}; }
    };

    template<typename T, typename std::enable_if<std::is_base_of<SerializableBase, T>::value, T>::type* = nullptr>
    static Value pack(const T& obj)
    {
        return obj.packValue();
    }

    template<typename T, typename std::enable_if<!std::is_base_of<SerializableBase, T>::value, T>::type* = nullptr>
    static Value pack(const T& obj)
    {
        return {ValueType::USER_DATA.id, packMsg<T>(obj)};
    }

    template<typename T, typename std::enable_if<std::is_base_of<SerializableBase, T>::value, T>::type* = nullptr>
    static T unpack(const Value& v)
    {
        T msg;
        msg.unpackValue(v);
        return msg;
    }

    template<typename T, typename std::enable_if<!std::is_base_of<SerializableBase, T>::value, T>::type* = nullptr>
    static T unpack(const Value& v)
    {
        return unpackMsg<T>(v.data);
    }

    template<typename T>
    T unpack()
    {
        return unpack<T>(*this);
    }

    inline bool isEncrypted() const { return not cypher.empty(); }
    inline bool isSigned() const { return owner and not signature.empty(); }

    /**
     * Sign the value using the provided private key.
     * Afterward, checkSignature() will return true and owner will
     * be set to the corresponding public key.
     */
    void sign(const crypto::PrivateKey& key);

    /**
     * Check that the value is signed and that the signature matches.
     * If true, the owner field will contain the signer public key.
     */
    inline bool checkSignature() const { return isSigned() and owner->checkSignature(getToSign(), signature); }

    inline std::shared_ptr<crypto::PublicKey> getOwner() const { return owner; }

    /**
     * Sign the value with from and returns the encrypted version for to.
     */
    Value encrypt(const crypto::PrivateKey& from, const crypto::PublicKey& to);

    Value() {}

    Value(Id id)
        : id(id)
    {}

    /** Generic constructor */
    Value(ValueType::Id t, const Blob& data, Id id = INVALID_ID)
        : id(id)
        , type(t)
        , data(data)
    {}
    Value(ValueType::Id t, Blob&& data, Id id = INVALID_ID)
        : id(id)
        , type(t)
        , data(std::move(data))
    {}
    Value(ValueType::Id t, const uint8_t* dat_ptr, size_t dat_len, Id id = INVALID_ID)
        : id(id)
        , type(t)
        , data(dat_ptr, dat_ptr + dat_len)
    {}

#ifdef OPENDHT_JSONCPP
    /**
     * Build a value from a json object
     * @param json
     */
    Value(const Json::Value& json);
#endif

    template<typename Type>
    Value(ValueType::Id t, const Type& d, Id id = INVALID_ID)
        : id(id)
        , type(t)
        , data(packMsg(d))
    {}

    template<typename Type>
    Value(const ValueType& t, const Type& d, Id id = INVALID_ID)
        : id(id)
        , type(t.id)
        , data(packMsg(d))
    {}

    /** Custom user data constructor */
    Value(const Blob& userdata)
        : data(userdata)
    {}
    Value(Blob&& userdata)
        : data(std::move(userdata))
    {}
    Value(const uint8_t* dat_ptr, size_t dat_len)
        : data(dat_ptr, dat_ptr + dat_len)
    {}

    Value(Value&& o) noexcept
        : id(o.id)
        , owner(std::move(o.owner))
        , recipient(o.recipient)
        , type(o.type)
        , data(std::move(o.data))
        , user_type(std::move(o.user_type))
        , seq(o.seq)
        , signature(std::move(o.signature))
        , cypher(std::move(o.cypher))
        , priority(o.priority)
        , pushType(std::move(o.pushType))
    {}

    template<typename Type>
    Value(const Type& vs)
        : Value(pack<Type>(vs))
    {}

    /**
     * Unpack a serialized value
     */
    Value(const msgpack::object& o) { msgpack_unpack(o); }

    /**
     * Returns true if value contents are equals (not considering the value ID)
     */
    inline bool contentEquals(const Value& o) const
    {
        return isEncrypted() ? cypher == o.cypher
                             : ((owner == o.owner || (owner and o.owner and *owner == *o.owner)) && type == o.type
                                && data == o.data && user_type == o.user_type && signature == o.signature);
    }

    inline bool operator==(const Value& o) const { return id == o.id and contentEquals(o); }
    inline bool operator!=(const Value& o) const { return !(*this == o); }

    inline void setRecipient(const InfoHash& r) { recipient = r; }

    inline void setCypher(Blob&& c) { cypher = std::move(c); }

    /**
     * Pack part of the data to be signed (must always be done the same way)
     */
    inline Blob getToSign() const
    {
        msgpack::sbuffer buffer;
        msgpack::packer<msgpack::sbuffer> pk(&buffer);
        msgpack_pack_to_sign(pk);
        return {buffer.data(), buffer.data() + buffer.size()};
    }

    /**
     * Pack part of the data to be encrypted
     */
    inline Blob getToEncrypt() const
    {
        msgpack::sbuffer buffer;
        msgpack::packer<msgpack::sbuffer> pk(&buffer);
        msgpack_pack_to_encrypt(pk);
        return {buffer.data(), buffer.data() + buffer.size()};
    }

    /** print value for debugging */
    OPENDHT_PUBLIC friend std::ostream& operator<<(std::ostream& s, const Value& v);

    inline std::string toString() const
    {
        std::ostringstream ss;
        ss << *this;
        return ss.str();
    }

#ifdef OPENDHT_JSONCPP
    /**
     * Build a json object from a value
     * Example:
     * {
     *  "data":"base64ofdata",
     *   id":"0", "seq":0,"type":3
     * }
     */
    Json::Value toJson() const;
#endif

    /** Return the size in bytes used by this value in memory (minimum). */
    size_t size() const;

    template<typename Packer>
    void msgpack_pack_to_sign(Packer& pk) const
    {
        bool has_owner = owner && *owner;
        pk.pack_map((user_type.empty() ? 0 : 1) + (has_owner ? (recipient ? 5 : 4) : 2));
        if (has_owner) { // isSigned
            pk.pack(VALUE_KEY_SEQ);
            pk.pack(seq);
            pk.pack(VALUE_KEY_OWNER);
            owner->msgpack_pack(pk);
            if (recipient) {
                pk.pack(VALUE_KEY_TO);
                pk.pack(recipient);
            }
        }
        pk.pack(VALUE_KEY_TYPE);
        pk.pack(type);
        pk.pack(VALUE_KEY_DATA);
        pk.pack_bin(data.size());
        pk.pack_bin_body((const char*) data.data(), data.size());
        if (not user_type.empty()) {
            pk.pack(VALUE_KEY_USERTYPE);
            pk.pack(user_type);
        }
    }

    template<typename Packer>
    void msgpack_pack_to_encrypt(Packer& pk) const
    {
        if (isEncrypted()) {
            pk.pack_bin(cypher.size());
            pk.pack_bin_body((const char*) cypher.data(), cypher.size());
        } else {
            pk.pack_map(isSigned() ? 2 : 1);
            pk.pack(VALUE_KEY_BODY);
            msgpack_pack_to_sign(pk);
            if (isSigned()) {
                pk.pack(VALUE_KEY_SIGNATURE);
                pk.pack_bin(signature.size());
                pk.pack_bin_body((const char*) signature.data(), signature.size());
            }
        }
    }

    template<typename Packer>
    void msgpack_pack(Packer& pk) const
    {
        pk.pack_map(2 + (priority ? 1 : 0) + (!pushType.empty() ? 1 : 0));
        pk.pack(VALUE_KEY_ID);
        pk.pack(id);
        pk.pack(VALUE_KEY_DAT);
        msgpack_pack_to_encrypt(pk);
        if (priority) {
            pk.pack(VALUE_KEY_PRIO);
            pk.pack(priority);
        }
        if (!pushType.empty()) {
            pk.pack(VALUE_KEY_PUSHTYPE);
            pk.pack(pushType);
        }
    }

    template<typename Packer>
    void msgpack_pack_fields(const std::set<Value::Field>& fields, Packer& pk) const
    {
        for (const auto& field : fields)
            switch (field) {
            case Value::Field::Id:
                pk.pack(static_cast<uint64_t>(id));
                break;
            case Value::Field::ValueType:
                pk.pack(static_cast<uint64_t>(type));
                break;
            case Value::Field::OwnerPk:
                if (owner)
                    owner->msgpack_pack(pk);
                else
                    InfoHash().msgpack_pack(pk);
                break;
            case Value::Field::SeqNum:
                pk.pack(static_cast<uint64_t>(seq));
                break;
            case Value::Field::UserType:
                pk.pack(user_type);
                break;
            default:
                break;
            }
    }

    void msgpack_unpack(const msgpack::object& o);
    void msgpack_unpack_body(const msgpack::object& o);
    Blob getPacked() const
    {
        msgpack::sbuffer buffer;
        msgpack::packer<msgpack::sbuffer> pk(&buffer);
        pk.pack(*this);
        return {buffer.data(), buffer.data() + buffer.size()};
    }

    void msgpack_unpack_fields(const std::set<Value::Field>& fields, const msgpack::object& o, unsigned offset);

    Id id {INVALID_ID};

    /**
     * Public key of the signer.
     */
    std::shared_ptr<crypto::PublicKey> owner {};

    /**
     * Hash of the recipient (optional).
     * Should only be present for encrypted values.
     * Can optionally be present for signed values.
     */
    InfoHash recipient {};

    /**
     * Type of data.
     */
    ValueType::Id type {ValueType::USER_DATA.id};
    Blob data {};

    /**
     * Custom user-defined type
     */
    std::string user_type {};

    /**
     * Sequence number to avoid replay attacks
     */
    uint16_t seq {0};

    /**
     * Optional signature.
     */
    Blob signature {};

    /**
     * Hold encrypted version of the data.
     */
    Blob cypher {};

    /**
     * Priority of this value (used for push notifications)
     * 0 = high priority
     * 1 = normal priority
     */
    unsigned priority {0};

    /**
     * Push notification type — set by the sender so that the proxy server
     * can include it in the FCM/APNs payload without decrypting the value.
     * Example: "audioCall", "videoCall"
     */
    std::string pushType {};

    inline bool isSignatureChecked() const { return signatureChecked; }
    inline bool isDecrypted() const { return decrypted; }
    bool checkSignature();
    Sp<Value> decrypt(const crypto::PrivateKey& key);

private:
    /* Cache for crypto ops */
    bool signatureChecked {false};
    bool signatureValid {false};
    bool decrypted {false};
    Sp<Value> decryptedValue {};
};

using ValuesExport = std::pair<InfoHash, Blob>;

/**
 * @class   FieldValue
 * @brief   Describes a value filter.
 * @details
 * This structure holds the value for a specified field. It's type can either be
 * uint64_t, InfoHash or Blob.
 */
struct OPENDHT_PUBLIC FieldValue
{
    FieldValue() {}
    FieldValue(Value::Field f, uint64_t int_value)
        : field(f)
        , intValue(int_value)
    {}
    FieldValue(Value::Field f, InfoHash hash_value)
        : field(f)
        , hashValue(hash_value)
    {}
    FieldValue(Value::Field f, Blob blob_value)
        : field(f)
        , blobValue(std::move(blob_value))
    {}

    bool operator==(const FieldValue& fd) const;

    // accessors
    Value::Field getField() const { return field; }
    uint64_t getInt() const { return intValue; }
    InfoHash getHash() const { return hashValue; }
    Blob getBlob() const { return blobValue; }

    template<typename Packer>
    void msgpack_pack(Packer& p) const
    {
        p.pack_map(2);
        p.pack("f"sv);
        p.pack(static_cast<uint8_t>(field));

        p.pack("v"sv);
        switch (field) {
        case Value::Field::Id:
        case Value::Field::ValueType:
            p.pack(intValue);
            break;
        case Value::Field::OwnerPk:
            p.pack(hashValue);
            break;
        case Value::Field::UserType:
            p.pack_bin(blobValue.size());
            p.pack_bin_body((const char*) blobValue.data(), blobValue.size());
            break;
        default:
            throw msgpack::type_error();
        }
    }

    void msgpack_unpack(const msgpack::object& msg)
    {
        hashValue = {};
        blobValue.clear();

        if (auto f = findMapValue(msg, "f"sv))
            field = (Value::Field) f->as<unsigned>();
        else
            throw msgpack::type_error();

        auto v = findMapValue(msg, "v"sv);
        if (not v)
            throw msgpack::type_error();
        else
            switch (field) {
            case Value::Field::Id:
            case Value::Field::ValueType:
                intValue = v->as<decltype(intValue)>();
                break;
            case Value::Field::OwnerPk:
                hashValue = v->as<decltype(hashValue)>();
                break;
            case Value::Field::UserType:
                blobValue = unpackBlob(*v);
                break;
            default:
                throw msgpack::type_error();
            }
    }

    Value::Filter getLocalFilter() const;

private:
    Value::Field field {Value::Field::None};
    // three possible value types
    uint64_t intValue {};
    InfoHash hashValue {};
    Blob blobValue {};
};

/**
 * @class   Select
 * @brief   Serializable Value field selection.
 * @details
 * This is a container for a list of FieldSelectorDescription instances. It
 * describes a complete SELECT query for dht::Value.
 */
struct OPENDHT_PUBLIC Select
{
    Select() {}
    Select(std::string_view q_str);

    bool isSatisfiedBy(const Select& os) const;

    /**
     * Selects a field of type Value::Field.
     *
     * @param field  the field to require.
     *
     * @return the resulting Select instance.
     */
    Select& field(Value::Field field)
    {
        if (std::find(fieldSelection_.begin(), fieldSelection_.end(), field) == fieldSelection_.end())
            fieldSelection_.emplace_back(field);
        return *this;
    }

    /**
     * Computes the set of selected fields based on previous require* calls.
     *
     * @return the set of fields.
     */
    std::set<Value::Field> getSelection() const { return {fieldSelection_.begin(), fieldSelection_.end()}; }

    template<typename Packer>
    void msgpack_pack(Packer& pk) const
    {
        pk.pack(fieldSelection_);
    }
    void msgpack_unpack(const msgpack::object& o) { fieldSelection_ = o.as<decltype(fieldSelection_)>(); }

    std::string toString() const
    {
        std::ostringstream ss;
        ss << *this;
        return ss.str();
    }

    bool empty() const { return fieldSelection_.empty(); }

    OPENDHT_PUBLIC friend std::ostream& operator<<(std::ostream& s, const dht::Select& q);

private:
    std::vector<Value::Field> fieldSelection_ {};
};

/**
 * @class   Where
 * @brief   Serializable dht::Value filter.
 * @details
 * This is container for a list of FieldValue instances. It describes a
 * complete WHERE query for dht::Value.
 */
struct OPENDHT_PUBLIC Where
{
    Where() {}
    Where(std::string_view q_str);

    bool isSatisfiedBy(const Where& where) const;

    /**
     * Adds restriction on Value::Id based on the id argument.
     *
     * @param id  the id.
     *
     * @return the resulting Where instance.
     */
    Where&& id(Value::Id id)
    {
        FieldValue fv {Value::Field::Id, id};
        if (std::find(filters_.begin(), filters_.end(), fv) == filters_.end())
            filters_.emplace_back(std::move(fv));
        return std::move(*this);
    }

    /**
     * Adds restriction on Value::ValueType based on the type argument.
     *
     * @param type  the value type.
     *
     * @return the resulting Where instance.
     */
    Where&& valueType(ValueType::Id type)
    {
        FieldValue fv {Value::Field::ValueType, type};
        if (std::find(filters_.begin(), filters_.end(), fv) == filters_.end())
            filters_.emplace_back(std::move(fv));
        return std::move(*this);
    }

    /**
     * Adds restriction on Value::OwnerPk based on the owner_pk_hash argument.
     *
     * @param owner_pk_hash  the owner public key fingerprint.
     *
     * @return the resulting Where instance.
     */
    Where&& owner(InfoHash owner_pk_hash)
    {
        FieldValue fv {Value::Field::OwnerPk, owner_pk_hash};
        if (std::find(filters_.begin(), filters_.end(), fv) == filters_.end())
            filters_.emplace_back(std::move(fv));
        return std::move(*this);
    }

    /**
     * Adds restriction on Value::OwnerPk based on the owner_pk_hash argument.
     *
     * @param owner_pk_hash  the owner public key fingerprint.
     *
     * @return the resulting Where instance.
     */
    Where&& seq(uint16_t seq_no)
    {
        FieldValue fv {Value::Field::SeqNum, seq_no};
        if (std::find(filters_.begin(), filters_.end(), fv) == filters_.end())
            filters_.emplace_back(std::move(fv));
        return std::move(*this);
    }

    /**
     * Adds restriction on Value::UserType based on the user_type argument.
     *
     * @param user_type  the user type.
     *
     * @return the resulting Where instance.
     */
    Where&& userType(std::string_view user_type)
    {
        FieldValue fv {
            Value::Field::UserType, Blob {user_type.begin(), user_type.end()}
        };
        if (std::find(filters_.begin(), filters_.end(), fv) == filters_.end())
            filters_.emplace_back(std::move(fv));
        return std::move(*this);
    }

    /**
     * Computes the Value::Filter based on the list of field value set.
     *
     * @return the resulting Value::Filter.
     */
    Value::Filter getFilter() const
    {
        if (filters_.empty())
            return {};
        if (filters_.size() == 1)
            return filters_[0].getLocalFilter();
        std::vector<Value::Filter> fset;
        fset.reserve(filters_.size());
        for (const auto& f : filters_) {
            if (auto lf = f.getLocalFilter())
                fset.emplace_back(std::move(lf));
        }
        return Value::Filter::chainAll(std::move(fset));
    }

    template<typename Packer>
    void msgpack_pack(Packer& pk) const
    {
        pk.pack(filters_);
    }
    void msgpack_unpack(const msgpack::object& o)
    {
        filters_.clear();
        filters_ = o.as<decltype(filters_)>();
    }

    std::string toString() const
    {
        std::ostringstream ss;
        ss << *this;
        return ss.str();
    }

    bool empty() const { return filters_.empty(); }

    OPENDHT_PUBLIC friend std::ostream& operator<<(std::ostream& s, const dht::Where& q);

private:
    std::vector<FieldValue> filters_;
};

/**
 * @class   Query
 * @brief   Describes a query destined to another peer.
 * @details
 * This class describes the list of filters on field values and the field
 * itselves to include in the peer response to a GET operation. See
 * FieldValue.
 */
struct OPENDHT_PUBLIC Query
{
    static const std::string QUERY_PARSE_ERROR;

    Query(Select s = {}, Where w = {}, bool none = false)
        : select(std::move(s))
        , where(std::move(w))
        , none(none) {};

    /**
     * Initializes a query based on a SQL-ish formatted string. The abstract
     * form of such a string is the following:
     *
     *  [SELECT $field$ [WHERE $field$=$value$]]
     *
     *  where
     *
     *  - $field$ = *|id|value_type|owner_pk|user_type
     *  - $value$ = $string$|$integer$
     *  - $string$: a simple string WITHOUT SPACES.
     *  - $integer$: a simple integer.
     */
    Query(std::string_view q_str)
    {
        auto pos_W = q_str.find("WHERE");
        auto pos_w = q_str.find("where");
        auto pos = std::min(pos_W != std::string_view::npos ? pos_W : q_str.size(),
                            pos_w != std::string_view::npos ? pos_w : q_str.size());
        select = q_str.substr(0, pos);
        where = q_str.substr(pos, q_str.size() - pos);
    }

    /**
     * Tell if the query is satisfied by another query.
     */
    bool isSatisfiedBy(const Query& q) const;

    template<typename Packer>
    void msgpack_pack(Packer& pk) const
    {
        pk.pack_map(2);
        pk.pack("s"sv);
        pk.pack(select); /* packing field selectors */
        pk.pack("w"sv);
        pk.pack(where); /* packing filters */
    }

    void msgpack_unpack(const msgpack::object& o);

    std::string toString() const
    {
        std::ostringstream ss;
        ss << *this;
        return ss.str();
    }

    friend std::ostream& operator<<(std::ostream& s, const dht::Query& q)
    {
        return s << "Query[" << q.select << " " << q.where << "]";
    }

    Select select {};
    Where where {};
    bool none {false}; /* When true, any query satisfies this. */
};

/*!
 * @class   FieldValueIndex
 * @brief   An index for field values.
 * @details
 * This structures is meant to manipulate a subset of fields normally contained
 * in Value.
 */
struct OPENDHT_PUBLIC FieldValueIndex
{
    FieldValueIndex() {}
    FieldValueIndex(const Value& v, const Select& s = {});
    /**
     * Tells if all the fields of this are contained in the other
     * FieldValueIndex with the same value.
     *
     * @param other  The other FieldValueIndex instance.
     */
    bool containedIn(const FieldValueIndex& other) const;

    OPENDHT_PUBLIC friend std::ostream& operator<<(std::ostream& os, const FieldValueIndex& fvi);

    void msgpack_unpack_fields(const std::set<Value::Field>& fields, const msgpack::object& o, unsigned offset);

    std::map<Value::Field, FieldValue> index {};
};

template<typename T, typename std::enable_if<std::is_base_of<Value::SerializableBase, T>::value, T>::type* = nullptr>
Value::Filter
getFilterSet(Value::Filter f)
{
    return Value::Filter::chain({Value::TypeFilter(T::TYPE), T::getFilter(), std::move(f)});
}

template<typename T, typename std::enable_if<!std::is_base_of<Value::SerializableBase, T>::value, T>::type* = nullptr>
Value::Filter
getFilterSet(Value::Filter f)
{
    return f;
}

template<typename T, typename std::enable_if<std::is_base_of<Value::SerializableBase, T>::value, T>::type* = nullptr>
Value::Filter
getFilterSet()
{
    return Value::Filter::chain({Value::TypeFilter(T::TYPE), T::getFilter()});
}

template<typename T, typename std::enable_if<!std::is_base_of<Value::SerializableBase, T>::value, T>::type* = nullptr>
Value::Filter
getFilterSet()
{
    return {};
}

template<class T>
std::vector<T>
unpackVector(const std::vector<std::shared_ptr<Value>>& vals)
{
    std::vector<T> ret;
    ret.reserve(vals.size());
    for (const auto& v : vals) {
        try {
            ret.emplace_back(Value::unpack<T>(*v));
        } catch (const std::exception&) {
        }
    }
    return ret;
}

#ifdef OPENDHT_JSONCPP
uint64_t unpackId(const Json::Value& json, const std::string& key);
#endif

} // namespace dht

MSGPACK_ADD_ENUM(dht::Value::Field)
