// Copyright (C) 2020 The Qt Company Ltd.
// Copyright (C) 2016 Intel Corporation.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
// Qt-Security score:significant reason:default

#ifndef QARRAYDATAOPS_H
#define QARRAYDATAOPS_H

#include <QtCore/qarraydata.h>
#include <QtCore/qcontainertools_impl.h>
#include <QtCore/qnamespace.h>

#include <QtCore/q20functional.h>
#include <QtCore/q20memory.h>
#include <new>
#include <string.h>
#include <utility>
#include <iterator>
#include <type_traits>

QT_BEGIN_NAMESPACE

template <class T> struct QArrayDataPointer;

namespace QtPrivate {

template <class T>
struct QPodArrayOps
        : public QArrayDataPointer<T>
{
    static_assert (std::is_nothrow_destructible_v<T>, "Types with throwing destructors are not supported in Qt containers.");

protected:
    typedef QTypedArrayData<T> Data;
    using DataPointer = QArrayDataPointer<T>;

public:
    typedef typename QArrayDataPointer<T>::parameter_type parameter_type;

    using QArrayDataPointer<T>::QArrayDataPointer;

    void copyAppend(const T *b, const T *e) noexcept
    {
        Q_ASSERT(this->isMutable() || b == e);
        Q_ASSERT(!this->isShared() || b == e);
        Q_ASSERT(b <= e);
        Q_ASSERT((e - b) <= this->freeSpaceAtEnd());

        if (b == e)
            return;

        ::memcpy(static_cast<void *>(this->end()), static_cast<const void *>(b), (e - b) * sizeof(T));
        this->size += (e - b);
    }

    void copyAppend(qsizetype n, parameter_type t) noexcept
    {
        Q_ASSERT(!this->isShared() || n == 0);
        Q_ASSERT(this->freeSpaceAtEnd() >= n);
        if (!n)
            return;

        T *where = this->end();
        this->size += qsizetype(n);
        while (n--)
            *where++ = t;
    }

    void moveAppend(T *b, T *e) noexcept
    {
        copyAppend(b, e);
    }

    void truncate(size_t newSize) noexcept
    {
        Q_ASSERT(this->isMutable());
        Q_ASSERT(!this->isShared());
        Q_ASSERT(newSize <= size_t(this->size));

        this->size = qsizetype(newSize);
    }

    void destroyAll() noexcept // Call from destructors, ONLY!
    {
        Q_ASSERT(this->d);
        Q_ASSERT(this->d->ref_.loadRelaxed() == 0);

        // As this is to be called only from destructor, it doesn't need to be
        // exception safe; size not updated.
    }

    T *createHole(QArrayData::GrowthPosition pos, qsizetype where, qsizetype n)
    {
        Q_ASSERT((pos == QArrayData::GrowsAtBeginning && n <= this->freeSpaceAtBegin()) ||
                 (pos == QArrayData::GrowsAtEnd && n <= this->freeSpaceAtEnd()));

        T *insertionPoint = this->ptr + where;
        if (pos == QArrayData::GrowsAtEnd) {
            if (where < this->size)
                ::memmove(static_cast<void *>(insertionPoint + n), static_cast<void *>(insertionPoint), (this->size - where) * sizeof(T));
        } else {
            Q_ASSERT(where == 0);
            this->ptr -= n;
            insertionPoint -= n;
        }
        this->size += n;
        return insertionPoint;
    }

    void insert(qsizetype i, const T *data, qsizetype n)
    {
        typename Data::GrowthPosition pos = Data::GrowsAtEnd;
        if (this->size != 0 && i == 0)
            pos = Data::GrowsAtBeginning;

        DataPointer oldData;
        this->detachAndGrow(pos, n, &data, &oldData);
        Q_ASSERT((pos == Data::GrowsAtBeginning && this->freeSpaceAtBegin() >= n) ||
                 (pos == Data::GrowsAtEnd && this->freeSpaceAtEnd() >= n));

        T *where = createHole(pos, i, n);
        ::memcpy(static_cast<void *>(where), static_cast<const void *>(data), n * sizeof(T));
    }

    void insert(qsizetype i, qsizetype n, parameter_type t)
    {
        T copy(t);

        typename Data::GrowthPosition pos = Data::GrowsAtEnd;
        if (this->size != 0 && i == 0)
            pos = Data::GrowsAtBeginning;

        this->detachAndGrow(pos, n, nullptr, nullptr);
        Q_ASSERT((pos == Data::GrowsAtBeginning && this->freeSpaceAtBegin() >= n) ||
                 (pos == Data::GrowsAtEnd && this->freeSpaceAtEnd() >= n));

        T *where = createHole(pos, i, n);
        while (n--)
            *where++ = copy;
    }

    template<typename... Args>
    void emplace(qsizetype i, Args &&... args)
    {
        bool detach = this->needsDetach();
        if (!detach) {
            if (i == this->size && this->freeSpaceAtEnd()) {
                new (this->end()) T(std::forward<Args>(args)...);
                ++this->size;
                return;
            }
            if (i == 0 && this->freeSpaceAtBegin()) {
                new (this->begin() - 1) T(std::forward<Args>(args)...);
                --this->ptr;
                ++this->size;
                return;
            }
        }
        T tmp(std::forward<Args>(args)...);
        typename QArrayData::GrowthPosition pos = QArrayData::GrowsAtEnd;
        if (this->size != 0 && i == 0)
            pos = QArrayData::GrowsAtBeginning;

        this->detachAndGrow(pos, 1, nullptr, nullptr);

        T *where = createHole(pos, i, 1);
        new (where) T(std::move(tmp));
    }

    void erase(T *b, qsizetype n)
    {
        T *e = b + n;
        Q_ASSERT(this->isMutable());
        Q_ASSERT(b < e);
        Q_ASSERT(b >= this->begin() && b < this->end());
        Q_ASSERT(e > this->begin() && e <= this->end());

        // Comply with std::vector::erase(): erased elements and all after them
        // are invalidated. However, erasing from the beginning effectively
        // means that all iterators are invalidated. We can use this freedom to
        // erase by moving towards the end.
        if (b == this->begin() && e != this->end()) {
            this->ptr = e;
        } else if (e != this->end()) {
            ::memmove(static_cast<void *>(b), static_cast<void *>(e),
                      (static_cast<T *>(this->end()) - e) * sizeof(T));
        }
        this->size -= n;
    }

    void eraseFirst() noexcept
    {
        Q_ASSERT(this->isMutable());
        Q_ASSERT(this->size);
        ++this->ptr;
        --this->size;
    }

    void eraseLast() noexcept
    {
        Q_ASSERT(this->isMutable());
        Q_ASSERT(this->size);
        --this->size;
    }

    template <typename Predicate>
    qsizetype eraseIf(Predicate pred)
    {
        qsizetype result = 0;
        if (this->size == 0)
            return result;

        if (!this->needsDetach()) {
            auto end = this->end();
            auto it = std::remove_if(this->begin(), end, pred);
            if (it != end) {
                result = std::distance(it, end);
                erase(it, result);
            }
        } else {
            const auto begin = this->begin();
            const auto end = this->end();
            auto it = std::find_if(begin, end, pred);
            if (it == end)
                return result;

            QPodArrayOps<T> other(this->size);
            Q_CHECK_PTR(other.data());
            auto dest = other.begin();
            // std::uninitialized_copy will fallback to ::memcpy/memmove()
            dest = std::uninitialized_copy(begin, it, dest);
            dest = q_uninitialized_remove_copy_if(std::next(it), end, dest, pred);
            other.size = std::distance(other.data(), dest);
            result = this->size - other.size;
            this->swap(other);
        }
        return result;
    }

    struct Span { T *begin; T *end; };

    void copyRanges(std::initializer_list<Span> ranges)
    {
        auto it = this->begin();
        std::for_each(ranges.begin(), ranges.end(), [&it](const auto &span) {
            it = std::copy(span.begin, span.end, it);
        });
        this->size = std::distance(this->begin(), it);
    }

    void assign(T *b, T *e, parameter_type t) noexcept
    {
        Q_ASSERT(b <= e);
        Q_ASSERT(b >= this->begin() && e <= this->end());

        while (b != e)
            ::memcpy(static_cast<void *>(b++), static_cast<const void *>(&t), sizeof(T));
    }

    void reallocate(qsizetype alloc, QArrayData::AllocationOption option)
    {
        auto pair = Data::reallocateUnaligned(this->d, this->ptr, alloc, option);
        Q_CHECK_PTR(pair.second);
        Q_ASSERT(pair.first != nullptr);
        this->d = pair.first;
        this->ptr = pair.second;
    }
};

template <class T>
struct QGenericArrayOps
        : public QArrayDataPointer<T>
{
    static_assert (std::is_nothrow_destructible_v<T>, "Types with throwing destructors are not supported in Qt containers.");

protected:
    typedef QTypedArrayData<T> Data;
    using DataPointer = QArrayDataPointer<T>;

public:
    typedef typename QArrayDataPointer<T>::parameter_type parameter_type;

    void copyAppend(const T *b, const T *e)
    {
        Q_ASSERT(this->isMutable() || b == e);
        Q_ASSERT(!this->isShared() || b == e);
        Q_ASSERT(b <= e);
        Q_ASSERT((e - b) <= this->freeSpaceAtEnd());

        if (b == e) // short-cut and handling the case b and e == nullptr
            return;

        T *data = this->begin();
        while (b < e) {
            new (data + this->size) T(*b);
            ++b;
            ++this->size;
        }
    }

    void copyAppend(qsizetype n, parameter_type t)
    {
        Q_ASSERT(!this->isShared() || n == 0);
        Q_ASSERT(this->freeSpaceAtEnd() >= n);
        if (!n)
            return;

        T *data = this->begin();
        while (n--) {
            new (data + this->size) T(t);
            ++this->size;
        }
    }

    void moveAppend(T *b, T *e)
    {
        Q_ASSERT(this->isMutable() || b == e);
        Q_ASSERT(!this->isShared() || b == e);
        Q_ASSERT(b <= e);
        Q_ASSERT((e - b) <= this->freeSpaceAtEnd());

        if (b == e)
            return;

        T *data = this->begin();
        while (b < e) {
            new (data + this->size) T(std::move(*b));
            ++b;
            ++this->size;
        }
    }

    void truncate(size_t newSize)
    {
        Q_ASSERT(this->isMutable());
        Q_ASSERT(!this->isShared());
        Q_ASSERT(newSize <= size_t(this->size));

        std::destroy(this->begin() + newSize, this->end());
        this->size = newSize;
    }

    void destroyAll() // Call from destructors, ONLY
    {
        Q_ASSERT(this->d);
        // As this is to be called only from destructor, it doesn't need to be
        // exception safe; size not updated.

        Q_ASSERT(this->d->ref_.loadRelaxed() == 0);

        std::destroy(this->begin(), this->end());
    }

    struct Inserter
    {
        QArrayDataPointer<T> *data;
        T *begin;
        qsizetype size;

        qsizetype sourceCopyConstruct = 0, nSource = 0, move = 0, sourceCopyAssign = 0;
        T *end = nullptr, *last = nullptr, *where = nullptr;

        Inserter(QArrayDataPointer<T> *d) : data(d)
        {
            begin = d->ptr;
            size = d->size;
        }
        ~Inserter() {
            data->ptr = begin;
            data->size = size;
        }
        Q_DISABLE_COPY(Inserter)

        void setup(qsizetype pos, qsizetype n)
        {
            end = begin + size;
            last = end - 1;
            where = begin + pos;
            qsizetype dist = size - pos;
            sourceCopyConstruct = 0;
            nSource = n;
            move = n - dist; // smaller 0
            sourceCopyAssign = n;
            if (n > dist) {
                sourceCopyConstruct = n - dist;
                move = 0;
                sourceCopyAssign -= sourceCopyConstruct;
            }
        }

        void insert(qsizetype pos, const T *source, qsizetype n)
        {
            qsizetype oldSize = size;
            Q_UNUSED(oldSize);

            setup(pos, n);

            // first create new elements at the end, by copying from elements
            // to be inserted (if they extend past the current end of the array)
            for (qsizetype i = 0; i != sourceCopyConstruct; ++i) {
                new (end + i) T(source[nSource - sourceCopyConstruct + i]);
                ++size;
            }
            Q_ASSERT(size <= oldSize + n);

            // now move construct new elements at the end from existing elements inside
            // the array.
            for (qsizetype i = sourceCopyConstruct; i != nSource; ++i) {
                new (end + i) T(std::move(*(end + i - nSource)));
                ++size;
            }
            // array has the new size now!
            Q_ASSERT(size == oldSize + n);

            // now move assign existing elements towards the end
            for (qsizetype i = 0; i != move; --i)
                last[i] = std::move(last[i - nSource]);

            // finally copy the remaining elements from source over
            for (qsizetype i = 0; i != sourceCopyAssign; ++i)
                where[i] = source[i];
        }

        void insert(qsizetype pos, const T &t, qsizetype n)
        {
            const qsizetype oldSize = size;
            Q_UNUSED(oldSize);

            setup(pos, n);

            // first create new elements at the end, by copying from elements
            // to be inserted (if they extend past the current end of the array)
            for (qsizetype i = 0; i != sourceCopyConstruct; ++i) {
                new (end + i) T(t);
                ++size;
            }
            Q_ASSERT(size <= oldSize + n);

            // now move construct new elements at the end from existing elements inside
            // the array.
            for (qsizetype i = sourceCopyConstruct; i != nSource; ++i) {
                new (end + i) T(std::move(*(end + i - nSource)));
                ++size;
            }
            // array has the new size now!
            Q_ASSERT(size == oldSize + n);

            // now move assign existing elements towards the end
            for (qsizetype i = 0; i != move; --i)
                last[i] = std::move(last[i - nSource]);

            // finally copy the remaining elements from source over
            for (qsizetype i = 0; i != sourceCopyAssign; ++i)
                where[i] = t;
        }

        void insertOne(qsizetype pos, T &&t)
        {
            setup(pos, 1);

            if (sourceCopyConstruct) {
                Q_ASSERT(sourceCopyConstruct == 1);
                new (end) T(std::move(t));
                ++size;
            } else {
                // create a new element at the end by move constructing one existing element
                // inside the array.
                new (end) T(std::move(*(end - 1)));
                ++size;

                // now move assign existing elements towards the end
                for (qsizetype i = 0; i != move; --i)
                    last[i] = std::move(last[i - 1]);

                // and move the new item into place
                *where = std::move(t);
            }
        }
    };

    void insert(qsizetype i, const T *data, qsizetype n)
    {
        const bool growsAtBegin = this->size != 0 && i == 0;
        const auto pos = growsAtBegin ? Data::GrowsAtBeginning : Data::GrowsAtEnd;

        DataPointer oldData;
        this->detachAndGrow(pos, n, &data, &oldData);
        Q_ASSERT((pos == Data::GrowsAtBeginning && this->freeSpaceAtBegin() >= n) ||
                 (pos == Data::GrowsAtEnd && this->freeSpaceAtEnd() >= n));

        if (growsAtBegin) {
            // copy construct items in reverse order at the begin
            Q_ASSERT(this->freeSpaceAtBegin() >= n);
            while (n) {
                --n;
                new (this->begin() - 1) T(data[n]);
                --this->ptr;
                ++this->size;
            }
        } else {
            Inserter(this).insert(i, data, n);
        }
    }

    void insert(qsizetype i, qsizetype n, parameter_type t)
    {
        T copy(t);

        const bool growsAtBegin = this->size != 0 && i == 0;
        const auto pos = growsAtBegin ? Data::GrowsAtBeginning : Data::GrowsAtEnd;

        this->detachAndGrow(pos, n, nullptr, nullptr);
        Q_ASSERT((pos == Data::GrowsAtBeginning && this->freeSpaceAtBegin() >= n) ||
                 (pos == Data::GrowsAtEnd && this->freeSpaceAtEnd() >= n));

        if (growsAtBegin) {
            // copy construct items in reverse order at the begin
            Q_ASSERT(this->freeSpaceAtBegin() >= n);
            while (n--) {
                new (this->begin() - 1) T(copy);
                --this->ptr;
                ++this->size;
            }
        } else {
            Inserter(this).insert(i, copy, n);
        }
    }

    template<typename... Args>
    void emplace(qsizetype i, Args &&... args)
    {
        bool detach = this->needsDetach();
        if (!detach) {
            if (i == this->size && this->freeSpaceAtEnd()) {
                new (this->end()) T(std::forward<Args>(args)...);
                ++this->size;
                return;
            }
            if (i == 0 && this->freeSpaceAtBegin()) {
                new (this->begin() - 1) T(std::forward<Args>(args)...);
                --this->ptr;
                ++this->size;
                return;
            }
        }
        T tmp(std::forward<Args>(args)...);
        const bool growsAtBegin = this->size != 0 && i == 0;
        const auto pos = growsAtBegin ? Data::GrowsAtBeginning : Data::GrowsAtEnd;

        this->detachAndGrow(pos, 1, nullptr, nullptr);

        if (growsAtBegin) {
            Q_ASSERT(this->freeSpaceAtBegin());
            new (this->begin() - 1) T(std::move(tmp));
            --this->ptr;
            ++this->size;
        } else {
            Inserter(this).insertOne(i, std::move(tmp));
        }
    }

    void erase(T *b, qsizetype n)
    {
        T *e = b + n;
        Q_ASSERT(this->isMutable());
        Q_ASSERT(b < e);
        Q_ASSERT(b >= this->begin() && b < this->end());
        Q_ASSERT(e > this->begin() && e <= this->end());

        // Comply with std::vector::erase(): erased elements and all after them
        // are invalidated. However, erasing from the beginning effectively
        // means that all iterators are invalidated. We can use this freedom to
        // erase by moving towards the end.
        if (b == this->begin() && e != this->end()) {
            this->ptr = e;
        } else {
            const T *const end = this->end();

            // move (by assignment) the elements from e to end
            // onto b to the new end
            while (e != end) {
                *b = std::move(*e);
                ++b;
                ++e;
            }
        }
        this->size -= n;
        std::destroy(b, e);
    }

    void eraseFirst() noexcept
    {
        Q_ASSERT(this->isMutable());
        Q_ASSERT(this->size);
        this->begin()->~T();
        ++this->ptr;
        --this->size;
    }

    void eraseLast() noexcept
    {
        Q_ASSERT(this->isMutable());
        Q_ASSERT(this->size);
        (this->end() - 1)->~T();
        --this->size;
    }


    void assign(T *b, T *e, parameter_type t)
    {
        Q_ASSERT(b <= e);
        Q_ASSERT(b >= this->begin() && e <= this->end());

        while (b != e)
            *b++ = t;
    }
};

template <class T>
struct QMovableArrayOps
    : QGenericArrayOps<T>
{
    static_assert (std::is_nothrow_destructible_v<T>, "Types with throwing destructors are not supported in Qt containers.");

protected:
    typedef QTypedArrayData<T> Data;
    using DataPointer = QArrayDataPointer<T>;

public:
    // using QGenericArrayOps<T>::copyAppend;
    // using QGenericArrayOps<T>::moveAppend;
    // using QGenericArrayOps<T>::truncate;
    // using QGenericArrayOps<T>::destroyAll;
    typedef typename QGenericArrayOps<T>::parameter_type parameter_type;

    struct Inserter
    {
        QArrayDataPointer<T> * const data;
        T *displaceFrom;
        T * const displaceTo;
        const qsizetype nInserts = 0;
        const size_t bytes;

        void verifyPost()
        { Q_ASSERT(displaceFrom == displaceTo); }

        explicit Inserter(QArrayDataPointer<T> *d, qsizetype pos, qsizetype n)
            : data{d},
              displaceFrom{d->ptr + pos},
              displaceTo{displaceFrom + n},
              nInserts{n},
              bytes{(data->size - pos) * sizeof(T)}
        {
            ::memmove(static_cast<void *>(displaceTo), static_cast<void *>(displaceFrom), bytes);
        }
        ~Inserter() {
            auto inserts = nInserts;
            if constexpr (!std::is_nothrow_copy_constructible_v<T>) {
                if (displaceFrom != displaceTo) {
                    ::memmove(static_cast<void *>(displaceFrom), static_cast<void *>(displaceTo), bytes);
                    inserts -= qAbs(displaceFrom - displaceTo);
                }
            }
            data->size += inserts;
        }
        Q_DISABLE_COPY(Inserter)

        void insertRange(const T *source, qsizetype n)
        {
            while (n--) {
                new (displaceFrom) T(*source);
                ++source;
                ++displaceFrom;
            }
            verifyPost();
        }

        void insertFill(const T &t, qsizetype n)
        {
            while (n--) {
                new (displaceFrom) T(t);
                ++displaceFrom;
            }
            verifyPost();
        }

        void insertOne(T &&t)
        {
            new (displaceFrom) T(std::move(t));
            ++displaceFrom;
            verifyPost();
        }

    };


    void insert(qsizetype i, const T *data, qsizetype n)
    {
        const bool growsAtBegin = this->size != 0 && i == 0;
        const auto pos = growsAtBegin ? Data::GrowsAtBeginning : Data::GrowsAtEnd;

        DataPointer oldData;
        this->detachAndGrow(pos, n, &data, &oldData);
        Q_ASSERT((pos == Data::GrowsAtBeginning && this->freeSpaceAtBegin() >= n) ||
                 (pos == Data::GrowsAtEnd && this->freeSpaceAtEnd() >= n));

        if (growsAtBegin) {
            // copy construct items in reverse order at the begin
            Q_ASSERT(this->freeSpaceAtBegin() >= n);
            while (n) {
                --n;
                new (this->begin() - 1) T(data[n]);
                --this->ptr;
                ++this->size;
            }
        } else {
            Inserter(this, i, n).insertRange(data, n);
        }
    }

    void insert(qsizetype i, qsizetype n, parameter_type t)
    {
        T copy(t);

        const bool growsAtBegin = this->size != 0 && i == 0;
        const auto pos = growsAtBegin ? Data::GrowsAtBeginning : Data::GrowsAtEnd;

        this->detachAndGrow(pos, n, nullptr, nullptr);
        Q_ASSERT((pos == Data::GrowsAtBeginning && this->freeSpaceAtBegin() >= n) ||
                 (pos == Data::GrowsAtEnd && this->freeSpaceAtEnd() >= n));

        if (growsAtBegin) {
            // copy construct items in reverse order at the begin
            Q_ASSERT(this->freeSpaceAtBegin() >= n);
            while (n--) {
                new (this->begin() - 1) T(copy);
                --this->ptr;
                ++this->size;
            }
        } else {
            Inserter(this, i, n).insertFill(copy, n);
        }
    }

    template<typename... Args>
    void emplace(qsizetype i, Args &&... args)
    {
        bool detach = this->needsDetach();
        if (!detach) {
            if (i == this->size && this->freeSpaceAtEnd()) {
                new (this->end()) T(std::forward<Args>(args)...);
                ++this->size;
                return;
            }
            if (i == 0 && this->freeSpaceAtBegin()) {
                new (this->begin() - 1) T(std::forward<Args>(args)...);
                --this->ptr;
                ++this->size;
                return;
            }
        }
        T tmp(std::forward<Args>(args)...);
        const bool growsAtBegin = this->size != 0 && i == 0;
        const auto pos = growsAtBegin ? Data::GrowsAtBeginning : Data::GrowsAtEnd;

        this->detachAndGrow(pos, 1, nullptr, nullptr);
        if (growsAtBegin) {
            Q_ASSERT(this->freeSpaceAtBegin());
            new (this->begin() - 1) T(std::move(tmp));
            --this->ptr;
            ++this->size;
        } else {
            Inserter(this, i, 1).insertOne(std::move(tmp));
        }
    }

    void erase(T *b, qsizetype n)
    {
        T *e = b + n;

        Q_ASSERT(this->isMutable());
        Q_ASSERT(b < e);
        Q_ASSERT(b >= this->begin() && b < this->end());
        Q_ASSERT(e > this->begin() && e <= this->end());

        // Comply with std::vector::erase(): erased elements and all after them
        // are invalidated. However, erasing from the beginning effectively
        // means that all iterators are invalidated. We can use this freedom to
        // erase by moving towards the end.

        std::destroy(b, e);
        if (b == this->begin() && e != this->end()) {
            this->ptr = e;
        } else if (e != this->end()) {
            memmove(static_cast<void *>(b), static_cast<const void *>(e), (static_cast<const T *>(this->end()) - e)*sizeof(T));
        }
        this->size -= n;
    }

    void reallocate(qsizetype alloc, QArrayData::AllocationOption option)
    {
        auto pair = Data::reallocateUnaligned(this->d, this->ptr, alloc, option);
        Q_CHECK_PTR(pair.second);
        Q_ASSERT(pair.first != nullptr);
        this->d = pair.first;
        this->ptr = pair.second;
    }
};

template <class T, class = void>
struct QArrayOpsSelector
{
    typedef QGenericArrayOps<T> Type;
};

template <class T>
struct QArrayOpsSelector<T,
    typename std::enable_if<
        !QTypeInfo<T>::isComplex && QTypeInfo<T>::isRelocatable
    >::type>
{
    typedef QPodArrayOps<T> Type;
};

template <class T>
struct QArrayOpsSelector<T,
    typename std::enable_if<
        QTypeInfo<T>::isComplex && QTypeInfo<T>::isRelocatable
    >::type>
{
    typedef QMovableArrayOps<T> Type;
};

template <class T>
struct QCommonArrayOps : QArrayOpsSelector<T>::Type
{
    using Base = typename QArrayOpsSelector<T>::Type;
    using Data = QTypedArrayData<T>;
    using DataPointer = QArrayDataPointer<T>;
    using parameter_type = typename Base::parameter_type;

protected:
    using Self = QCommonArrayOps<T>;

public:
    // using Base::truncate;
    // using Base::destroyAll;

    template<typename It>
    void appendIteratorRange(It b, It e, QtPrivate::IfIsForwardIterator<It> = true)
    {
        Q_ASSERT(this->isMutable() || b == e);
        Q_ASSERT(!this->isShared() || b == e);
        const qsizetype distance = std::distance(b, e);
        Q_ASSERT(distance >= 0 && distance <= this->allocatedCapacity() - this->size);
        Q_UNUSED(distance);

#if __cplusplus >= 202002L && defined(__cpp_concepts) && defined(__cpp_lib_concepts)
        constexpr bool canUseCopyAppend =
                std::contiguous_iterator<It> &&
                std::is_same_v<
                    std::remove_cv_t<typename std::iterator_traits<It>::value_type>,
                    T
                >;
        if constexpr (canUseCopyAppend) {
            this->copyAppend(std::to_address(b), std::to_address(e));
        } else
#endif
        {
            T *iter = this->end();
            for (; b != e; ++iter, ++b) {
                new (iter) T(*b);
                ++this->size;
            }
        }
    }

    // slightly higher level API than copyAppend() that also preallocates space
    void growAppend(const T *b, const T *e)
    {
        if (b == e)
            return;
        Q_ASSERT(b < e);
        const qsizetype n = e - b;
        DataPointer old;

        // points into range:
        if (QtPrivate::q_points_into_range(b, *this))
            this->detachAndGrow(QArrayData::GrowsAtEnd, n, &b, &old);
        else
            this->detachAndGrow(QArrayData::GrowsAtEnd, n, nullptr, nullptr);
        Q_ASSERT(this->freeSpaceAtEnd() >= n);
        // b might be updated so use [b, n)
        this->copyAppend(b, b + n);
    }

    void appendUninitialized(qsizetype newSize)
    {
        Q_ASSERT(this->isMutable());
        Q_ASSERT(!this->isShared());
        Q_ASSERT(newSize > this->size);
        Q_ASSERT(newSize - this->size <= this->freeSpaceAtEnd());


        T *const b = this->begin() + this->size;
        T *const e = this->begin() + newSize;
        if constexpr (std::is_constructible_v<T, Qt::Initialization>)
            std::uninitialized_fill(b, e, Qt::Uninitialized);
        else
            std::uninitialized_default_construct(b, e);
        this->size = newSize;
    }

    using Base::assign;

    template <typename InputIterator, typename Projection = q20::identity>
    void assign(InputIterator first, InputIterator last, Projection proj = {})
    {
        // This function only provides the basic exception guarantee.
        using Category = typename std::iterator_traits<InputIterator>::iterator_category;
        constexpr bool IsFwdIt = std::is_convertible_v<Category, std::forward_iterator_tag>;

        const qsizetype n = IsFwdIt ? std::distance(first, last) : 0;
        bool undoPrependOptimization = true;
        bool needCapacity = n > this->constAllocatedCapacity();
        if (needCapacity || this->needsDetach()) {
            qsizetype newCapacity = this->detachCapacity(n);
            bool wasLastRef = !this->deref();
            if (wasLastRef && needCapacity) {
                // free memory we can't reuse
                this->destroyAll();
                Data::deallocate(this->d);
            }
            if (!needCapacity && wasLastRef) {
                // we were the last reference and can reuse the storage
                this->d->ref_.storeRelaxed(1);
            } else {
                // we must allocate new memory
                std::tie(this->d, this->ptr) = Data::allocate(newCapacity);
                this->size = 0;
                undoPrependOptimization = false;
            }
        }

        if constexpr (!std::is_nothrow_constructible_v<T, decltype(std::invoke(proj, *first))>
                      || !std::is_nothrow_invocable_v<Projection, decltype(*first)>)
        {
            // If construction can throw, and we have freeSpaceAtBegin(),
            // it's easiest to just clear the container and start fresh.
            // The alternative would be to keep track of two active, disjoint ranges.
            if (undoPrependOptimization) {
                this->truncate(0);
                this->setBegin(Data::dataStart(this->d, alignof(typename Data::AlignmentDummy)));
                undoPrependOptimization = false;
            }
        }

        const auto dend = this->end();
        T *dst = this->begin();
        T *capacityBegin = dst;
        if (undoPrependOptimization) {
            capacityBegin = Data::dataStart(this->d, alignof(typename Data::AlignmentDummy));
            this->setBegin(capacityBegin); // undo prepend optimization
        }

        assign_impl(first, last, capacityBegin, dst, dend, proj, Category{});
    }

    template <typename InputIterator, typename Projection>
    void assign_impl(InputIterator first, InputIterator last, T *capacityBegin, T *dst, T *dend,
                     Projection proj, std::input_iterator_tag)
    {
        if (qsizetype offset = dst - capacityBegin) {
            T *prependBufferEnd = dst;
            dst = capacityBegin;

            // By construction, the following loop is nothrow!
            // (otherwise, we can't reach here)
            // Assumes InputIterator operations don't throw.
            // (but we can't statically assert that, as these operations
            //  have preconditons, so typically aren't noexcept)
            while (true) {
                if (dst == prependBufferEnd) {  // ran out of prepend buffer space
                    this->size += offset;
                    // we now have a contiguous buffer, continue with the main loop:
                    break;
                }
                if (first == last) {            // ran out of elements to assign
                    std::destroy(prependBufferEnd, dend);
                    this->size = dst - this->begin();
                    return;
                }
                // construct element in prepend buffer
                q20::construct_at(dst, std::invoke(proj, *first));
                ++dst;
                ++first;
            }
        }
        while (true) {
            if (first == last) {    // ran out of elements to assign
                std::destroy(dst, dend);
                break;
            }
            if (dst == dend) {      // ran out of existing elements to overwrite
                do {
                    this->emplace(this->size, std::invoke(proj, *first));
                } while (++first != last);
                return;         // size() is already correct (and dst invalidated)!
            }
            *dst = std::invoke(proj, *first);    // overwrite existing element
            ++dst;
            ++first;
        }
        this->size = dst - this->begin();
    }

    template <typename InputIterator, typename Projection>
    void assign_impl(InputIterator first, InputIterator last, T *capacityBegin, T *dst, T *dend,
                     Projection proj, std::forward_iterator_tag)
    {
        constexpr bool IsIdentity = std::is_same_v<Projection, q20::identity>;
        const qsizetype n = std::distance(first, last);
        if constexpr (IsIdentity && !QTypeInfo<T>::isComplex) {
            // For non-complex types, we prefer a single std::copy() -> memcpy()
            // call. We can do that because either the default constructor is
            // trivial (so the lifetime has started) or the copy constructor is
            // (and won't care what the stored value is).
            std::copy(first, last, capacityBegin);
        } else {
            // There are two possibilities:
            // 1) fewer elements than the current allocated space
            //    | prepend buffer | array |  destroy  |
            // 2) more elements than the current allocated space
            //    | prepend buffer | array | construct |
            //
            // Both the prepend buffer and the current array may be empty.

            // construct elements in the prepend buffer
            while (first != last && capacityBegin != dst) {
                q20::construct_at(capacityBegin, std::invoke(proj, *first));
                ++first;
                ++capacityBegin;
            }

            // overwrite elements in the existing array
            while (first != last && dst != dend) {
                *dst = std::invoke(proj, *first);    // overwrite existing element
                ++first;
                ++dst;
            }

            // construct new elements in the append buffer
            while (first != last) {
                q20::construct_at(dst, std::invoke(proj, *first));
                ++first;
                ++dst;
            }
            // or destroy elements from the existing array
            if (dst < dend)
                std::destroy(dst, dend);
        }
        this->size = n;
    }
};

} // namespace QtPrivate

template <class T>
struct QArrayDataOps
    : QtPrivate::QCommonArrayOps<T>
{
};

QT_END_NAMESPACE

#endif // include guard
