/****************************************************************************
**
** Copyright (C) 2020 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the QtCore module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 3 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL3 included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 3 requirements
** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 2.0 or (at your option) the GNU General
** Public license version 3 or any later version approved by the KDE Free
** Qt Foundation. The licenses are as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-2.0.html and
** https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/

#ifndef QMETACONTAINER_H
#define QMETACONTAINER_H

#include <QtCore/qcontainerinfo.h>
#include <QtCore/qflags.h>
#include <QtCore/qglobal.h>

#include <iterator>

QT_BEGIN_NAMESPACE

class QMetaType;
namespace QtPrivate {
class QMetaTypeInterface;
template<typename T>
constexpr const QMetaTypeInterface *qMetaTypeInterfaceForType();
}

namespace QtMetaContainerPrivate {

enum IteratorCapability : quint8 {
    InputCapability         = 1 << 0,
    ForwardCapability       = 1 << 1,
    BiDirectionalCapability = 1 << 2,
    RandomAccessCapability  = 1 << 3,
};

Q_DECLARE_FLAGS(IteratorCapabilities, IteratorCapability)
Q_DECLARE_OPERATORS_FOR_FLAGS(IteratorCapabilities)

enum AddRemoveCapability : quint8 {
    CanAddAtBegin    = 1 << 0,
    CanRemoveAtBegin = 1 << 1,
    CanAddAtEnd      = 1 << 2,
    CanRemoveAtEnd   = 1 << 3
};
Q_DECLARE_FLAGS(AddRemoveCapabilities, AddRemoveCapability)
Q_DECLARE_OPERATORS_FOR_FLAGS(AddRemoveCapabilities)

class QMetaContainerInterface
{
public:
    enum Position : quint8 { AtBegin, AtEnd, Unspecified };
    ushort revision = 0;
    IteratorCapabilities iteratorCapabilities;

    using SizeFn = qsizetype(*)(const void *);
    SizeFn sizeFn;
    using ClearFn = void(*)(void *);
    ClearFn clearFn;

    using CreateIteratorFn = void *(*)(void *, Position);
    CreateIteratorFn createIteratorFn;
    using DestroyIteratorFn = void(*)(const void *);
    DestroyIteratorFn destroyIteratorFn;
    using CompareIteratorFn = bool(*)(const void *, const void *);
    CompareIteratorFn compareIteratorFn;
    using CopyIteratorFn = void(*)(void *, const void *);
    CopyIteratorFn copyIteratorFn;
    using AdvanceIteratorFn = void(*)(void *, qsizetype);
    AdvanceIteratorFn advanceIteratorFn;
    using DiffIteratorFn = qsizetype(*)(const void *, const void *);
    DiffIteratorFn diffIteratorFn;

    using CreateConstIteratorFn = void *(*)(const void *, Position);
    CreateConstIteratorFn createConstIteratorFn;
    DestroyIteratorFn destroyConstIteratorFn;
    CompareIteratorFn compareConstIteratorFn;
    CopyIteratorFn copyConstIteratorFn;
    AdvanceIteratorFn advanceConstIteratorFn;
    DiffIteratorFn diffConstIteratorFn;

    QMetaContainerInterface() = default;

    template<typename MetaContainer>
    constexpr QMetaContainerInterface(const MetaContainer &)
        : iteratorCapabilities(MetaContainer::getIteratorCapabilities())
        , sizeFn(MetaContainer::getSizeFn())
        , clearFn(MetaContainer::getClearFn())
        , createIteratorFn(MetaContainer::getCreateIteratorFn())
        , destroyIteratorFn(MetaContainer::getDestroyIteratorFn())
        , compareIteratorFn(MetaContainer::getCompareIteratorFn())
        , copyIteratorFn(MetaContainer::getCopyIteratorFn())
        , advanceIteratorFn(MetaContainer::getAdvanceIteratorFn())
        , diffIteratorFn(MetaContainer::getDiffIteratorFn())
        , createConstIteratorFn(MetaContainer::getCreateConstIteratorFn())
        , destroyConstIteratorFn(MetaContainer::getDestroyConstIteratorFn())
        , compareConstIteratorFn(MetaContainer::getCompareConstIteratorFn())
        , copyConstIteratorFn(MetaContainer::getCopyConstIteratorFn())
        , advanceConstIteratorFn(MetaContainer::getAdvanceConstIteratorFn())
        , diffConstIteratorFn(MetaContainer::getDiffConstIteratorFn())
    {}
};

class QMetaSequenceInterface : public QMetaContainerInterface
{
public:
    const QtPrivate::QMetaTypeInterface *valueMetaType;
    AddRemoveCapabilities addRemoveCapabilities;

    using ValueAtIndexFn = void(*)(const void *, qsizetype, void *);
    ValueAtIndexFn valueAtIndexFn;
    using SetValueAtIndexFn = void(*)(void *, qsizetype, const void *);
    SetValueAtIndexFn setValueAtIndexFn;

    using AddValueFn = void(*)(void *, const void *, Position);
    AddValueFn addValueFn;
    using RemoveValueFn = void(*)(void *, Position);
    RemoveValueFn removeValueFn;

    using ValueAtIteratorFn = void(*)(const void *, void *);
    ValueAtIteratorFn valueAtIteratorFn;
    using SetValueAtIteratorFn = void(*)(const void *, const void *);
    SetValueAtIteratorFn setValueAtIteratorFn;
    using InsertValueAtIteratorFn = void(*)(void *, const void *, const void *);
    InsertValueAtIteratorFn insertValueAtIteratorFn;

    ValueAtIteratorFn valueAtConstIteratorFn;

    using EraseValueAtIteratorFn = void(*)(void *, const void *);
    EraseValueAtIteratorFn eraseValueAtIteratorFn;

    using EraseRangeAtIteratorFn = void(*)(void *, const void *, const void *);
    EraseRangeAtIteratorFn eraseRangeAtIteratorFn;

    QMetaSequenceInterface() = default;

    template<typename MetaSequence>
    constexpr QMetaSequenceInterface(const MetaSequence &m)
        : QMetaContainerInterface(m)
        , valueMetaType(MetaSequence::getValueMetaType())
        , addRemoveCapabilities(MetaSequence::getAddRemoveCapabilities())
        , valueAtIndexFn(MetaSequence::getValueAtIndexFn())
        , setValueAtIndexFn(MetaSequence::getSetValueAtIndexFn())
        , addValueFn(MetaSequence::getAddValueFn())
        , removeValueFn(MetaSequence::getRemoveValueFn())
        , valueAtIteratorFn(MetaSequence::getValueAtIteratorFn())
        , setValueAtIteratorFn(MetaSequence::getSetValueAtIteratorFn())
        , insertValueAtIteratorFn(MetaSequence::getInsertValueAtIteratorFn())
        , valueAtConstIteratorFn(MetaSequence::getValueAtConstIteratorFn())
        , eraseValueAtIteratorFn(MetaSequence::getEraseValueAtIteratorFn())
        , eraseRangeAtIteratorFn(MetaSequence::getEraseRangeAtIteratorFn())
    {}
};

class QMetaAssociationInterface : public QMetaContainerInterface
{
public:
    const QtPrivate::QMetaTypeInterface *keyMetaType;
    const QtPrivate::QMetaTypeInterface *mappedMetaType;

    using InsertKeyFn = void(*)(void *, const void *);
    InsertKeyFn insertKeyFn;
    using RemoveKeyFn = void(*)(void *, const void *);
    RemoveKeyFn removeKeyFn;
    using ContainsKeyFn = bool(*)(const void *, const void *);
    ContainsKeyFn containsKeyFn;

    using MappedAtKeyFn = void(*)(const void *, const void *, void *);
    MappedAtKeyFn mappedAtKeyFn;
    using SetMappedAtKeyFn = void(*)(void *, const void *, const void *);
    SetMappedAtKeyFn setMappedAtKeyFn;

    using CreateIteratorAtKeyFn = void *(*)(void *, const void *);
    CreateIteratorAtKeyFn createIteratorAtKeyFn;
    using CreateConstIteratorAtKeyFn = void *(*)(const void *, const void *);
    CreateConstIteratorAtKeyFn createConstIteratorAtKeyFn;

    using KeyAtIteratorFn = void(*)(const void *, void *);
    KeyAtIteratorFn keyAtIteratorFn;
    KeyAtIteratorFn keyAtConstIteratorFn;

    using MappedAtIteratorFn = void(*)(const void *, void *);
    MappedAtIteratorFn mappedAtIteratorFn;
    MappedAtIteratorFn mappedAtConstIteratorFn;

    using SetMappedAtIteratorFn = void(*)(const void *, const void *);
    SetMappedAtIteratorFn setMappedAtIteratorFn;

    using EraseKeyAtIteratorFn = void(*)(void *, const void *);
    EraseKeyAtIteratorFn eraseKeyAtIteratorFn;

    QMetaAssociationInterface() = default;

    template<typename MetaAssociation>
    constexpr QMetaAssociationInterface(const MetaAssociation &m)
        : QMetaContainerInterface(m)
        , keyMetaType(MetaAssociation::getKeyMetaType())
        , mappedMetaType(MetaAssociation::getMappedMetaType())
        , insertKeyFn(MetaAssociation::getInsertKeyFn())
        , removeKeyFn(MetaAssociation::getRemoveKeyFn())
        , containsKeyFn(MetaAssociation::getContainsKeyFn())
        , mappedAtKeyFn(MetaAssociation::getMappedAtKeyFn())
        , setMappedAtKeyFn(MetaAssociation::getSetMappedAtKeyFn())
        , createIteratorAtKeyFn(MetaAssociation::createIteratorAtKeyFn())
        , createConstIteratorAtKeyFn(MetaAssociation::createConstIteratorAtKeyFn())
        , keyAtIteratorFn(MetaAssociation::getKeyAtIteratorFn())
        , keyAtConstIteratorFn(MetaAssociation::getKeyAtConstIteratorFn())
        , mappedAtIteratorFn(MetaAssociation::getMappedAtIteratorFn())
        , mappedAtConstIteratorFn(MetaAssociation::getMappedAtConstIteratorFn())
        , setMappedAtIteratorFn(MetaAssociation::getSetMappedAtIteratorFn())
        , eraseKeyAtIteratorFn(MetaAssociation::getEraseKeyAtIteratorFn())
    {}
};

template<typename C>
class QMetaContainerForContainer
{
    friend QMetaContainerInterface;

    template <typename Iterator>
    static constexpr IteratorCapabilities capabilitiesForIterator()
    {
       using Tag = typename std::iterator_traits<Iterator>::iterator_category;
       IteratorCapabilities caps {};
       if constexpr (std::is_base_of_v<std::input_iterator_tag, Tag>)
           caps |= InputCapability;
       if constexpr (std::is_base_of_v<std::forward_iterator_tag, Tag>)
           caps |= ForwardCapability;
       if constexpr (std::is_base_of_v<std::bidirectional_iterator_tag, Tag>)
           caps |= BiDirectionalCapability;
       if constexpr (std::is_base_of_v<std::random_access_iterator_tag, Tag>)
           caps |= RandomAccessCapability;
       return caps;
    }

    static constexpr IteratorCapabilities getIteratorCapabilities()
    {
        if constexpr (QContainerInfo::has_iterator_v<C> && !std::is_const_v<C>)
            return capabilitiesForIterator<QContainerInfo::iterator<C>>();
        else if constexpr (QContainerInfo::has_const_iterator_v<C>)
            return capabilitiesForIterator<QContainerInfo::const_iterator<C>>();
        else
            return {};
    }

    static constexpr QMetaContainerInterface::SizeFn getSizeFn()
    {
        if constexpr (QContainerInfo::has_size_v<C>) {
            return [](const void *c) -> qsizetype { return static_cast<const C *>(c)->size(); };
        } else {
            return nullptr;
        }
    }

    static constexpr QMetaContainerInterface::ClearFn getClearFn()
    {
        if constexpr (QContainerInfo::has_clear_v<C>) {
            return [](void *c) { return static_cast<C *>(c)->clear(); };
        } else {
            return nullptr;
        }
    }

    static constexpr QMetaContainerInterface::CreateIteratorFn getCreateIteratorFn()
    {
        if constexpr (QContainerInfo::has_iterator_v<C> && !std::is_const_v<C>) {
            return [](void *c, QMetaContainerInterface::Position p) -> void* {
                using Iterator = QContainerInfo::iterator<C>;
                switch (p) {
                case QMetaContainerInterface::Unspecified:
                    return new Iterator;
                case QMetaContainerInterface::AtBegin:
                    return new Iterator(static_cast<C *>(c)->begin());
                    break;
                case QMetaContainerInterface::AtEnd:
                    return new Iterator(static_cast<C *>(c)->end());
                    break;
                }
                return nullptr;
            };
        } else {
            return nullptr;
        }
    }

    static constexpr QMetaContainerInterface::DestroyIteratorFn getDestroyIteratorFn()
    {
        if constexpr (QContainerInfo::has_iterator_v<C> && !std::is_const_v<C>) {
            return [](const void *i) {
                using Iterator = QContainerInfo::iterator<C>;
                delete static_cast<const Iterator *>(i);
            };
        } else {
            return nullptr;
        }
    }

    static constexpr QMetaContainerInterface::CompareIteratorFn getCompareIteratorFn()
    {
        if constexpr (QContainerInfo::has_iterator_v<C> && !std::is_const_v<C>) {
            return [](const void *i, const void *j) {
                using Iterator = QContainerInfo::iterator<C>;
                return *static_cast<const Iterator *>(i) == *static_cast<const Iterator *>(j);
            };
        } else {
            return nullptr;
        }
    }

    static constexpr QMetaContainerInterface::CopyIteratorFn getCopyIteratorFn()
    {
        if constexpr (QContainerInfo::has_iterator_v<C> && !std::is_const_v<C>) {
            return [](void *i, const void *j) {
                using Iterator = QContainerInfo::iterator<C>;
                *static_cast<Iterator *>(i) = *static_cast<const Iterator *>(j);
            };
        } else {
            return nullptr;
        }
    }

    static constexpr QMetaContainerInterface::AdvanceIteratorFn getAdvanceIteratorFn()
    {
        if constexpr (QContainerInfo::has_iterator_v<C> && !std::is_const_v<C>) {
            return [](void *i, qsizetype step) {
                std::advance(*static_cast<QContainerInfo::iterator<C> *>(i), step);
            };
        } else {
            return nullptr;
        }
    }

    static constexpr QMetaContainerInterface::DiffIteratorFn getDiffIteratorFn()
    {
        if constexpr (QContainerInfo::has_iterator_v<C> && !std::is_const_v<C>) {
            return [](const void *i, const void *j) -> qsizetype {
                return std::distance(*static_cast<const QContainerInfo::iterator<C> *>(j),
                                     *static_cast<const QContainerInfo::iterator<C> *>(i));
            };
        } else {
            return nullptr;
        }
    }

    static constexpr QMetaContainerInterface::CreateConstIteratorFn getCreateConstIteratorFn()
    {
        if constexpr (QContainerInfo::has_const_iterator_v<C>) {
            return [](const void *c, QMetaContainerInterface::Position p) -> void* {
                using Iterator = QContainerInfo::const_iterator<C>;
                switch (p) {
                case QMetaContainerInterface::Unspecified:
                    return new Iterator;
                case QMetaContainerInterface::AtBegin:
                    return new Iterator(static_cast<const C *>(c)->begin());
                case QMetaContainerInterface::AtEnd:
                    return new Iterator(static_cast<const C *>(c)->end());
                }
                return nullptr;
            };
        } else {
            return nullptr;
        }
    }

    static constexpr QMetaContainerInterface::DestroyIteratorFn getDestroyConstIteratorFn()
    {
        if constexpr (QContainerInfo::has_const_iterator_v<C>) {
            return [](const void *i) {
                using Iterator = QContainerInfo::const_iterator<C>;
                delete static_cast<const Iterator *>(i);
            };
        } else {
            return nullptr;
        }
    }

    static constexpr QMetaContainerInterface::CompareIteratorFn getCompareConstIteratorFn()
    {
        if constexpr (QContainerInfo::has_const_iterator_v<C>) {
            return [](const void *i, const void *j) {
                using Iterator = QContainerInfo::const_iterator<C>;
                return *static_cast<const Iterator *>(i) == *static_cast<const Iterator *>(j);
            };
        } else {
            return nullptr;
        }
    }

    static constexpr QMetaContainerInterface::CopyIteratorFn getCopyConstIteratorFn()
    {
        if constexpr (QContainerInfo::has_const_iterator_v<C>) {
            return [](void *i, const void *j) {
                using Iterator = QContainerInfo::const_iterator<C>;
                *static_cast<Iterator *>(i) = *static_cast<const Iterator *>(j);
            };
        } else {
            return nullptr;
        }
    }

    static constexpr QMetaContainerInterface::AdvanceIteratorFn getAdvanceConstIteratorFn()
    {
        if constexpr (QContainerInfo::has_const_iterator_v<C>) {
            return [](void *i, qsizetype step) {
                std::advance(*static_cast<QContainerInfo::const_iterator<C> *>(i), step);
            };
        } else {
            return nullptr;
        }
    }

    static constexpr QMetaContainerInterface::DiffIteratorFn getDiffConstIteratorFn()
    {
        if constexpr (QContainerInfo::has_const_iterator_v<C>) {
            return [](const void *i, const void *j) -> qsizetype {
                return std::distance(*static_cast<const QContainerInfo::const_iterator<C> *>(j),
                                     *static_cast<const QContainerInfo::const_iterator<C> *>(i));
            };
        } else {
            return nullptr;
        }
    }

protected:

    template<typename EraseFn>
    static constexpr EraseFn getEraseAtIteratorFn()
    {
        if constexpr (QContainerInfo::has_iterator_v<C>
                && QContainerInfo::can_erase_at_iterator_v<C> && !std::is_const_v<C>) {
            return [](void *c, const void *i) {
                static_cast<C *>(c)->erase(*static_cast<const QContainerInfo::iterator<C> *>(i));
            };
        } else {
            return nullptr;
        }
    }
};

template<typename C>
class QMetaSequenceForContainer : public QMetaContainerForContainer<C>
{
    friend QMetaSequenceInterface;

    static constexpr const QtPrivate::QMetaTypeInterface *getValueMetaType()
    {
        if constexpr (QContainerInfo::has_value_type_v<C>)
            return QtPrivate::qMetaTypeInterfaceForType<typename C::value_type>();
        else
            return nullptr;
    }

    static constexpr AddRemoveCapabilities getAddRemoveCapabilities()
    {
        AddRemoveCapabilities caps;
        if constexpr (QContainerInfo::has_push_back_v<C>)
            caps |= CanAddAtEnd;
        if constexpr (QContainerInfo::has_pop_back_v<C>)
            caps |= CanRemoveAtEnd;
        if constexpr (QContainerInfo::has_push_front_v<C>)
            caps |= CanAddAtBegin;
        if constexpr (QContainerInfo::has_pop_front_v<C>)
            caps |= CanRemoveAtBegin;
        return caps;
    }

    static constexpr QMetaSequenceInterface::ValueAtIndexFn getValueAtIndexFn()
    {
        if constexpr (QContainerInfo::has_at_index_v<C>) {
            return [](const void *c, qsizetype i, void *r) {
                *static_cast<QContainerInfo::value_type<C> *>(r)
                        = static_cast<const C *>(c)->at(i);
            };
        } else if constexpr (QContainerInfo::can_get_at_index_v<C>) {
            return [](const void *c, qsizetype i, void *r) {
                *static_cast<QContainerInfo::value_type<C> *>(r)
                        = (*static_cast<const C *>(c))[i];
            };
        } else {
            return nullptr;
        }
    }

    static constexpr QMetaSequenceInterface::SetValueAtIndexFn getSetValueAtIndexFn()
    {
        if constexpr (QContainerInfo::can_set_at_index_v<C>) {
            return [](void *c, qsizetype i, const void *e) {
                (*static_cast<C *>(c))[i]
                        = *static_cast<const QContainerInfo::value_type<C> *>(e);
            };
        } else {
            return nullptr;
        }
    }

    static constexpr QMetaSequenceInterface::AddValueFn getAddValueFn()
    {
        if constexpr (QContainerInfo::has_push_back_v<C>) {
            if constexpr (QContainerInfo::has_push_front_v<C>) {
                return [](void *c, const void *v, QMetaSequenceInterface::Position position) {
                    const auto &value = *static_cast<const QContainerInfo::value_type<C> *>(v);
                    switch (position) {
                    case QMetaSequenceInterface::AtBegin:
                        static_cast<C *>(c)->push_front(value);
                        break;
                    case QMetaSequenceInterface::AtEnd:
                    case QMetaSequenceInterface::Unspecified:
                        static_cast<C *>(c)->push_back(value);
                        break;
                    }
                };
            } else {
                return [](void *c, const void *v, QMetaSequenceInterface::Position position) {
                    const auto &value = *static_cast<const QContainerInfo::value_type<C> *>(v);
                    switch (position) {
                    case QMetaSequenceInterface::AtBegin:
                        break;
                    case QMetaSequenceInterface::AtEnd:
                    case QMetaSequenceInterface::Unspecified:
                        static_cast<C *>(c)->push_back(value);
                        break;
                    }
                };
            }
        } else if constexpr (QContainerInfo::has_push_front_v<C>) {
            return [](void *c, const void *v, QMetaSequenceInterface::Position position) {
                const auto &value = *static_cast<const QContainerInfo::value_type<C> *>(v);
                switch (position) {
                case QMetaSequenceInterface::Unspecified:
                case QMetaSequenceInterface::AtBegin:
                    static_cast<C *>(c)->push_front(value);
                case QMetaSequenceInterface::AtEnd:
                    break;
                }
            };
        } else if constexpr (QContainerInfo::has_insert_v<C>) {
            return [](void *c, const void *v, QMetaSequenceInterface::Position position) {
                if (position == QMetaSequenceInterface::Unspecified) {
                    static_cast<C *>(c)->insert(
                                *static_cast<const QContainerInfo::value_type<C> *>(v));
                }
            };
        } else {
            return nullptr;
        }
    }

    static constexpr QMetaSequenceInterface::RemoveValueFn getRemoveValueFn()
    {
        if constexpr (QContainerInfo::has_pop_back_v<C>) {
            if constexpr (QContainerInfo::has_pop_front_v<C>) {
                return [](void *c, QMetaSequenceInterface::Position position) {
                    switch (position) {
                    case QMetaSequenceInterface::AtBegin:
                        static_cast<C *>(c)->pop_front();
                        break;
                    case QMetaSequenceInterface::AtEnd:
                    case QMetaSequenceInterface::Unspecified:
                        static_cast<C *>(c)->pop_back();
                        break;
                    }
                };
            } else {
                return [](void *c, QMetaSequenceInterface::Position position) {
                    switch (position) {
                    case QMetaSequenceInterface::AtBegin:
                        break;
                    case QMetaSequenceInterface::Unspecified:
                    case QMetaSequenceInterface::AtEnd:
                        static_cast<C *>(c)->pop_back();
                        break;
                    }
                };
            }
        } else if constexpr (QContainerInfo::has_pop_front_v<C>) {
            return [](void *c, QMetaSequenceInterface::Position position) {
                switch (position) {
                case QMetaSequenceInterface::Unspecified:
                case QMetaSequenceInterface::AtBegin:
                    static_cast<C *>(c)->pop_front();
                    break;
                case QMetaSequenceInterface::AtEnd:
                    break;
                }
            };
        } else {
            return nullptr;
        }
    }

    static constexpr QMetaSequenceInterface::ValueAtIteratorFn getValueAtIteratorFn()
    {
        if constexpr (QContainerInfo::has_iterator_v<C>
                && QContainerInfo::iterator_dereferences_to_value_v<C> && !std::is_const_v<C>) {
            return [](const void *i, void *r) {
                *static_cast<QContainerInfo::value_type<C> *>(r) =
                        *(*static_cast<const QContainerInfo::iterator<C> *>(i));
            };
        } else {
            return nullptr;
        }
    }

    static constexpr QMetaSequenceInterface::SetValueAtIteratorFn getSetValueAtIteratorFn()
    {
        if constexpr (QContainerInfo::has_iterator_v<C>
                && QContainerInfo::can_set_value_at_iterator_v<C> && !std::is_const_v<C>) {
            return [](const void *i, const void *e) {
                *(*static_cast<const QContainerInfo::iterator<C> *>(i))
                        = *static_cast<const QContainerInfo::value_type<C> *>(e);
            };
        } else {
            return nullptr;
        }
    }

    static constexpr QMetaSequenceInterface::InsertValueAtIteratorFn getInsertValueAtIteratorFn()
    {
        if constexpr (QContainerInfo::has_iterator_v<C>
                && QContainerInfo::can_insert_value_at_iterator_v<C> && !std::is_const_v<C>) {
            return [](void *c, const void *i, const void *e) {
                static_cast<C *>(c)->insert(
                            *static_cast<const QContainerInfo::iterator<C> *>(i),
                            *static_cast<const QContainerInfo::value_type<C> *>(e));
            };
        } else {
            return nullptr;
        }
    }

    static constexpr QMetaSequenceInterface::ValueAtIteratorFn getValueAtConstIteratorFn()
    {
        if constexpr (QContainerInfo::has_const_iterator_v<C>
                && QContainerInfo::iterator_dereferences_to_value_v<C>) {
            return [](const void *i, void *r) {
                *static_cast<QContainerInfo::value_type<C> *>(r) =
                        *(*static_cast<const QContainerInfo::const_iterator<C> *>(i));
            };
        } else {
            return nullptr;
        }
    }

    static constexpr QMetaSequenceInterface::EraseValueAtIteratorFn getEraseValueAtIteratorFn()
    {
        return QMetaContainerForContainer<C>::template getEraseAtIteratorFn<
                QMetaSequenceInterface::EraseValueAtIteratorFn>();
    }

    static constexpr QMetaSequenceInterface::EraseRangeAtIteratorFn getEraseRangeAtIteratorFn()
    {
        if constexpr (QContainerInfo::has_iterator_v<C>
                && QContainerInfo::can_erase_range_at_iterator_v<C> && !std::is_const_v<C>) {
            return [](void *c, const void *i, const void *j) {
                static_cast<C *>(c)->erase(*static_cast<const QContainerInfo::iterator<C> *>(i),
                                           *static_cast<const QContainerInfo::iterator<C> *>(j));
            };
        } else {
            return nullptr;
        }
    }
};

template<typename C>
class QMetaAssociationForContainer : public QMetaContainerForContainer<C>
{
    friend QMetaAssociationInterface;

    static constexpr const QtPrivate::QMetaTypeInterface *getKeyMetaType()
    {
        if constexpr (QContainerInfo::has_key_type_v<C>)
            return QtPrivate::qMetaTypeInterfaceForType<typename C::key_type>();
        else
            return nullptr;
    }

    static constexpr const QtPrivate::QMetaTypeInterface *getMappedMetaType()
    {
        if constexpr (QContainerInfo::has_mapped_type_v<C>)
            return QtPrivate::qMetaTypeInterfaceForType<typename C::mapped_type>();
        else
            return nullptr;
    }

    static constexpr QMetaAssociationInterface::InsertKeyFn getInsertKeyFn()
    {
        if constexpr (QContainerInfo::can_insert_key_v<C>) {
            return [](void *c, const void *k) {
                static_cast<C *>(c)->insert(
                            *static_cast<const QContainerInfo::key_type<C> *>(k));
            };
        } else if constexpr (QContainerInfo::can_insert_pair_v<C>) {
            return [](void *c, const void *k) {
                static_cast<C *>(c)->insert(
                            {*static_cast<const QContainerInfo::key_type<C> *>(k), {}});
            };
        } else if constexpr (QContainerInfo::can_insert_key_mapped_v<C>) {
            return [](void *c, const void *k) {
                static_cast<C *>(c)->insert(
                            *static_cast<const QContainerInfo::key_type<C> *>(k), {});
            };
        } else {
            return nullptr;
        }
    }

    static constexpr QMetaAssociationInterface::RemoveKeyFn getRemoveKeyFn()
    {
        if constexpr (QContainerInfo::can_erase_at_key_v<C>) {
            return [](void *c, const void *k) {
                static_cast<C *>(c)->erase(*static_cast<const QContainerInfo::key_type<C> *>(k));
            };
        } else if constexpr (QContainerInfo::can_remove_at_key_v<C>) {
            return [](void *c, const void *k) {
                static_cast<C *>(c)->remove(*static_cast<const QContainerInfo::key_type<C> *>(k));
            };
        } else {
            return nullptr;
        }
    }

    static constexpr QMetaAssociationInterface::ContainsKeyFn getContainsKeyFn()
    {
        if constexpr (QContainerInfo::has_contains_v<C>) {
            return [](const void *c, const void *k) {
                return static_cast<const C *>(c)->contains(
                            *static_cast<const QContainerInfo::key_type<C> *>(k));
            };
        } else if (QContainerInfo::has_find_v<C>) {
            return [](const void *c, const void *k) {
                const C *container = static_cast<const C *>(c);
                return container->find(
                            *static_cast<const QContainerInfo::key_type<C> *>(k))
                        != container->end();
            };
        } else {
            return nullptr;
        }
    }

    static constexpr QMetaAssociationInterface::MappedAtKeyFn getMappedAtKeyFn()
    {
        if constexpr (QContainerInfo::has_at_key_v<C>) {
            return [](const void *c, const void *k, void *r) {
                *static_cast<QContainerInfo::mapped_type<C> *>(r)
                        = static_cast<const C *>(c)->at(
                                *static_cast<const QContainerInfo::key_type<C> *>(k));
            };
        } else if constexpr (QContainerInfo::can_get_at_key_v<C>) {
            return [](const void *c, const void *k, void *r) {
                *static_cast<QContainerInfo::mapped_type<C> *>(r)
                        = (*static_cast<const C *>(c))[
                                *static_cast<const QContainerInfo::key_type<C> *>(k)];
            };
        } else {
            return nullptr;
        }
    }

    static constexpr QMetaAssociationInterface::SetMappedAtKeyFn getSetMappedAtKeyFn()
    {
        if constexpr (QContainerInfo::can_set_at_key_v<C>) {
            return [](void *c, const void *k, const void *m) {
                (*static_cast<C *>(c))[*static_cast<const QContainerInfo::key_type<C> *>(k)] =
                        *static_cast<const QContainerInfo::mapped_type<C> *>(m);
            };
        } else {
            return nullptr;
        }
    }

    static constexpr QMetaAssociationInterface::CreateIteratorAtKeyFn createIteratorAtKeyFn()
    {
        if constexpr (QContainerInfo::has_find_v<C>) {
            return [](void *c, const void *k) -> void* {
                using Iterator = QContainerInfo::iterator<C>;
                return new Iterator(static_cast<C *>(c)->find(
                            *static_cast<const QContainerInfo::key_type<C> *>(k)));
            };
        } else {
            return nullptr;
        }
    }

    static constexpr QMetaAssociationInterface::CreateConstIteratorAtKeyFn createConstIteratorAtKeyFn()
    {
        if constexpr (QContainerInfo::has_find_v<C>) {
            return [](const void *c, const void *k) -> void* {
                using Iterator = QContainerInfo::const_iterator<C>;
                return new Iterator(static_cast<const C *>(c)->find(
                            *static_cast<const QContainerInfo::key_type<C> *>(k)));
            };
        } else {
            return nullptr;
        }
    }

    template<typename Iterator>
    static constexpr QMetaAssociationInterface::KeyAtIteratorFn keyAtIteratorFn()
    {
        if constexpr (QContainerInfo::iterator_has_key_v<C>) {
            return [](const void *i, void *k) {
                *static_cast<QContainerInfo::key_type<C> *>(k)
                        = static_cast<const Iterator *>(i)->key();
            };
        } else if constexpr (QContainerInfo::iterator_dereferences_to_value_v<C>
                && QContainerInfo::value_type_has_first_v<C>) {
            return [](const void *i, void *k) {
                *static_cast<QContainerInfo::key_type<C> *>(k)
                        = (*static_cast<const Iterator *>(i))->first;
            };
        } else if constexpr (QContainerInfo::iterator_dereferences_to_key_v<C>) {
            return [](const void *i, void *k) {
                *static_cast<QContainerInfo::key_type<C> *>(k)
                        = *(*static_cast<const Iterator *>(i));
            };
        } else {
            return nullptr;
        }
    }

    static constexpr QMetaAssociationInterface::KeyAtIteratorFn getKeyAtIteratorFn()
    {
        return keyAtIteratorFn<QContainerInfo::iterator<C>>();
    }

    static constexpr QMetaAssociationInterface::KeyAtIteratorFn getKeyAtConstIteratorFn()
    {
        return keyAtIteratorFn<QContainerInfo::const_iterator<C>>();
    }

    template<typename Iterator>
    static constexpr QMetaAssociationInterface::MappedAtIteratorFn mappedAtIteratorFn()
    {
        if constexpr (QContainerInfo::iterator_has_value_v<C>) {
            return [](const void *i, void *k) {
                *static_cast<QContainerInfo::mapped_type<C> *>(k)
                        = static_cast<const Iterator *>(i)->value();
            };
        } else if constexpr (QContainerInfo::iterator_dereferences_to_value_v<C>
                && QContainerInfo::value_type_has_second_v<C>) {
            return [](const void *i, void *k) {
                *static_cast<QContainerInfo::mapped_type<C> *>(k)
                        = (*static_cast<const Iterator *>(i))->second;
            };
        } else if constexpr (QContainerInfo::iterator_dereferences_to_mapped_v<C>) {
            return [](const void *i, void *k) {
                *static_cast<QContainerInfo::mapped_type<C> *>(k)
                        = *static_cast<const Iterator *>(i);
            };
        } else {
            return nullptr;
        }
    }

    static constexpr QMetaAssociationInterface::MappedAtIteratorFn getMappedAtIteratorFn()
    {
        return mappedAtIteratorFn<QContainerInfo::iterator<C>>();
    }

    static constexpr QMetaAssociationInterface::MappedAtIteratorFn getMappedAtConstIteratorFn()
    {
        return mappedAtIteratorFn<QContainerInfo::const_iterator<C>>();
    }

    static constexpr QMetaAssociationInterface::SetMappedAtIteratorFn getSetMappedAtIteratorFn()
    {
        if constexpr (QContainerInfo::can_set_mapped_at_iterator_v<C> && !std::is_const_v<C>) {
            return [](const void *i, const void *m) {
                *(*static_cast<const QContainerInfo::iterator<C> *>(i))
                        = *static_cast<const QContainerInfo::mapped_type<C> *>(m);
            };
        } else if constexpr (QContainerInfo::iterator_dereferences_to_value_v<C>
                && QContainerInfo::value_type_has_second_v<C>) {
            return [](const void *i, const void *m) {
                (*static_cast<const QContainerInfo::iterator<C> *>(i))->second
                        = *static_cast<const QContainerInfo::mapped_type<C> *>(m);
            };
        } else {
            return nullptr;
        }
    }

    static constexpr QMetaAssociationInterface::EraseKeyAtIteratorFn getEraseKeyAtIteratorFn()
    {
        return QMetaContainerForContainer<C>::template getEraseAtIteratorFn<
                QMetaAssociationInterface::EraseKeyAtIteratorFn>();
    }
};

} // namespace QtMetaContainerPrivate

class Q_CORE_EXPORT QMetaContainer
{
public:
    QMetaContainer() = default;
    explicit QMetaContainer(const QtMetaContainerPrivate::QMetaContainerInterface *d) : d_ptr(d) {}

    bool hasInputIterator() const;
    bool hasForwardIterator() const;
    bool hasBidirectionalIterator() const;
    bool hasRandomAccessIterator() const;

    bool hasSize() const;
    qsizetype size(const void *container) const;

    bool canClear() const;
    void clear(void *container) const;

    bool hasIterator() const;
    void *begin(void *container) const;
    void *end(void *container) const;
    void destroyIterator(const void *iterator) const;
    bool compareIterator(const void *i, const void *j) const;
    void copyIterator(void *target, const void *source) const;
    void advanceIterator(void *iterator, qsizetype step) const;
    qsizetype diffIterator(const void *i, const void *j) const;

    bool hasConstIterator() const;
    void *constBegin(const void *container) const;
    void *constEnd(const void *container) const;
    void destroyConstIterator(const void *iterator) const;
    bool compareConstIterator(const void *i, const void *j) const;
    void copyConstIterator(void *target, const void *source) const;
    void advanceConstIterator(void *iterator, qsizetype step) const;
    qsizetype diffConstIterator(const void *i, const void *j) const;

protected:
    const QtMetaContainerPrivate::QMetaContainerInterface *d_ptr = nullptr;
};

class Q_CORE_EXPORT QMetaSequence : public QMetaContainer
{
public:
    QMetaSequence() = default;
    explicit QMetaSequence(const QtMetaContainerPrivate::QMetaSequenceInterface *d) : QMetaContainer(d) {}

    template<typename T>
    static constexpr QMetaSequence fromContainer()
    {
        return QMetaSequence(&MetaSequence<T>::value);
    }

    QMetaType valueMetaType() const;

    bool isSortable() const;
    bool canAddValueAtBegin() const;
    void addValueAtBegin(void *container, const void *value) const;
    bool canAddValueAtEnd() const;
    void addValueAtEnd(void *container, const void *value) const;
    bool canRemoveValueAtBegin() const;
    void removeValueAtBegin(void *container) const;
    bool canRemoveValueAtEnd() const;
    void removeValueAtEnd(void *container) const;

    bool canGetValueAtIndex() const;
    void valueAtIndex(const void *container, qsizetype index, void *result) const;

    bool canSetValueAtIndex() const;
    void setValueAtIndex(void *container, qsizetype index, const void *value) const;

    bool canAddValue() const;
    void addValue(void *container, const void *value) const;

    bool canRemoveValue() const;
    void removeValue(void *container) const;

    bool canGetValueAtIterator() const;
    void valueAtIterator(const void *iterator, void *result) const;

    bool canSetValueAtIterator() const;
    void setValueAtIterator(const void *iterator, const void *value) const;

    bool canInsertValueAtIterator() const;
    void insertValueAtIterator(void *container, const void *iterator, const void *value) const;

    bool canEraseValueAtIterator() const;
    void eraseValueAtIterator(void *container, const void *iterator) const;

    bool canEraseRangeAtIterator() const;
    void eraseRangeAtIterator(void *container, const void *iterator1, const void *iterator2) const;

    bool canGetValueAtConstIterator() const;
    void valueAtConstIterator(const void *iterator, void *result) const;

    friend bool operator==(const QMetaSequence &a, const QMetaSequence &b)
    {
        return a.d() == b.d();
    }
    friend bool operator!=(const QMetaSequence &a, const QMetaSequence &b)
    {
        return a.d() != b.d();
    }

private:
    template<typename T>
    struct MetaSequence
    {
        static constexpr const QtMetaContainerPrivate::QMetaSequenceInterface value
            = QtMetaContainerPrivate::QMetaSequenceInterface(
                    QtMetaContainerPrivate::QMetaSequenceForContainer<T>());
    };

    const QtMetaContainerPrivate::QMetaSequenceInterface *d() const
    {
        return static_cast<const QtMetaContainerPrivate::QMetaSequenceInterface *>(d_ptr);
    }
};

class Q_CORE_EXPORT QMetaAssociation : public QMetaContainer
{
public:
    QMetaAssociation() = default;
    explicit QMetaAssociation(const QtMetaContainerPrivate::QMetaAssociationInterface *d) : QMetaContainer(d) {}

    template<typename T>
    static constexpr QMetaAssociation fromContainer()
    {
        return QMetaAssociation(&MetaAssociation<T>::value);
    }

    QMetaType keyMetaType() const;
    QMetaType mappedMetaType() const;

    bool canInsertKey() const
    {
        if (auto iface = d())
            return iface->insertKeyFn;
        return false;
    }
    void insertKey(void *container, const void *key) const
    {
        if (canInsertKey())
            d()->insertKeyFn(container, key);
    }

    bool canRemoveKey() const
    {
        if (auto iface = d())
            return iface->removeKeyFn;
        return false;
    }
    void removeKey(void *container, const void *key) const
    {
        if (canRemoveKey())
            d()->removeKeyFn(container, key);
    }

    bool canContainsKey() const
    {
        if (auto iface = d())
            return iface->containsKeyFn;
        return false;
    }
    bool containsKey(const void *container, const void *key) const
    {
        if (canContainsKey())
            return d()->containsKeyFn(container, key);
        return false;
    }


    bool canGetMappedAtKey() const
    {
        if (auto iface = d())
            return iface->mappedAtKeyFn;
        return false;
    }
    void mappedAtKey(const void *container, const void *key, void *mapped) const
    {
        if (canGetMappedAtKey())
            d()->mappedAtKeyFn(container, key, mapped);
    }

    bool canSetMappedAtKey() const
    {
        if (auto iface = d())
            return iface->setMappedAtKeyFn;
        return false;
    }
    void setMappedAtKey(void *container, const void *key, const void *mapped) const
    {
        if (canSetMappedAtKey())
            d()->setMappedAtKeyFn(container, key, mapped);
    }

    bool canGetKeyAtIterator() const
    {
        if (auto iface = d())
            return iface->keyAtIteratorFn;
        return false;
    }

    void keyAtIterator(const void *iterator, void *key) const
    {
        if (canGetKeyAtIterator())
            d()->keyAtIteratorFn(iterator, key);
    }

    bool canGetKeyAtConstIterator() const
    {
        if (auto iface = d())
            return iface->keyAtConstIteratorFn;
        return false;
    }

    void keyAtConstIterator(const void *iterator, void *key) const
    {
        if (canGetKeyAtConstIterator())
            d()->keyAtConstIteratorFn(iterator, key);
    }

    bool canGetMappedAtIterator() const
    {
        if (auto iface = d())
            return iface->mappedAtIteratorFn;
        return false;
    }

    void mappedAtIterator(const void *iterator, void *mapped) const
    {
        if (canGetMappedAtIterator())
            d()->mappedAtIteratorFn(iterator, mapped);
    }

    bool canGetMappedAtConstIterator() const
    {
        if (auto iface = d())
            return iface->mappedAtConstIteratorFn;
        return false;
    }

    void mappedAtConstIterator(const void *iterator, void *mapped) const
    {
        if (canGetMappedAtConstIterator())
            d()->mappedAtConstIteratorFn(iterator, mapped);
    }

    bool canSetMappedAtIterator() const
    {
        if (auto iface = d())
            return iface->setMappedAtIteratorFn;
        return false;
    }

    void setMappedAtIterator(const void *iterator, const void *mapped) const
    {
        if (canSetMappedAtIterator())
            d()->setMappedAtIteratorFn(iterator, mapped);
    }

    bool canCreateIteratorAtKey() const
    {
        if (auto iface = d())
            return iface->createIteratorAtKeyFn;
        return false;
    }

    void *createIteratorAtKey(void *container, const void *key) const
    {
        if (canCreateIteratorAtKey())
            return d()->createIteratorAtKeyFn(container, key);
        return nullptr;
    }

    bool canCreateConstIteratorAtKey() const
    {
        if (auto iface = d())
            return iface->createConstIteratorAtKeyFn;
        return false;
    }

    void *createConstIteratorAtKey(const void *container, const void *key) const
    {
        if (canCreateConstIteratorAtKey())
            return d()->createConstIteratorAtKeyFn(container, key);
        return nullptr;
    }

    friend bool operator==(const QMetaAssociation &a, const QMetaAssociation &b)
    {
        return a.d() == b.d();
    }
    friend bool operator!=(const QMetaAssociation &a, const QMetaAssociation &b)
    {
        return a.d() != b.d();
    }

private:
    template<typename T>
    struct MetaAssociation
    {
        static constexpr const QtMetaContainerPrivate::QMetaAssociationInterface value
                = QtMetaContainerPrivate::QMetaAssociationInterface(
                        QtMetaContainerPrivate::QMetaAssociationForContainer<T>());
    };

    const QtMetaContainerPrivate::QMetaAssociationInterface *d() const
    {
        return static_cast<const QtMetaContainerPrivate::QMetaAssociationInterface *>(d_ptr);
    }
};

QT_END_NAMESPACE

#endif // QMETACONTAINER_H
