/****************************************************************************
**
** Copyright (C) 2017 Ford Motor Company
** Contact: https://www.qt.io/licensing/
**
** This file is part of the QtRemoteObjects module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:GPL-EXCEPT$
** 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 General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** 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-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/

#include <QtTest/QtTest>
#include <QModelIndex>

namespace {

inline bool compareIndices(const QModelIndex &lhs, const QModelIndex &rhs)
{
    QModelIndex left = lhs;
    QModelIndex right = rhs;
    while (left.row() == right.row() && left.column() == right.column() && left.isValid() && right.isValid()) {
        left = left.parent();
        right = right.parent();
    }
    if (left.isValid() || right.isValid())
        return false;
    return true;
}

struct WaitForDataChanged
{
    struct IndexPair
    {
        QModelIndex topLeft;
        QModelIndex bottomRight;
    };

    WaitForDataChanged(const QVector<QModelIndex> &pending, QSignalSpy *spy) : m_pending(pending), m_spy(spy){}
    bool wait()
    {
        Q_ASSERT(m_spy);
        const int maxRuns = std::min(m_pending.size(), 100);
        int runs = 0;
        bool cancel = false;
        while (!cancel) {
            const int numSignals = m_spy->size();
            for (int i = 0; i < numSignals; ++i) {
                const QList<QVariant> &signal = m_spy->takeFirst();
                IndexPair pair = extractPair(signal);
                checkAndRemoveRange(pair.topLeft, pair.bottomRight);
                cancel = m_pending.isEmpty();
            }
            if (!cancel)
                m_spy->wait();
            ++runs;
            if (runs >= maxRuns)
                cancel = true;
        }
        return runs < maxRuns;
    }

    static IndexPair extractPair(const QList<QVariant> &signal)
    {
        IndexPair pair;
        if (signal.size() != 3)
            return pair;
        const bool matchingTypes = signal[0].type() == QVariant::nameToType("QModelIndex")
                                   && signal[1].type() == QVariant::nameToType("QModelIndex")
                                   && signal[2].type() == QVariant::nameToType("QVector<int>");
        if (!matchingTypes)
            return pair;
        const QModelIndex topLeft = signal[0].value<QModelIndex>();
        const QModelIndex bottomRight = signal[1].value<QModelIndex>();
        pair.topLeft = topLeft;
        pair.bottomRight = bottomRight;
        return pair;
    }

    void checkAndRemoveRange(const QModelIndex &topLeft, const QModelIndex &bottomRight)
    {
        QVERIFY(topLeft.parent() == bottomRight.parent());
        const auto isInRange = [topLeft, bottomRight] (const QModelIndex &pending) noexcept -> bool {
            if (pending.isValid()  && compareIndices(pending.parent(), topLeft.parent())) {
                const bool fitLeft = topLeft.column() <= pending.column();
                const bool fitRight = bottomRight.column() >= pending.column();
                const bool fitTop = topLeft.row() <= pending.row();
                const bool fitBottom = bottomRight.row() >= pending.row();
                if (fitLeft && fitRight && fitTop && fitBottom)
                    return true;
            }
            return false;
        };
        m_pending.erase(std::remove_if(m_pending.begin(), m_pending.end(), isInRange),
                        m_pending.end());
    }

    QVector<QModelIndex> m_pending;
    QSignalSpy *m_spy;
};

} // namespace
