// Copyright (C) 2020 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author Giuseppe D'Angelo <giuseppe.dangelo@kdab.com>
// Copyright (C) 2021 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only

#ifndef QMAP_H
#define QMAP_H

#include <QtCore/qiterator.h>
#include <QtCore/qlist.h>
#include <QtCore/qrefcount.h>
#include <QtCore/qpair.h>
#include <QtCore/qshareddata.h>
#include <QtCore/qshareddata_impl.h>

#include <functional>
#include <initializer_list>
#include <map>
#include <algorithm>

QT_BEGIN_NAMESPACE

// common code shared between QMap and QMultimap
template <typename AMap>
class QMapData : public QSharedData
{
public:
    using Map = AMap;
    using Key = typename Map::key_type;
    using T = typename Map::mapped_type;
    using value_type = typename Map::value_type;
    using size_type = typename Map::size_type;
    using iterator = typename Map::iterator;
    using const_iterator = typename Map::const_iterator;

    static_assert(std::is_nothrow_destructible_v<Key>, "Types with throwing destructors are not supported in Qt containers.");
    static_assert(std::is_nothrow_destructible_v<T>, "Types with throwing destructors are not supported in Qt containers.");

    Map m;

    QMapData() = default;
    explicit QMapData(const Map &other)
        : m(other)
    {}

    explicit QMapData(Map &&other)
        : m(std::move(other))
    {}

    // used in remove(); copies from source all the values not matching key.
    // returns how many were NOT copied (removed).
    size_type copyIfNotEquivalentTo(const Map &source, const Key &key)
    {
        Q_ASSERT(m.empty());

        size_type result = 0;
        const auto &keyCompare = source.key_comp();
        const auto filter = [&result, &key, &keyCompare](const auto &v)
        {
            if (!keyCompare(key, v.first) && !keyCompare(v.first, key)) {
                // keys are equivalent (neither a<b nor b<a) => found it
                ++result;
                return true;
            }
            return false;
        };

        std::remove_copy_if(source.cbegin(), source.cend(),
                            std::inserter(m, m.end()),
                            filter);
        return result;
    }

    // used in key(T), count(Key, T), find(key, T), etc; returns a
    // comparator object suitable for algorithms with std::(multi)map
    // iterators.
    static auto valueIsEqualTo(const T &value)
    {
        return [&value](const auto &v) { return v.second == value; };
    }

    Key key(const T &value, const Key &defaultKey) const
    {
        auto i = std::find_if(m.cbegin(),
                              m.cend(),
                              valueIsEqualTo(value));
        if (i != m.cend())
            return i->first;

        return defaultKey;
    }

    QList<Key> keys() const
    {
        QList<Key> result;
        result.reserve(m.size());

        const auto extractKey = [](const auto &v) { return v.first; };

        std::transform(m.cbegin(),
                       m.cend(),
                       std::back_inserter(result),
                       extractKey);
        return result;
    }

    QList<Key> keys(const T &value) const
    {
        QList<Key> result;
        result.reserve(m.size());
        // no std::transform_if...
        for (const auto &v : m) {
            if (v.second == value)
                result.append(v.first);
        }
        result.shrink_to_fit();
        return result;
    }

    QList<T> values() const
    {
        QList<T> result;
        result.reserve(m.size());

        const auto extractValue = [](const auto &v) { return v.second; };

        std::transform(m.cbegin(),
                       m.cend(),
                       std::back_inserter(result),
                       extractValue);
        return result;
    }

    size_type count(const Key &key) const
    {
        return m.count(key);
    }

    // Used in erase. Allocates a new QMapData and copies, from this->m,
    // the elements not in the [first, last) range. The return contains
    // the new QMapData and an iterator in its map pointing at the first
    // element after the erase.
    struct EraseResult {
        QMapData *data;
        iterator it;
    };

    EraseResult erase(const_iterator first, const_iterator last) const
    {
        EraseResult result;
        result.data = new QMapData;
        result.it = result.data->m.end();
        const auto newDataEnd = result.it;

        auto i = m.begin();
        const auto e = m.end();

        // copy over all the elements before first
        while (i != first) {
            result.it = result.data->m.insert(newDataEnd, *i);
            ++i;
        }

        // skip until last
        while (i != last)
            ++i;

        // copy from last to the end
        while (i != e) {
            result.data->m.insert(newDataEnd, *i);
            ++i;
        }

        if (result.it != newDataEnd)
            ++result.it;

        return result;
    }
};

//
// QMap
//

template <class Key, class T>
class QMap
{
    using Map = std::map<Key, T>;
    using MapData = QMapData<Map>;
    QtPrivate::QExplicitlySharedDataPointerV2<MapData> d;

    friend class QMultiMap<Key, T>;

public:
    using key_type = Key;
    using mapped_type = T;
    using difference_type = qptrdiff;
    using size_type = qsizetype;

    QMap() = default;

    // implicitly generated special member functions are OK!

    void swap(QMap<Key, T> &other) noexcept
    {
        d.swap(other.d);
    }

    QMap(std::initializer_list<std::pair<Key, T>> list)
    {
        for (auto &p : list)
            insert(p.first, p.second);
    }

    explicit QMap(const std::map<Key, T> &other)
        : d(other.empty() ? nullptr : new MapData(other))
    {
    }

    explicit QMap(std::map<Key, T> &&other)
        : d(other.empty() ? nullptr : new MapData(std::move(other)))
    {
    }

    std::map<Key, T> toStdMap() const &
    {
        if (d)
            return d->m;
        return {};
    }

    std::map<Key, T> toStdMap() &&
    {
        if (d) {
            if (d.isShared())
                return d->m;
            else
                return std::move(d->m);
        }

        return {};
    }

#ifndef Q_QDOC
    template <typename AKey = Key, typename AT = T> friend
    QTypeTraits::compare_eq_result_container<QMap, AKey, AT> operator==(const QMap &lhs, const QMap &rhs)
    {
        if (lhs.d == rhs.d)
            return true;
        if (!lhs.d)
            return rhs == lhs;
        Q_ASSERT(lhs.d);
        return rhs.d ? (lhs.d->m == rhs.d->m) : lhs.d->m.empty();
    }

    template <typename AKey = Key, typename AT = T> friend
    QTypeTraits::compare_eq_result_container<QMap, AKey, AT> operator!=(const QMap &lhs, const QMap &rhs)
    {
        return !(lhs == rhs);
    }
    // TODO: add the other comparison operators; std::map has them.
#else
    friend bool operator==(const QMap &lhs, const QMap &rhs);
    friend bool operator!=(const QMap &lhs, const QMap &rhs);
#endif // Q_QDOC

    size_type size() const { return d ? size_type(d->m.size()) : size_type(0); }

    bool isEmpty() const { return d ? d->m.empty() : true; }

    void detach()
    {
        if (d)
            d.detach();
        else
            d.reset(new MapData);
    }

    bool isDetached() const noexcept
    {
        return d ? !d.isShared() : false; // false makes little sense, but that's shared_null's behavior...
    }

    bool isSharedWith(const QMap<Key, T> &other) const noexcept
    {
        return d == other.d; // also this makes little sense?
    }

    void clear()
    {
        if (!d)
            return;

        if (!d.isShared())
            d->m.clear();
        else
            d.reset();
    }

    size_type remove(const Key &key)
    {
        if (!d)
            return 0;

        if (!d.isShared())
            return size_type(d->m.erase(key));

        MapData *newData = new MapData;
        size_type result = newData->copyIfNotEquivalentTo(d->m, key);

        d.reset(newData);

        return result;
    }

    template <typename Predicate>
    size_type removeIf(Predicate pred)
    {
        return QtPrivate::associative_erase_if(*this, pred);
    }

    T take(const Key &key)
    {
        if (!d)
            return T();

        const auto copy = d.isShared() ? *this : QMap(); // keep `key` alive across the detach
        // TODO: improve. There is no need of copying all the
        // elements (the one to be removed can be skipped).
        detach();

        auto i = d->m.find(key);
        if (i != d->m.end()) {
            T result(std::move(i->second));
            d->m.erase(i);
            return result;
        }
        return T();
    }

    bool contains(const Key &key) const
    {
        if (!d)
            return false;
        auto i = d->m.find(key);
        return i != d->m.end();
    }

    Key key(const T &value, const Key &defaultKey = Key()) const
    {
        if (!d)
            return defaultKey;

        return d->key(value, defaultKey);
    }

    T value(const Key &key, const T &defaultValue = T()) const
    {
        if (!d)
            return defaultValue;
        const auto i = d->m.find(key);
        if (i != d->m.cend())
            return i->second;
        return defaultValue;
    }

    T &operator[](const Key &key)
    {
        const auto copy = d.isShared() ? *this : QMap(); // keep `key` alive across the detach
        detach();
        auto i = d->m.find(key);
        if (i == d->m.end())
            i = d->m.insert({key, T()}).first;
        return i->second;
    }

    // CHANGE: return T, not const T!
    T operator[](const Key &key) const
    {
        return value(key);
    }

    QList<Key> keys() const
    {
        if (!d)
            return {};
        return d->keys();
    }

    QList<Key> keys(const T &value) const
    {
        if (!d)
            return {};
        return d->keys(value);
    }

    QList<T> values() const
    {
        if (!d)
            return {};
        return d->values();
    }

    size_type count(const Key &key) const
    {
        if (!d)
            return 0;
        return d->count(key);
    }

    size_type count() const
    {
        return size();
    }

    inline const Key &firstKey() const { Q_ASSERT(!isEmpty()); return constBegin().key(); }
    inline const Key &lastKey() const { Q_ASSERT(!isEmpty()); return (--constEnd()).key(); }

    inline T &first() { Q_ASSERT(!isEmpty()); return *begin(); }
    inline const T &first() const { Q_ASSERT(!isEmpty()); return *constBegin(); }
    inline T &last() { Q_ASSERT(!isEmpty()); return *(--end()); }
    inline const T &last() const { Q_ASSERT(!isEmpty()); return *(--constEnd()); }

    class const_iterator;

    class iterator
    {
        friend class QMap<Key, T>;
        friend class const_iterator;

        typename Map::iterator i;
        explicit iterator(typename Map::iterator it) : i(it) {}
    public:
        using iterator_category = std::bidirectional_iterator_tag;
        using difference_type = qptrdiff;
        using value_type = T;
        using pointer = T *;
        using reference = T &;

        iterator() = default;

        const Key &key() const { return i->first; }
        T &value() const { return i->second; }
        T &operator*() const { return i->second; }
        T *operator->() const { return &i->second; }
        friend bool operator==(const iterator &lhs, const iterator &rhs) { return lhs.i == rhs.i; }
        friend bool operator!=(const iterator &lhs, const iterator &rhs) { return lhs.i != rhs.i; }

        iterator &operator++()
        {
            ++i;
            return *this;
        }
        iterator operator++(int)
        {
            iterator r = *this;
            ++i;
            return r;
        }
        iterator &operator--()
        {
            --i;
            return *this;
        }
        iterator operator--(int)
        {
            iterator r = *this;
            --i;
            return r;
        }

#if QT_DEPRECATED_SINCE(6, 0)
        QT_DEPRECATED_VERSION_X_6_0("Use std::next; QMap iterators are not random access")
        //! [qmap-op-it-plus-step]
        friend iterator operator+(iterator it, difference_type j) { return std::next(it, j); }

        QT_DEPRECATED_VERSION_X_6_0("Use std::prev; QMap iterators are not random access")
        //! [qmap-op-it-minus-step]
        friend iterator operator-(iterator it, difference_type j) { return std::prev(it, j); }

        QT_DEPRECATED_VERSION_X_6_0("Use std::next or std::advance; QMap iterators are not random access")
        iterator &operator+=(difference_type j) { std::advance(*this, j); return *this; }

        QT_DEPRECATED_VERSION_X_6_0("Use std::prev or std::advance; QMap iterators are not random access")
        iterator &operator-=(difference_type j) { std::advance(*this, -j); return *this; }

        QT_DEPRECATED_VERSION_X_6_0("Use std::next; QMap iterators are not random access")
        //! [qmap-op-step-plus-it]
        friend iterator operator+(difference_type j, iterator it) { return std::next(it, j); }

        QT_DEPRECATED_VERSION_X_6_0("Use std::prev; QMap iterators are not random access")
        //! [qmap-op-step-minus-it]
        friend iterator operator-(difference_type j, iterator it) { return std::prev(it, j); }
#endif
    };

    class const_iterator
    {
        friend class QMap<Key, T>;
        typename Map::const_iterator i;
        explicit const_iterator(typename Map::const_iterator it) : i(it) {}

    public:
        using iterator_category = std::bidirectional_iterator_tag;
        using difference_type = qptrdiff;
        using value_type = T;
        using pointer = const T *;
        using reference = const T &;

        const_iterator() = default;
        Q_IMPLICIT const_iterator(const iterator &o) : i(o.i) {}

        const Key &key() const { return i->first; }
        const T &value() const { return i->second; }
        const T &operator*() const { return i->second; }
        const T *operator->() const { return &i->second; }
        friend bool operator==(const const_iterator &lhs, const const_iterator &rhs) { return lhs.i == rhs.i; }
        friend bool operator!=(const const_iterator &lhs, const const_iterator &rhs) { return lhs.i != rhs.i; }

        const_iterator &operator++()
        {
            ++i;
            return *this;
        }
        const_iterator operator++(int)
        {
            const_iterator r = *this;
            ++i;
            return r;
        }
        const_iterator &operator--()
        {
            --i;
            return *this;
        }
        const_iterator operator--(int)
        {
            const_iterator r = *this;
            --i;
            return r;
        }

#if QT_DEPRECATED_SINCE(6, 0)
        QT_DEPRECATED_VERSION_X_6_0("Use std::next; QMap iterators are not random access")
        //! [qmap-op-it-plus-step-const]
        friend const_iterator operator+(const_iterator it, difference_type j) { return std::next(it, j); }

        QT_DEPRECATED_VERSION_X_6_0("Use std::prev; QMap iterators are not random access")
        //! [qmap-op-it-minus-step-const]
        friend const_iterator operator-(const_iterator it, difference_type j) { return std::prev(it, j); }

        QT_DEPRECATED_VERSION_X_6_0("Use std::next or std::advance; QMap iterators are not random access")
        const_iterator &operator+=(difference_type j) { std::advance(*this, j); return *this; }

        QT_DEPRECATED_VERSION_X_6_0("Use std::prev or std::advance; QMap iterators are not random access")
        const_iterator &operator-=(difference_type j) { std::advance(*this, -j); return *this; }

        QT_DEPRECATED_VERSION_X_6_0("Use std::next; QMap iterators are not random access")
        //! [qmap-op-step-plus-it-const]
        friend const_iterator operator+(difference_type j, const_iterator it) { return std::next(it, j); }

        QT_DEPRECATED_VERSION_X_6_0("Use std::prev; QMap iterators are not random access")
        //! [qmap-op-step-minus-it-const]
        friend const_iterator operator-(difference_type j, const_iterator it) { return std::prev(it, j); }
#endif
    };

    class key_iterator
    {
        const_iterator i;

    public:
        typedef typename const_iterator::iterator_category iterator_category;
        typedef typename const_iterator::difference_type difference_type;
        typedef Key value_type;
        typedef const Key *pointer;
        typedef const Key &reference;

        key_iterator() = default;
        explicit key_iterator(const_iterator o) : i(o) { }

        const Key &operator*() const { return i.key(); }
        const Key *operator->() const { return &i.key(); }
        bool operator==(key_iterator o) const { return i == o.i; }
        bool operator!=(key_iterator o) const { return i != o.i; }

        inline key_iterator &operator++() { ++i; return *this; }
        inline key_iterator operator++(int) { return key_iterator(i++);}
        inline key_iterator &operator--() { --i; return *this; }
        inline key_iterator operator--(int) { return key_iterator(i--); }
        const_iterator base() const { return i; }
    };

    typedef QKeyValueIterator<const Key&, const T&, const_iterator> const_key_value_iterator;
    typedef QKeyValueIterator<const Key&, T&, iterator> key_value_iterator;

    // STL style
    iterator begin() { detach(); return iterator(d->m.begin()); }
    const_iterator begin() const { if (!d) return const_iterator(); return const_iterator(d->m.cbegin()); }
    const_iterator constBegin() const { return begin(); }
    const_iterator cbegin() const { return begin(); }
    iterator end() { detach(); return iterator(d->m.end()); }
    const_iterator end() const { if (!d) return const_iterator(); return const_iterator(d->m.end()); }
    const_iterator constEnd() const { return end(); }
    const_iterator cend() const { return end(); }
    key_iterator keyBegin() const { return key_iterator(begin()); }
    key_iterator keyEnd() const { return key_iterator(end()); }
    key_value_iterator keyValueBegin() { return key_value_iterator(begin()); }
    key_value_iterator keyValueEnd() { return key_value_iterator(end()); }
    const_key_value_iterator keyValueBegin() const { return const_key_value_iterator(begin()); }
    const_key_value_iterator constKeyValueBegin() const { return const_key_value_iterator(begin()); }
    const_key_value_iterator keyValueEnd() const { return const_key_value_iterator(end()); }
    const_key_value_iterator constKeyValueEnd() const { return const_key_value_iterator(end()); }
    auto asKeyValueRange() & { return QtPrivate::QKeyValueRange(*this); }
    auto asKeyValueRange() const & { return QtPrivate::QKeyValueRange(*this); }
    auto asKeyValueRange() && { return QtPrivate::QKeyValueRange(std::move(*this)); }
    auto asKeyValueRange() const && { return QtPrivate::QKeyValueRange(std::move(*this)); }

    iterator erase(const_iterator it)
    {
        return erase(it, std::next(it));
    }

    iterator erase(const_iterator afirst, const_iterator alast)
    {
        if (!d)
            return iterator();

        if (!d.isShared())
            return iterator(d->m.erase(afirst.i, alast.i));

        auto result = d->erase(afirst.i, alast.i);
        d.reset(result.data);
        return iterator(result.it);
    }

    // more Qt
    typedef iterator Iterator;
    typedef const_iterator ConstIterator;

    iterator find(const Key &key)
    {
        const auto copy = d.isShared() ? *this : QMap(); // keep `key` alive across the detach
        detach();
        return iterator(d->m.find(key));
    }

    const_iterator find(const Key &key) const
    {
        if (!d)
            return const_iterator();
        return const_iterator(d->m.find(key));
    }

    const_iterator constFind(const Key &key) const
    {
        return find(key);
    }

    iterator lowerBound(const Key &key)
    {
        const auto copy = d.isShared() ? *this : QMap(); // keep `key` alive across the detach
        detach();
        return iterator(d->m.lower_bound(key));
    }

    const_iterator lowerBound(const Key &key) const
    {
        if (!d)
            return const_iterator();
        return const_iterator(d->m.lower_bound(key));
    }

    iterator upperBound(const Key &key)
    {
        const auto copy = d.isShared() ? *this : QMap(); // keep `key` alive across the detach
        detach();
        return iterator(d->m.upper_bound(key));
    }

    const_iterator upperBound(const Key &key) const
    {
        if (!d)
            return const_iterator();
        return const_iterator(d->m.upper_bound(key));
    }

    iterator insert(const Key &key, const T &value)
    {
        const auto copy = d.isShared() ? *this : QMap(); // keep `key` alive across the detach
        // TODO: improve. In case of assignment, why copying first?
        detach();
        return iterator(d->m.insert_or_assign(key, value).first);
    }

    iterator insert(const_iterator pos, const Key &key, const T &value)
    {
        // TODO: improve. In case of assignment, why copying first?
        typename Map::const_iterator dpos;
        const auto copy = d.isShared() ? *this : QMap(); // keep `key`/`value` alive across the detach
        if (!d || d.isShared()) {
            auto posDistance = d ? std::distance(d->m.cbegin(), pos.i) : 0;
            detach();
            dpos = std::next(d->m.cbegin(), posDistance);
        } else {
            dpos = pos.i;
        }
        return iterator(d->m.insert_or_assign(dpos, key, value));
    }

    void insert(const QMap<Key, T> &map)
    {
        // TODO: improve. In case of assignment, why copying first?
        if (map.isEmpty())
            return;

        detach();

#ifdef __cpp_lib_node_extract
        auto copy = map.d->m;
        copy.merge(std::move(d->m));
        d->m = std::move(copy);
#else
        // this is a std::copy, but we can't use std::inserter (need insert_or_assign...).
        // copy in reverse order, trying to make effective use of insertionHint.
        auto insertionHint = d->m.end();
        auto mapIt = map.d->m.crbegin();
        auto end = map.d->m.crend();
        for (; mapIt != end; ++mapIt)
            insertionHint = d->m.insert_or_assign(insertionHint, mapIt->first, mapIt->second);
#endif
    }

    void insert(QMap<Key, T> &&map)
    {
        if (!map.d || map.d->m.empty())
            return;

        if (map.d.isShared()) {
            // fall back to a regular copy
            insert(map);
            return;
        }

        detach();

#ifdef __cpp_lib_node_extract
        map.d->m.merge(std::move(d->m));
        *this = std::move(map);
#else
        // same as above
        auto insertionHint = d->m.end();
        auto mapIt = map.d->m.crbegin();
        auto end = map.d->m.crend();
        for (; mapIt != end; ++mapIt)
            insertionHint = d->m.insert_or_assign(insertionHint, std::move(mapIt->first), std::move(mapIt->second));
#endif
    }

    // STL compatibility
    inline bool empty() const
    {
        return isEmpty();
    }

    std::pair<iterator, iterator> equal_range(const Key &akey)
    {
        const auto copy = d.isShared() ? *this : QMap(); // keep `key` alive across the detach
        detach();
        auto result = d->m.equal_range(akey);
        return {iterator(result.first), iterator(result.second)};
    }

    std::pair<const_iterator, const_iterator> equal_range(const Key &akey) const
    {
        if (!d)
            return {};
        auto result = d->m.equal_range(akey);
        return {const_iterator(result.first), const_iterator(result.second)};
    }
};

Q_DECLARE_ASSOCIATIVE_ITERATOR(Map)
Q_DECLARE_MUTABLE_ASSOCIATIVE_ITERATOR(Map)

template <typename Key, typename T, typename Predicate>
qsizetype erase_if(QMap<Key, T> &map, Predicate pred)
{
    return QtPrivate::associative_erase_if(map, pred);
}

//
// QMultiMap
//

template <class Key, class T>
class QMultiMap
{
    using Map = std::multimap<Key, T>;
    using MapData = QMapData<Map>;
    QtPrivate::QExplicitlySharedDataPointerV2<MapData> d;

public:
    using key_type = Key;
    using mapped_type = T;
    using difference_type = qptrdiff;
    using size_type = qsizetype;

    QMultiMap() = default;

    // implicitly generated special member functions are OK!

    QMultiMap(std::initializer_list<std::pair<Key,T>> list)
    {
        for (auto &p : list)
            insert(p.first, p.second);
    }

    void swap(QMultiMap<Key, T> &other) noexcept
    {
        d.swap(other.d);
    }

    explicit QMultiMap(const QMap<Key, T> &other)
        : d(other.isEmpty() ? nullptr : new MapData)
    {
        if (d) {
            Q_ASSERT(other.d);
            d->m.insert(other.d->m.begin(),
                        other.d->m.end());
        }
    }

    explicit QMultiMap(QMap<Key, T> &&other)
        : d(other.isEmpty() ? nullptr : new MapData)
    {
        if (d) {
            Q_ASSERT(other.d);
            if (other.d.isShared()) {
                d->m.insert(other.d->m.begin(),
                            other.d->m.end());
            } else {
#ifdef __cpp_lib_node_extract
                d->m.merge(std::move(other.d->m));
#else
                d->m.insert(std::make_move_iterator(other.d->m.begin()),
                            std::make_move_iterator(other.d->m.end()));
#endif
            }
        }
    }

    explicit QMultiMap(const std::multimap<Key, T> &other)
        : d(other.empty() ? nullptr : new MapData(other))
    {
    }

    explicit QMultiMap(std::multimap<Key, T> &&other)
        : d(other.empty() ? nullptr : new MapData(std::move(other)))
    {
    }

    // CHANGE: return type
    Q_DECL_DEPRECATED_X("Use toStdMultiMap instead")
    std::multimap<Key, T> toStdMap() const
    {
        return toStdMultiMap();
    }

    std::multimap<Key, T> toStdMultiMap() const &
    {
        if (d)
            return d->m;
        return {};
    }

    std::multimap<Key, T> toStdMultiMap() &&
    {
        if (d) {
            if (d.isShared())
                return d->m;
            else
                return std::move(d->m);
        }

        return {};
    }

#ifndef Q_QDOC
    template <typename AKey = Key, typename AT = T> friend
    QTypeTraits::compare_eq_result_container<QMultiMap, AKey, AT> operator==(const QMultiMap &lhs, const QMultiMap &rhs)
    {
        if (lhs.d == rhs.d)
            return true;
        if (!lhs.d)
            return rhs == lhs;
        Q_ASSERT(lhs.d);
        return rhs.d ? (lhs.d->m == rhs.d->m) : lhs.d->m.empty();
    }

    template <typename AKey = Key, typename AT = T> friend
    QTypeTraits::compare_eq_result_container<QMultiMap, AKey, AT> operator!=(const QMultiMap &lhs, const QMultiMap &rhs)
    {
        return !(lhs == rhs);
    }
    // TODO: add the other comparison operators; std::multimap has them.
#else
    friend bool operator==(const QMultiMap &lhs, const QMultiMap &rhs);
    friend bool operator!=(const QMultiMap &lhs, const QMultiMap &rhs);
#endif // Q_QDOC

    size_type size() const { return d ? size_type(d->m.size()) : size_type(0); }

    bool isEmpty() const { return d ? d->m.empty() : true; }

    void detach()
    {
        if (d)
            d.detach();
        else
            d.reset(new MapData);
    }

    bool isDetached() const noexcept
    {
        return d ? !d.isShared() : false; // false makes little sense, but that's shared_null's behavior...
    }

    bool isSharedWith(const QMultiMap<Key, T> &other) const noexcept
    {
        return d == other.d; // also this makes little sense?
    }

    void clear()
    {
        if (!d)
            return;

        if (!d.isShared())
            d->m.clear();
        else
            d.reset();
    }

    size_type remove(const Key &key)
    {
        if (!d)
            return 0;

        if (!d.isShared())
            return size_type(d->m.erase(key));

        MapData *newData = new MapData;
        size_type result = newData->copyIfNotEquivalentTo(d->m, key);

        d.reset(newData);

        return result;
    }

    size_type remove(const Key &key, const T &value)
    {
        if (!d)
            return 0;

        // key and value may belong to this map. As such, we need to copy
        // them to ensure they stay valid throughout the iteration below
        // (which may destroy them)
        const Key keyCopy = key;
        const T valueCopy = value;

        // TODO: improve. Copy over only the elements not to be removed.
        detach();

        size_type result = 0;
        const auto &keyCompare = d->m.key_comp();

        auto i = d->m.find(keyCopy);
        const auto e = d->m.end();

        while (i != e && !keyCompare(keyCopy, i->first)) {
            if (i->second == valueCopy) {
                i = d->m.erase(i);
                ++result;
            } else {
                ++i;
            }
        }

        return result;
    }

    template <typename Predicate>
    size_type removeIf(Predicate pred)
    {
        return QtPrivate::associative_erase_if(*this, pred);
    }

    T take(const Key &key)
    {
        if (!d)
            return T();

        const auto copy = d.isShared() ? *this : QMultiMap(); // keep `key` alive across the detach

        // TODO: improve. There is no need of copying all the
        // elements (the one to be removed can be skipped).
        detach();

        auto i = d->m.find(key);
        if (i != d->m.end()) {
            T result(std::move(i->second));
            d->m.erase(i);
            return result;
        }
        return T();
    }

    bool contains(const Key &key) const
    {
        if (!d)
            return false;
        auto i = d->m.find(key);
        return i != d->m.end();
    }

    bool contains(const Key &key, const T &value) const
    {
        return find(key, value) != end();
    }

    Key key(const T &value, const Key &defaultKey = Key()) const
    {
        if (!d)
            return defaultKey;

        return d->key(value, defaultKey);
    }

    T value(const Key &key, const T &defaultValue = T()) const
    {
        if (!d)
            return defaultValue;
        const auto i = d->m.find(key);
        if (i != d->m.cend())
            return i->second;
        return defaultValue;
    }

    QList<Key> keys() const
    {
        if (!d)
            return {};
        return d->keys();
    }

    QList<Key> keys(const T &value) const
    {
        if (!d)
            return {};
        return d->keys(value);
    }

    QList<Key> uniqueKeys() const
    {
        QList<Key> result;
        if (!d)
            return result;

        result.reserve(size());

        std::unique_copy(keyBegin(), keyEnd(),
                         std::back_inserter(result));

        result.shrink_to_fit();
        return result;
    }

    QList<T> values() const
    {
        if (!d)
            return {};
        return d->values();
    }

    QList<T> values(const Key &key) const
    {
        QList<T> result;
        const auto range = equal_range(key);
        result.reserve(std::distance(range.first, range.second));
        std::copy(range.first, range.second, std::back_inserter(result));
        return result;
    }

    size_type count(const Key &key) const
    {
        if (!d)
            return 0;
        return d->count(key);
    }

    size_type count(const Key &key, const T &value) const
    {
        if (!d)
            return 0;

        // TODO: improve; no need of scanning the equal_range twice.
        auto range = d->m.equal_range(key);

        return size_type(std::count_if(range.first,
                                       range.second,
                                       MapData::valueIsEqualTo(value)));
    }

    inline const Key &firstKey() const { Q_ASSERT(!isEmpty()); return constBegin().key(); }
    inline const Key &lastKey() const { Q_ASSERT(!isEmpty()); return std::next(constEnd(), -1).key(); }

    inline T &first() { Q_ASSERT(!isEmpty()); return *begin(); }
    inline const T &first() const { Q_ASSERT(!isEmpty()); return *constBegin(); }
    inline T &last() { Q_ASSERT(!isEmpty()); return *std::next(end(), -1); }
    inline const T &last() const { Q_ASSERT(!isEmpty()); return *std::next(constEnd(), -1); }

    class const_iterator;

    class iterator
    {
        friend class QMultiMap<Key, T>;
        friend class const_iterator;

        typename Map::iterator i;
        explicit iterator(typename Map::iterator it) : i(it) {}
    public:
        using iterator_category = std::bidirectional_iterator_tag;
        using difference_type = qptrdiff;
        using value_type = T;
        using pointer = T *;
        using reference = T &;

        iterator() = default;

        const Key &key() const { return i->first; }
        T &value() const { return i->second; }
        T &operator*() const { return i->second; }
        T *operator->() const { return &i->second; }
        friend bool operator==(const iterator &lhs, const iterator &rhs) { return lhs.i == rhs.i; }
        friend bool operator!=(const iterator &lhs, const iterator &rhs) { return lhs.i != rhs.i; }

        iterator &operator++()
        {
            ++i;
            return *this;
        }
        iterator operator++(int)
        {
            iterator r = *this;
            ++i;
            return r;
        }
        iterator &operator--()
        {
            --i;
            return *this;
        }
        iterator operator--(int)
        {
            iterator r = *this;
            --i;
            return r;
        }

#if QT_DEPRECATED_SINCE(6, 0)
        QT_DEPRECATED_VERSION_X_6_0("Use std::next; QMultiMap iterators are not random access")
        //! [qmultimap-op-it-plus-step]
        friend iterator operator+(iterator it, difference_type j) { return std::next(it, j); }

        QT_DEPRECATED_VERSION_X_6_0("Use std::prev; QMultiMap iterators are not random access")
        //! [qmultimap-op-it-minus-step]
        friend iterator operator-(iterator it, difference_type j) { return std::prev(it, j); }

        QT_DEPRECATED_VERSION_X_6_0("Use std::next or std::advance; QMultiMap iterators are not random access")
        iterator &operator+=(difference_type j) { std::advance(*this, j); return *this; }

        QT_DEPRECATED_VERSION_X_6_0("Use std::prev or std::advance; QMultiMap iterators are not random access")
        iterator &operator-=(difference_type j) { std::advance(*this, -j); return *this; }

        QT_DEPRECATED_VERSION_X_6_0("Use std::next; QMultiMap iterators are not random access")
        //! [qmultimap-op-step-plus-it]
        friend iterator operator+(difference_type j, iterator it) { return std::next(it, j); }

        QT_DEPRECATED_VERSION_X_6_0("Use std::prev; QMultiMap iterators are not random access")
        //! [qmultimap-op-step-minus-it]
        friend iterator operator-(difference_type j, iterator it) { return std::prev(it, j); }
#endif
    };

    class const_iterator
    {
        friend class QMultiMap<Key, T>;
        typename Map::const_iterator i;
        explicit const_iterator(typename Map::const_iterator it) : i(it) {}

    public:
        using iterator_category = std::bidirectional_iterator_tag;
        using difference_type = qptrdiff;
        using value_type = T;
        using pointer = const T *;
        using reference = const T &;

        const_iterator() = default;
        Q_IMPLICIT const_iterator(const iterator &o) : i(o.i) {}

        const Key &key() const { return i->first; }
        const T &value() const { return i->second; }
        const T &operator*() const { return i->second; }
        const T *operator->() const { return &i->second; }
        friend bool operator==(const const_iterator &lhs, const const_iterator &rhs) { return lhs.i == rhs.i; }
        friend bool operator!=(const const_iterator &lhs, const const_iterator &rhs) { return lhs.i != rhs.i; }

        const_iterator &operator++()
        {
            ++i;
            return *this;
        }
        const_iterator operator++(int)
        {
            const_iterator r = *this;
            ++i;
            return r;
        }
        const_iterator &operator--()
        {
            --i;
            return *this;
        }
        const_iterator operator--(int)
        {
            const_iterator r = *this;
            --i;
            return r;
        }

#if QT_DEPRECATED_SINCE(6, 0)
        QT_DEPRECATED_VERSION_X_6_0("Use std::next; QMultiMap iterators are not random access")
        //! [qmultimap-op-it-plus-step-const]
        friend const_iterator operator+(const_iterator it, difference_type j) { return std::next(it, j); }

        QT_DEPRECATED_VERSION_X_6_0("Use std::prev; QMultiMap iterators are not random access")
        //! [qmultimap-op-it-minus-step-const]
        friend const_iterator operator-(const_iterator it, difference_type j) { return std::prev(it, j); }

        QT_DEPRECATED_VERSION_X_6_0("Use std::next or std::advance; QMultiMap iterators are not random access")
        const_iterator &operator+=(difference_type j) { std::advance(*this, j); return *this; }

        QT_DEPRECATED_VERSION_X_6_0("Use std::prev or std::advance; QMultiMap iterators are not random access")
        const_iterator &operator-=(difference_type j) { std::advance(*this, -j); return *this; }

        QT_DEPRECATED_VERSION_X_6_0("Use std::next; QMultiMap iterators are not random access")
        //! [qmultimap-op-step-plus-it-const]
        friend const_iterator operator+(difference_type j, const_iterator it) { return std::next(it, j); }

        QT_DEPRECATED_VERSION_X_6_0("Use std::prev; QMultiMap iterators are not random access")
        //! [qmultimap-op-step-minus-it-const]
        friend const_iterator operator-(difference_type j, const_iterator it) { return std::prev(it, j); }
#endif
    };

    class key_iterator
    {
        const_iterator i;

    public:
        typedef typename const_iterator::iterator_category iterator_category;
        typedef typename const_iterator::difference_type difference_type;
        typedef Key value_type;
        typedef const Key *pointer;
        typedef const Key &reference;

        key_iterator() = default;
        explicit key_iterator(const_iterator o) : i(o) { }

        const Key &operator*() const { return i.key(); }
        const Key *operator->() const { return &i.key(); }
        bool operator==(key_iterator o) const { return i == o.i; }
        bool operator!=(key_iterator o) const { return i != o.i; }

        inline key_iterator &operator++() { ++i; return *this; }
        inline key_iterator operator++(int) { return key_iterator(i++);}
        inline key_iterator &operator--() { --i; return *this; }
        inline key_iterator operator--(int) { return key_iterator(i--); }
        const_iterator base() const { return i; }
    };

    typedef QKeyValueIterator<const Key&, const T&, const_iterator> const_key_value_iterator;
    typedef QKeyValueIterator<const Key&, T&, iterator> key_value_iterator;

    // STL style
    iterator begin() { detach(); return iterator(d->m.begin()); }
    const_iterator begin() const { if (!d) return const_iterator(); return const_iterator(d->m.cbegin()); }
    const_iterator constBegin() const { return begin(); }
    const_iterator cbegin() const { return begin(); }
    iterator end() { detach(); return iterator(d->m.end()); }
    const_iterator end() const { if (!d) return const_iterator(); return const_iterator(d->m.end()); }
    const_iterator constEnd() const { return end(); }
    const_iterator cend() const { return end(); }
    key_iterator keyBegin() const { return key_iterator(begin()); }
    key_iterator keyEnd() const { return key_iterator(end()); }
    key_value_iterator keyValueBegin() { return key_value_iterator(begin()); }
    key_value_iterator keyValueEnd() { return key_value_iterator(end()); }
    const_key_value_iterator keyValueBegin() const { return const_key_value_iterator(begin()); }
    const_key_value_iterator constKeyValueBegin() const { return const_key_value_iterator(begin()); }
    const_key_value_iterator keyValueEnd() const { return const_key_value_iterator(end()); }
    const_key_value_iterator constKeyValueEnd() const { return const_key_value_iterator(end()); }
    auto asKeyValueRange() & { return QtPrivate::QKeyValueRange(*this); }
    auto asKeyValueRange() const & { return QtPrivate::QKeyValueRange(*this); }
    auto asKeyValueRange() && { return QtPrivate::QKeyValueRange(std::move(*this)); }
    auto asKeyValueRange() const && { return QtPrivate::QKeyValueRange(std::move(*this)); }

    iterator erase(const_iterator it)
    {
        return erase(it, std::next(it));
    }

    iterator erase(const_iterator afirst, const_iterator alast)
    {
        if (!d)
            return iterator();

        if (!d.isShared())
            return iterator(d->m.erase(afirst.i, alast.i));

        auto result = d->erase(afirst.i, alast.i);
        d.reset(result.data);
        return iterator(result.it);
    }

    // more Qt
    typedef iterator Iterator;
    typedef const_iterator ConstIterator;

    size_type count() const
    {
        return size();
    }

    iterator find(const Key &key)
    {
        const auto copy = d.isShared() ? *this : QMultiMap(); // keep `key` alive across the detach
        detach();
        return iterator(d->m.find(key));
    }

    const_iterator find(const Key &key) const
    {
        if (!d)
            return const_iterator();
        return const_iterator(d->m.find(key));
    }

    const_iterator constFind(const Key &key) const
    {
        return find(key);
    }

    iterator find(const Key &key, const T &value)
    {
        const auto copy = d.isShared() ? *this : QMultiMap(); // keep `key`/`value` alive across the detach

        detach();

        auto range = d->m.equal_range(key);
        auto i = std::find_if(range.first, range.second,
                              MapData::valueIsEqualTo(value));

        if (i != range.second)
            return iterator(i);
        return iterator(d->m.end());
    }

    const_iterator find(const Key &key, const T &value) const
    {
        if (!d)
            return const_iterator();

        auto range = d->m.equal_range(key);
        auto i = std::find_if(range.first, range.second,
                              MapData::valueIsEqualTo(value));

        if (i != range.second)
            return const_iterator(i);
        return const_iterator(d->m.end());
    }

    const_iterator constFind(const Key &key, const T &value) const
    {
        return find(key, value);
    }

    iterator lowerBound(const Key &key)
    {
        const auto copy = d.isShared() ? *this : QMultiMap(); // keep `key` alive across the detach
        detach();
        return iterator(d->m.lower_bound(key));
    }

    const_iterator lowerBound(const Key &key) const
    {
        if (!d)
            return const_iterator();
        return const_iterator(d->m.lower_bound(key));
    }

    iterator upperBound(const Key &key)
    {
        const auto copy = d.isShared() ? *this : QMultiMap(); // keep `key` alive across the detach
        detach();
        return iterator(d->m.upper_bound(key));
    }

    const_iterator upperBound(const Key &key) const
    {
        if (!d)
            return const_iterator();
        return const_iterator(d->m.upper_bound(key));
    }

    iterator insert(const Key &key, const T &value)
    {
        const auto copy = d.isShared() ? *this : QMultiMap(); // keep `key`/`value` alive across the detach
        detach();
        // note that std::multimap inserts at the end of an equal_range for a key,
        // QMultiMap at the beginning.
        auto i = d->m.lower_bound(key);
        return iterator(d->m.insert(i, {key, value}));
    }

    iterator insert(const_iterator pos, const Key &key, const T &value)
    {
        const auto copy = d.isShared() ? *this : QMultiMap(); // keep `key`/`value` alive across the detach
        typename Map::const_iterator dpos;
        if (!d || d.isShared()) {
            auto posDistance = d ? std::distance(d->m.cbegin(), pos.i) : 0;
            detach();
            dpos = std::next(d->m.cbegin(), posDistance);
        } else {
            dpos = pos.i;
        }
        return iterator(d->m.insert(dpos, {key, value}));
    }

#if QT_DEPRECATED_SINCE(6, 0)
    QT_DEPRECATED_VERSION_X_6_0("Use insert() instead")
    iterator insertMulti(const Key &key, const T &value)
    {
        return insert(key, value);
    }
    QT_DEPRECATED_VERSION_X_6_0("Use insert() instead")
    iterator insertMulti(const_iterator pos, const Key &key, const T &value)
    {
        return insert(pos, key, value);
    }

    QT_DEPRECATED_VERSION_X_6_0("Use unite() instead")
    void insert(const QMultiMap<Key, T> &map)
    {
        unite(map);
    }

    QT_DEPRECATED_VERSION_X_6_0("Use unite() instead")
    void insert(QMultiMap<Key, T> &&map)
    {
        unite(std::move(map));
    }
#endif

    iterator replace(const Key &key, const T &value)
    {
        const auto copy = d.isShared() ? *this : QMultiMap(); // keep `key`/`value` alive across the detach

        // TODO: improve. No need of copying and then overwriting.
        detach();

        // Similarly, improve here (e.g. lower_bound and hinted insert);
        // there's no insert_or_assign on multimaps
        auto i = d->m.find(key);
        if (i != d->m.end())
            i->second = value;
        else
            i = d->m.insert({key, value});

        return iterator(i);
    }

    // STL compatibility
    inline bool empty() const { return isEmpty(); }

    std::pair<iterator, iterator> equal_range(const Key &akey)
    {
        const auto copy = d.isShared() ? *this : QMultiMap(); // keep `key` alive across the detach
        detach();
        auto result = d->m.equal_range(akey);
        return {iterator(result.first), iterator(result.second)};
    }

    std::pair<const_iterator, const_iterator> equal_range(const Key &akey) const
    {
        if (!d)
            return {};
        auto result = d->m.equal_range(akey);
        return {const_iterator(result.first), const_iterator(result.second)};
    }

    QMultiMap &unite(const QMultiMap &other)
    {
        if (other.isEmpty())
            return *this;

        detach();

        auto copy = other.d->m;
#ifdef __cpp_lib_node_extract
        copy.merge(std::move(d->m));
#else
        copy.insert(std::make_move_iterator(d->m.begin()),
                    std::make_move_iterator(d->m.end()));
#endif
        d->m = std::move(copy);
        return *this;
    }

    QMultiMap &unite(QMultiMap<Key, T> &&other)
    {
        if (!other.d || other.d->m.empty())
            return *this;

        if (other.d.isShared()) {
            // fall back to a regular copy
            unite(other);
            return *this;
        }

        detach();

#ifdef __cpp_lib_node_extract
        other.d->m.merge(std::move(d->m));
#else
        other.d->m.insert(std::make_move_iterator(d->m.begin()),
                          std::make_move_iterator(d->m.end()));
#endif
        *this = std::move(other);
        return *this;
    }
};

Q_DECLARE_ASSOCIATIVE_ITERATOR(MultiMap)
Q_DECLARE_MUTABLE_ASSOCIATIVE_ITERATOR(MultiMap)

template <typename Key, typename T>
QMultiMap<Key, T> operator+(const QMultiMap<Key, T> &lhs, const QMultiMap<Key, T> &rhs)
{
    auto result = lhs;
    result += rhs;
    return result;
}

template <typename Key, typename T>
QMultiMap<Key, T> operator+=(QMultiMap<Key, T> &lhs, const QMultiMap<Key, T> &rhs)
{
    return lhs.unite(rhs);
}

template <typename Key, typename T, typename Predicate>
qsizetype erase_if(QMultiMap<Key, T> &map, Predicate pred)
{
    return QtPrivate::associative_erase_if(map, pred);
}

QT_END_NAMESPACE

#endif // QMAP_H
