// Copyright (C) 2016 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 QQMLLISTCOMPOSITOR_P_H
#define QQMLLISTCOMPOSITOR_P_H

//
//  W A R N I N G
//  -------------
//
// This file is not part of the Qt API.  It exists purely as an
// implementation detail.  This header file may change from version to
// version without notice, or even be removed.
//
// We mean it.
//

#include <QtCore/qglobal.h>
#include <QtCore/qvector.h>

#include <private/qqmlchangeset_p.h>

#include <QtCore/qdebug.h>

QT_BEGIN_NAMESPACE

class Q_AUTOTEST_EXPORT QQmlListCompositor
{
public:
    enum { MinimumGroupCount = 3, MaximumGroupCount = 11 };

    enum Group
    {
        Cache   = 0,
        Default = 1,
        Persisted = 2
    };

    enum Flag
    {
        CacheFlag       = 1 << Cache,
        DefaultFlag     = 1 << Default,
        PersistedFlag   = 1 << Persisted,
        PrependFlag     = 0x10000000,
        AppendFlag      = 0x20000000,
        UnresolvedFlag  = 0x40000000,
        MovedFlag       = 0x80000000,
        GroupMask       = ~(PrependFlag | AppendFlag | UnresolvedFlag | MovedFlag | CacheFlag)
    };

    class Range
    {
    public:
        Range() : next(this), previous(this) {}
        Range(Range *next, void *list, int index, int count, uint flags)
            : next(next), previous(next->previous), list(list), index(index), count(count), flags(flags) {
            next->previous = this; previous->next = this; }

        Range *next;
        Range *previous;
        void *list = nullptr;
        int index = 0;
        int count = 0;
        uint flags = 0;

        inline int start() const { return index; }
        inline int end() const { return index + count; }

        inline int groups() const { return flags & GroupMask; }

        inline bool inGroup() const { return flags & GroupMask; }
        inline bool inCache() const { return flags & CacheFlag; }
        inline bool inGroup(int group) const { return flags & (1 << group); }
        inline bool isUnresolved() const { return flags & UnresolvedFlag; }

        inline bool prepend() const { return flags & PrependFlag; }
        inline bool append() const { return flags & AppendFlag; }
    };

    class Q_AUTOTEST_EXPORT iterator
    {
    public:
        inline iterator() = default;
        inline iterator(Range *range, int offset, Group group, int groupCount);

        bool operator ==(const iterator &it) const { return range == it.range && offset == it.offset; }
        bool operator !=(const iterator &it) const { return range != it.range || offset != it.offset; }

        bool operator ==(Group group) const { return range->flags & (1 << group); }
        bool operator !=(Group group) const { return !(range->flags & (1 << group)); }

        Range *&operator *() { return range; }
        Range * const &operator *() const { return range; }
        Range *operator ->() { return range; }
        const Range *operator ->() const { return range; }

        iterator &operator +=(int difference);

        template<typename T> T *list() const { return static_cast<T *>(range->list); }
        int modelIndex() const { return range->index + offset; }

        void incrementIndexes(int difference) { incrementIndexes(difference, range->flags); }
        void decrementIndexes(int difference) { decrementIndexes(difference, range->flags); }

        inline void incrementIndexes(int difference, uint flags);
        inline void decrementIndexes(int difference, uint flags);

        void setGroup(Group g) { group = g; groupFlag = 1 << g; }

        Range *range = nullptr;
        int offset = 0;
        Group group = Default;
        int groupFlag = 0;
        int groupCount = 0;
        int index[MaximumGroupCount] = { 0 };

        int cacheIndex() const {
            return index[Cache];
        }

        void setCacheIndex(int cacheIndex) {
            index[Cache] = cacheIndex;
        }
    };

    class Q_AUTOTEST_EXPORT insert_iterator : public iterator
    {
    public:
        inline insert_iterator() {}
        inline insert_iterator(const iterator &it) : iterator(it) {}
        inline insert_iterator(Range *, int, Group, int);
        inline ~insert_iterator() {}

        insert_iterator &operator +=(int difference);
    };

    struct Change
    {
        inline Change() = default;
        inline Change(const iterator &it, int count, uint flags, int moveId = -1);
        int count = 0;
        uint flags = 0;
        int moveId = 0;
        int index[MaximumGroupCount] = { 0 };

        int cacheIndex() const {
            return index[Cache];
        }

        void setCacheIndex(int cacheIndex) {
            index[Cache] = cacheIndex;
        }

        inline bool isMove() const { return moveId >= 0; }
        inline bool inCache() const { return flags & CacheFlag; }
        inline bool inGroup() const { return flags & GroupMask; }
        inline bool inGroup(int group) const { return flags & (CacheFlag << group); }

        inline int groups() const { return flags & GroupMask; }
    };

    struct Insert : public Change
    {
        Insert() {}
        Insert(const iterator &it, int count, uint flags, int moveId = -1)
            : Change(it, count, flags, moveId) {}
    };

    struct Remove : public Change
    {
        Remove() {}
        Remove(const iterator &it, int count, uint flags, int moveId = -1)
            : Change(it, count, flags, moveId) {}
    };

    QQmlListCompositor();
    ~QQmlListCompositor();

    int defaultGroups() const { return m_defaultFlags & ~PrependFlag; }
    void setDefaultGroups(int groups) { m_defaultFlags = groups | PrependFlag; }
    void setDefaultGroup(Group group) { m_defaultFlags |= (1 << group); }
    void clearDefaultGroup(Group group) { m_defaultFlags &= ~(1 << group); }
    void setRemoveGroups(int groups) { m_removeFlags = PrependFlag | AppendFlag | groups; }
    void setGroupCount(int count);

    int count(Group group) const;
    iterator find(Group group, int index);
    iterator find(Group group, int index) const;
    insert_iterator findInsertPosition(Group group, int index);

    const iterator &end() { return m_end; }

    void append(void *list, int index, int count, uint flags, QVector<Insert> *inserts = nullptr);
    void insert(Group group, int before, void *list, int index, int count, uint flags, QVector<Insert> *inserts = nullptr);
    iterator insert(iterator before, void *list, int index, int count, uint flags, QVector<Insert> *inserts = nullptr);

    void setFlags(Group fromGroup, int from, int count, Group group, int flags, QVector<Insert> *inserts = nullptr);
    void setFlags(iterator from, int count, Group group, uint flags, QVector<Insert> *inserts = nullptr);
    void setFlags(Group fromGroup, int from, int count, uint flags, QVector<Insert> *inserts = nullptr) {
        setFlags(fromGroup, from, count, fromGroup, flags, inserts); }
    void setFlags(const iterator from, int count, uint flags, QVector<Insert> *inserts = nullptr) {
        setFlags(from, count, from.group, flags, inserts); }

    void clearFlags(Group fromGroup, int from, int count, Group group, uint flags, QVector<Remove> *removals = nullptr);
    void clearFlags(iterator from, int count, Group group, uint flags, QVector<Remove> *removals = nullptr);
    void clearFlags(Group fromGroup, int from, int count, uint flags, QVector<Remove> *removals = nullptr) {
        clearFlags(fromGroup, from, count, fromGroup, flags, removals); }
    void clearFlags(const iterator &from, int count, uint flags, QVector<Remove> *removals = nullptr) {
        clearFlags(from, count, from.group, flags, removals); }

    bool verifyMoveTo(Group fromGroup, int from, Group toGroup, int to, int count, Group group) const;

    void move(
            Group fromGroup,
            int from,
            Group toGroup,
            int to,
            int count,
            Group group,
            QVector<Remove> *removals = nullptr,
            QVector<Insert> *inserts = nullptr);
    void clear();

    void listItemsInserted(void *list, int index, int count, QVector<Insert> *inserts);
    void listItemsRemoved(void *list, int index, int count, QVector<Remove> *removals);
    void listItemsMoved(void *list, int from, int to, int count, QVector<Remove> *removals, QVector<Insert> *inserts);
    void listItemsChanged(void *list, int index, int count, QVector<Change> *changes);

    void transition(
            Group from,
            Group to,
            QVector<QQmlChangeSet::Change> *removes,
            QVector<QQmlChangeSet::Change> *inserts);

private:
    Range m_ranges;
    iterator m_end;
    iterator m_cacheIt;
    int m_groupCount;
    int m_defaultFlags;
    int m_removeFlags;
    int m_moveId;

    inline Range *insert(Range *before, void *list, int index, int count, uint flags);
    inline Range *erase(Range *range);

    struct MovedFlags
    {
        MovedFlags() {}
        MovedFlags(int moveId, uint flags) : moveId(moveId), flags(flags) {}

        int moveId;
        uint flags;
    };

    void listItemsRemoved(
            QVector<Remove> *translatedRemovals,
            void *list,
            QVector<QQmlChangeSet::Change> *removals,
            QVector<QQmlChangeSet::Change> *insertions = nullptr,
            QVector<MovedFlags> *movedFlags = nullptr);
    void listItemsInserted(
            QVector<Insert> *translatedInsertions,
            void *list,
            const QVector<QQmlChangeSet::Change> &insertions,
            const QVector<MovedFlags> *movedFlags = nullptr);
    void listItemsChanged(
            QVector<Change> *translatedChanges,
            void *list,
            const QVector<QQmlChangeSet::Change> &changes);

    friend Q_AUTOTEST_EXPORT QDebug operator <<(QDebug debug, const QQmlListCompositor &list);
};

Q_DECLARE_TYPEINFO(QQmlListCompositor::Change, Q_PRIMITIVE_TYPE);
Q_DECLARE_TYPEINFO(QQmlListCompositor::Remove, Q_PRIMITIVE_TYPE);
Q_DECLARE_TYPEINFO(QQmlListCompositor::Insert, Q_PRIMITIVE_TYPE);

QT_WARNING_PUSH
// GCC isn't wrong, as groupCount is public in iterator, but we tried Q_ASSUME(),
// right in front of the loops, and it didn't help, so we disable the warning:
QT_WARNING_DISABLE_GCC("-Warray-bounds")
inline QQmlListCompositor::iterator::iterator(
        Range *range, int offset, Group group, int groupCount)
    : range(range)
    , offset(offset)
    , group(group)
    , groupFlag(1 << group)
    , groupCount(groupCount)
{
    for (int i = 0; i < groupCount; ++i)
        index[i] = 0;
}

inline void QQmlListCompositor::iterator::incrementIndexes(int difference, uint flags)
{
    for (int i = 0; i < groupCount; ++i) {
        if (flags & (1 << i))
            index[i] += difference;
    }
}

inline void QQmlListCompositor::iterator::decrementIndexes(int difference, uint flags)
{
    for (int i = 0; i < groupCount; ++i) {
        if (flags & (1 << i))
            index[i] -= difference;
    }
}
QT_WARNING_POP // -Warray-bounds

inline QQmlListCompositor::insert_iterator::insert_iterator(
        Range *range, int offset, Group group, int groupCount)
    : iterator(range, offset, group, groupCount) {}

inline QQmlListCompositor::Change::Change(const iterator &it, int count, uint flags, int moveId)
    : count(count), flags(flags), moveId(moveId)
{
    for (int i = 0; i < MaximumGroupCount; ++i)
        index[i] = it.index[i];
}

Q_AUTOTEST_EXPORT QDebug operator <<(QDebug debug, const QQmlListCompositor::Group &group);
Q_AUTOTEST_EXPORT QDebug operator <<(QDebug debug, const QQmlListCompositor::Range &range);
Q_AUTOTEST_EXPORT QDebug operator <<(QDebug debug, const QQmlListCompositor::iterator &it);
Q_AUTOTEST_EXPORT QDebug operator <<(QDebug debug, const QQmlListCompositor::Change &change);
Q_AUTOTEST_EXPORT QDebug operator <<(QDebug debug, const QQmlListCompositor::Remove &remove);
Q_AUTOTEST_EXPORT QDebug operator <<(QDebug debug, const QQmlListCompositor::Insert &insert);
Q_AUTOTEST_EXPORT QDebug operator <<(QDebug debug, const QQmlListCompositor &list);

QT_END_NAMESPACE

#endif
