/****************************************************************************
**
** 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: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$
**
****************************************************************************/

#include "qremoteobjectabstractitemmodelreplica.h"
#include "qremoteobjectabstractitemmodelreplica_p.h"

#include "qremoteobjectnode.h"

#include <QtCore/qdebug.h>
#include <QtCore/qrect.h>
#include <QtCore/qpoint.h>

QT_BEGIN_NAMESPACE

inline QDebug operator<<(QDebug stream, const RequestedData &data)
{
    return stream.nospace() << "RequestedData[start=" << data.start << ", end=" << data.end << ", roles=" << data.roles << "]";
}

CacheData::CacheData(QAbstractItemModelReplicaImplementation *model, CacheData *parentItem)
    : replicaModel(model)
    , parent(parentItem)
    , hasChildren(false)
    , columnCount(0)
    , rowCount(0)
{
    if (parent)
        replicaModel->m_activeParents.insert(parent);
}

CacheData::~CacheData() {
    if (parent && !replicaModel->m_activeParents.empty())
        replicaModel->m_activeParents.erase(this);
}

QAbstractItemModelReplicaImplementation::QAbstractItemModelReplicaImplementation()
    : QRemoteObjectReplica()
    , m_selectionModel(0)
    , m_rootItem(this)
{
    QAbstractItemModelReplicaImplementation::registerMetatypes();
    initializeModelConnections();
    connect(this, &QAbstractItemModelReplicaImplementation::availableRolesChanged, this, [this]{
        m_availableRoles.clear();
    });
}

QAbstractItemModelReplicaImplementation::QAbstractItemModelReplicaImplementation(QRemoteObjectNode *node, const QString &name)
    : QRemoteObjectReplica(ConstructWithNode)
    , m_selectionModel(0)
    , m_rootItem(this)
{
    QAbstractItemModelReplicaImplementation::registerMetatypes();
    initializeModelConnections();
    initializeNode(node, name);
    connect(this, &QAbstractItemModelReplicaImplementation::availableRolesChanged, this, [this]{
        m_availableRoles.clear();
    });
}

QAbstractItemModelReplicaImplementation::~QAbstractItemModelReplicaImplementation()
{
    m_rootItem.clear();
    qDeleteAll(m_pendingRequests);
}

void QAbstractItemModelReplicaImplementation::initialize()
{
    QVariantList properties;
    properties << QVariant::fromValue(QVector<int>());
    properties << QVariant::fromValue(QIntHash());
    setProperties(properties);
}

void QAbstractItemModelReplicaImplementation::registerMetatypes()
{
    static bool alreadyRegistered = false;
    if (alreadyRegistered)
        return;

    alreadyRegistered = true;
    qRegisterMetaType<QAbstractItemModel*>();
    qRegisterMetaType<Qt::Orientation>();
    qRegisterMetaType<QVector<Qt::Orientation> >();
    qRegisterMetaTypeStreamOperators<ModelIndex>();
    qRegisterMetaTypeStreamOperators<IndexList>();
    qRegisterMetaTypeStreamOperators<DataEntries>();
    qRegisterMetaTypeStreamOperators<MetaAndDataEntries>();
    qRegisterMetaTypeStreamOperators<Qt::Orientation>();
    qRegisterMetaTypeStreamOperators<QVector<Qt::Orientation> >();
    qRegisterMetaTypeStreamOperators<QItemSelectionModel::SelectionFlags>();
    qRegisterMetaType<QItemSelectionModel::SelectionFlags>();
    qRegisterMetaType<QSize>();
    qRegisterMetaType<QIntHash>();
    qRegisterMetaTypeStreamOperators<QIntHash>();
}

void QAbstractItemModelReplicaImplementation::initializeModelConnections()
{
    connect(this, &QAbstractItemModelReplicaImplementation::dataChanged, this, &QAbstractItemModelReplicaImplementation::onDataChanged);
    connect(this, &QAbstractItemModelReplicaImplementation::rowsInserted, this, &QAbstractItemModelReplicaImplementation::onRowsInserted);
    connect(this, &QAbstractItemModelReplicaImplementation::columnsInserted, this, &QAbstractItemModelReplicaImplementation::onColumnsInserted);
    connect(this, &QAbstractItemModelReplicaImplementation::rowsRemoved, this, &QAbstractItemModelReplicaImplementation::onRowsRemoved);
    connect(this, &QAbstractItemModelReplicaImplementation::rowsMoved, this, &QAbstractItemModelReplicaImplementation::onRowsMoved);
    connect(this, &QAbstractItemModelReplicaImplementation::currentChanged, this, &QAbstractItemModelReplicaImplementation::onCurrentChanged);
    connect(this, &QAbstractItemModelReplicaImplementation::modelReset, this, &QAbstractItemModelReplicaImplementation::onModelReset);
    connect(this, &QAbstractItemModelReplicaImplementation::headerDataChanged, this, &QAbstractItemModelReplicaImplementation::onHeaderDataChanged);
}

inline void removeIndexFromRow(const QModelIndex &index, const QVector<int> &roles, CachedRowEntry *entry)
{
    CachedRowEntry &entryRef = *entry;
    if (index.column() < entryRef.size()) {
        CacheEntry &entry = entryRef[index.column()];
        if (roles.isEmpty()) {
            entry.data.clear();
        } else {
            for (int role : roles)
                entry.data.remove(role);
        }
    }
}

void QAbstractItemModelReplicaImplementation::onReplicaCurrentChanged(const QModelIndex &current, const QModelIndex &previous)
{
    Q_UNUSED(previous)
    IndexList currentIndex = toModelIndexList(current, q);
    qCDebug(QT_REMOTEOBJECT_MODELS) << Q_FUNC_INFO << "current=" << currentIndex;
    replicaSetCurrentIndex(currentIndex, QItemSelectionModel::Clear|QItemSelectionModel::Select|QItemSelectionModel::Current);
}

void QAbstractItemModelReplicaImplementation::setModel(QAbstractItemModelReplica *model)
{
    q = model;
    setParent(model);
    m_selectionModel.reset(new QItemSelectionModel(model));
    connect(m_selectionModel.data(), &QItemSelectionModel::currentChanged, this, &QAbstractItemModelReplicaImplementation::onReplicaCurrentChanged);
}

bool QAbstractItemModelReplicaImplementation::clearCache(const IndexList &start, const IndexList &end, const QVector<int> &roles = QVector<int>())
{
    Q_ASSERT(start.size() == end.size());

    bool ok = true;
    const QModelIndex startIndex = toQModelIndex(start, q, &ok);
    if (!ok)
        return false;
    const QModelIndex endIndex = toQModelIndex(end, q, &ok);
    if (!ok)
        return false;
    Q_ASSERT(startIndex.isValid());
    Q_ASSERT(endIndex.isValid());
    Q_ASSERT(startIndex.parent() == endIndex.parent());
    Q_UNUSED(endIndex);
    QModelIndex parentIndex = startIndex.parent();
    auto parentItem = cacheData(parentIndex);

    const int startRow = start.last().row;
    const int lastRow = end.last().row;
    const int startColumn = start.last().column;
    const int lastColumn = end.last().column;
    for (int row = startRow; row <= lastRow; ++row) {
        Q_ASSERT_X(row >= 0 && row < parentItem->rowCount, __FUNCTION__, qPrintable(QString(QLatin1String("0 <= %1 < %2")).arg(row).arg(parentItem->rowCount)));
        auto item = parentItem->children.get(row);
        if (item) {
            CachedRowEntry *entry = &(item->cachedRowEntry);
            for (int column = startColumn; column <= lastColumn; ++column)
                removeIndexFromRow(q->index(row, column, parentIndex), roles, entry);
        }
    }
    return true;
}

void QAbstractItemModelReplicaImplementation::onDataChanged(const IndexList &start, const IndexList &end, const QVector<int> &roles)
{
    qCDebug(QT_REMOTEOBJECT_MODELS) << Q_FUNC_INFO << "start=" << start << "end=" << end << "roles=" << roles;

    // we need to clear the cache to make sure the new remote data is fetched if the new data call is happening
    if (clearCache(start, end, roles)) {
        bool ok = true;
        const QModelIndex startIndex = toQModelIndex(start, q, &ok);
        if (!ok)
            return;
        const QModelIndex endIndex = toQModelIndex(end, q, &ok);
        if (!ok)
            return;
        Q_ASSERT(startIndex.parent() == endIndex.parent());
        auto parentItem = cacheData(startIndex.parent());
        int startRow = start.last().row;
        int endRow = end.last().row;
        bool dataChanged = false;
        while (startRow <= endRow) {
            for (;startRow <= endRow; startRow++) {
                if (parentItem->children.exists(startRow))
                    break;
            }

            if (startRow  > endRow)
                break;

            RequestedData data;
            data.roles = roles;
            data.start = start;
            data.start.last().row = startRow;

            while (startRow <= endRow && parentItem->children.exists(startRow))
                ++startRow;

            data.end = end;
            data.end.last().row = startRow -1;

            m_requestedData.append(data);
            dataChanged = true;
        }

        if (dataChanged)
            QMetaObject::invokeMethod(this, "fetchPendingData", Qt::QueuedConnection);
    }
}

void QAbstractItemModelReplicaImplementation::onRowsInserted(const IndexList &parent, int start, int end)
{
    qCDebug(QT_REMOTEOBJECT_MODELS) << Q_FUNC_INFO << "start=" << start << "end=" << end << "parent=" << parent;

    bool treeFullyLazyLoaded = true;
    const QModelIndex parentIndex = toQModelIndex(parent, q, &treeFullyLazyLoaded, true);
    if (!treeFullyLazyLoaded)
        return;

    auto parentItem = cacheData(parentIndex);
    q->beginInsertRows(parentIndex, start, end);
    parentItem->insertChildren(start, end);
    q->endInsertRows();
    if (!parentItem->hasChildren && parentItem->columnCount > 0) {
        parentItem->hasChildren = true;
        emit q->dataChanged(parentIndex, parentIndex);
    }
}

void QAbstractItemModelReplicaImplementation::onColumnsInserted(const IndexList &parent, int start, int end)
{
    qCDebug(QT_REMOTEOBJECT_MODELS) << Q_FUNC_INFO << "start=" << start << "end=" << end << "parent=" << parent;

    bool treeFullyLazyLoaded = true;
    const QModelIndex parentIndex = toQModelIndex(parent, q, &treeFullyLazyLoaded);
    if (!treeFullyLazyLoaded)
        return;

    //Since we need to support QAIM and models that don't emit columnCountChanged
    //check if we have a constant columnCount everywhere if thats the case don't insert
    //more columns
    auto parentItem = cacheData(parentIndex);
    auto parentOfParent = parentItem->parent;
    if (parentOfParent && parentItem != &m_rootItem)
        if (parentOfParent->columnCount == parentItem->columnCount)
            return;
    q->beginInsertColumns(parentIndex, start, end);
    parentItem->columnCount += end - start + 1;
    q->endInsertColumns();
    if (!parentItem->hasChildren && parentItem->children.size() > 0) {
        parentItem->hasChildren = true;
        emit q->dataChanged(parentIndex, parentIndex);
    }

}

void QAbstractItemModelReplicaImplementation::onRowsRemoved(const IndexList &parent, int start, int end)
{
    qCDebug(QT_REMOTEOBJECT_MODELS) << Q_FUNC_INFO << "start=" << start << "end=" << end << "parent=" << parent;

    bool treeFullyLazyLoaded = true;
    const QModelIndex parentIndex = toQModelIndex(parent, q, &treeFullyLazyLoaded);
    if (!treeFullyLazyLoaded)
        return;

    auto parentItem = cacheData(parentIndex);
    q->beginRemoveRows(parentIndex, start, end);
    if (parentItem)
        parentItem->removeChildren(start, end);
    q->endRemoveRows();
}

void QAbstractItemModelReplicaImplementation::onRowsMoved(IndexList srcParent, int srcRow, int count, IndexList destParent, int destRow)
{
    qCDebug(QT_REMOTEOBJECT_MODELS) << Q_FUNC_INFO;

    const QModelIndex sourceParent = toQModelIndex(srcParent, q);
    const QModelIndex destinationParent = toQModelIndex(destParent, q);
    Q_ASSERT(!sourceParent.isValid());
    Q_ASSERT(!destinationParent.isValid());
    q->beginMoveRows(sourceParent, srcRow, count, destinationParent, destRow);
//TODO misses parents...
    IndexList start, end;
    start << ModelIndex(srcRow, 0);
    end << ModelIndex(srcRow + count, q->columnCount(sourceParent)-1);
    clearCache(start, end);
    IndexList start2, end2;
    start2 << ModelIndex(destRow, 0);
    end2 << ModelIndex(destRow + count, q->columnCount(destinationParent)-1);
    clearCache(start2, end2);
    q->endMoveRows();
}

void QAbstractItemModelReplicaImplementation::onCurrentChanged(IndexList current, IndexList previous)
{
    qCDebug(QT_REMOTEOBJECT_MODELS) << Q_FUNC_INFO << "current=" << current << "previous=" << previous;
    Q_UNUSED(previous);
    Q_ASSERT(m_selectionModel);
    bool ok;
    // If we have several tree models sharing a selection model, we
    // can't guarantee that all Replicas have the selected cell
    // available.
    const QModelIndex currentIndex = toQModelIndex(current, q, &ok);
    // Ignore selection if we can't find the desired cell.
    if (ok)
        m_selectionModel->setCurrentIndex(currentIndex, QItemSelectionModel::Clear|QItemSelectionModel::Select|QItemSelectionModel::Current);
}

void QAbstractItemModelReplicaImplementation::handleInitDone(QRemoteObjectPendingCallWatcher *watcher)
{
    qCDebug(QT_REMOTEOBJECT_MODELS) << Q_FUNC_INFO;

    handleModelResetDone(watcher);
    m_initDone = true;
    emit q->initialized();
}

void QAbstractItemModelReplicaImplementation::handleModelResetDone(QRemoteObjectPendingCallWatcher *watcher)
{
    QSize size;
    if (m_initialAction == QtRemoteObjects::FetchRootSize)
        size = watcher->returnValue().toSize();
    else {
        Q_ASSERT(watcher->returnValue().canConvert<MetaAndDataEntries>());
        size = watcher->returnValue().value<MetaAndDataEntries>().size;
    }

    qCDebug(QT_REMOTEOBJECT_MODELS) << Q_FUNC_INFO << "size=" << size;

    q->beginResetModel();
    m_rootItem.clear();
    if (size.height() > 0) {
        m_rootItem.rowCount = size.height();
        m_rootItem.hasChildren = true;
    }

    m_rootItem.columnCount = size.width();
    m_headerData[0].resize(size.width());
    m_headerData[1].resize(size.height());
    {
        QVector<CacheEntry> &headerEntries = m_headerData[0];
        for (int i = 0; i < size.width(); ++i )
            headerEntries[i].data.clear();
    }
    {
        QVector<CacheEntry> &headerEntries = m_headerData[1];
        for (int i = 0; i < size.height(); ++i )
            headerEntries[i].data.clear();
    }
    if (m_initialAction == QtRemoteObjects::PrefetchData) {
        auto entries = watcher->returnValue().value<MetaAndDataEntries>();
        for (int i = 0; i < entries.data.size(); ++i)
            fillCache(entries.data[i], entries.roles);
    }
    q->endResetModel();
    m_pendingRequests.removeAll(watcher);
    delete watcher;
}

void QAbstractItemModelReplicaImplementation::handleSizeDone(QRemoteObjectPendingCallWatcher *watcher)
{
    SizeWatcher *sizeWatcher = static_cast<SizeWatcher*>(watcher);
    const QSize size = sizeWatcher->returnValue().toSize();
    auto parentItem = cacheData(sizeWatcher->parentList);
    const QModelIndex parent = toQModelIndex(sizeWatcher->parentList, q);

    if (size.width() != parentItem->columnCount) {
        const int columnCount = std::max(0, parentItem->columnCount);
        Q_ASSERT_X(size.width() >= parentItem->columnCount, __FUNCTION__, "The column count should only shrink in columnsRemoved!!");
        parentItem->columnCount = size.width();
        if (size.width() > columnCount) {
            Q_ASSERT(size.width() > 0);
            q->beginInsertColumns(parent, columnCount, size.width() - 1);
            q->endInsertColumns();
        } else {
            Q_ASSERT_X(size.width() == columnCount, __FUNCTION__, qPrintable(QString(QLatin1String("%1 != %2")).arg(size.width()).arg(columnCount)));
        }
    }

    Q_ASSERT_X(size.height() >= parentItem->rowCount, __FUNCTION__, "The new size and the current size should match!!");
    if (!parentItem->rowCount) {
        if (size.height() > 0) {
            q->beginInsertRows(parent, 0, size.height() - 1);
            parentItem->rowCount = size.height();
            q->endInsertRows();
        }
    } else {
        Q_ASSERT_X(parentItem->rowCount == size.height(), __FUNCTION__, qPrintable(QString(QLatin1String("%1 != %2")).arg(parentItem->rowCount).arg(size.height())));
    }
    m_pendingRequests.removeAll(watcher);
    delete watcher;
}

void QAbstractItemModelReplicaImplementation::init()
{
    qCDebug(QT_REMOTEOBJECT_MODELS) << Q_FUNC_INFO << this->node()->objectName();
    QRemoteObjectPendingCallWatcher *watcher = doModelReset();
    connect(watcher, &QRemoteObjectPendingCallWatcher::finished, this, &QAbstractItemModelReplicaImplementation::handleInitDone);
}

QRemoteObjectPendingCallWatcher* QAbstractItemModelReplicaImplementation::doModelReset()
{
    qDeleteAll(m_pendingRequests);
    m_pendingRequests.clear();
    IndexList parentList;
    QRemoteObjectPendingCallWatcher *watcher;
    if (m_initialAction == QtRemoteObjects::FetchRootSize) {
        auto call = replicaSizeRequest(parentList);
        watcher = new SizeWatcher(parentList, call);
    } else {
        auto call = replicaCacheRequest(m_rootItem.children.cacheSize, m_initialFetchRolesHint);
        watcher = new QRemoteObjectPendingCallWatcher(call);
    }
    m_pendingRequests.push_back(watcher);
    return watcher;
}

inline void fillCacheEntry(CacheEntry *entry, const IndexValuePair &pair, const QVector<int> &roles)
{
    Q_ASSERT(entry);

    const QVariantList &data = pair.data;
    Q_ASSERT(roles.size() == data.size());

    entry->flags = pair.flags;

    qCDebug(QT_REMOTEOBJECT_MODELS) << Q_FUNC_INFO << "data.size=" << data.size();
    for (int i = 0; i < data.size(); ++i) {
        const int role = roles[i];
        const QVariant dataVal = data[i];
        qCDebug(QT_REMOTEOBJECT_MODELS) << Q_FUNC_INFO << "role=" << role << "data=" << dataVal;
        entry->data[role] = dataVal;
    }
}

inline void fillRow(CacheData *item, const IndexValuePair &pair, const QAbstractItemModel *model, const QVector<int> &roles)
{
    CachedRowEntry &rowRef = item->cachedRowEntry;
    const QModelIndex index = toQModelIndex(pair.index, model);
    Q_ASSERT(index.isValid());
    qCDebug(QT_REMOTEOBJECT_MODELS) << Q_FUNC_INFO << "row=" << index.row() << "column=" << index.column();
    if (index.column() == 0)
        item->hasChildren = pair.hasChildren;
    bool existed = false;
    for (int i = 0; i < rowRef.size(); ++i) {
        if (i == index.column()) {
            fillCacheEntry(&rowRef[i], pair, roles);
            existed = true;
        }
    }
    qCDebug(QT_REMOTEOBJECT_MODELS) << Q_FUNC_INFO << "existed=" << existed;
    if (!existed) {
        CacheEntry entries;
        fillCacheEntry(&entries, pair, roles);
        rowRef.append(entries);
    }
}

int collectEntriesForRow(DataEntries* filteredEntries, int row, const DataEntries &entries, int startIndex)
{
    Q_ASSERT(filteredEntries);
    const int size = entries.data.size();
    for (int i = startIndex; i < size; ++i)
    {
        const IndexValuePair &pair = entries.data[i];
        if (pair.index.last().row == row)
            filteredEntries->data << pair;
        else
            return i;
    }
    return size;
}

void QAbstractItemModelReplicaImplementation::fillCache(const IndexValuePair &pair, const QVector<int> &roles)
{
    if (auto item = createCacheData(pair.index)) {
        fillRow(item, pair, q, roles);
        item->rowCount = pair.size.height();
        item->columnCount = pair.size.width();
    }
    for (const auto &it : pair.children)
        fillCache(it, roles);
}

void QAbstractItemModelReplicaImplementation::requestedData(QRemoteObjectPendingCallWatcher *qobject)
{
    RowWatcher *watcher = static_cast<RowWatcher *>(qobject);
    Q_ASSERT(watcher);
    Q_ASSERT(watcher->start.size() == watcher->end.size());

    qCDebug(QT_REMOTEOBJECT_MODELS) << Q_FUNC_INFO << "start=" << watcher->start << "end=" << watcher->end;

    IndexList parentList = watcher->start;
    Q_ASSERT(!parentList.isEmpty());
    parentList.pop_back();
    auto parentItem = cacheData(parentList);
    DataEntries entries = watcher->returnValue().value<DataEntries>();

    const int rowCount = parentItem->rowCount;
    const int columnCount = parentItem->columnCount;

    if (rowCount < 1 || columnCount < 1)
        return;

    const int startRow =  std::min(watcher->start.last().row, rowCount - 1);
    const int endRow = std::min(watcher->end.last().row, rowCount - 1);
    const int startColumn = std::min(watcher->start.last().column, columnCount - 1);
    const int endColumn = std::min(watcher->end.last().column, columnCount - 1);
    Q_ASSERT_X(startRow >= 0 && startRow < parentItem->rowCount, __FUNCTION__, qPrintable(QString(QLatin1String("0 <= %1 < %2")).arg(startRow).arg(parentItem->rowCount)));
    Q_ASSERT_X(endRow >= 0 && endRow < parentItem->rowCount, __FUNCTION__, qPrintable(QString(QLatin1String("0 <= %1 < %2")).arg(endRow).arg(parentItem->rowCount)));

    for (int i = 0; i < entries.data.size(); ++i) {
        IndexValuePair pair = entries.data[i];
        if (auto item = createCacheData(pair.index))
            fillRow(item, pair, q, watcher->roles);
    }

    const QModelIndex parentIndex = toQModelIndex(parentList, q);
    const QModelIndex startIndex = q->index(startRow, startColumn, parentIndex);
    const QModelIndex endIndex = q->index(endRow, endColumn, parentIndex);
    Q_ASSERT(startIndex.isValid());
    Q_ASSERT(endIndex.isValid());
    emit q->dataChanged(startIndex, endIndex, watcher->roles);
    m_pendingRequests.removeAll(watcher);
    delete watcher;
}

void QAbstractItemModelReplicaImplementation::fetchPendingData()
{
    if (m_requestedData.isEmpty())
        return;

    qCDebug(QT_REMOTEOBJECT_MODELS) << Q_FUNC_INFO << "m_requestedData.size=" << m_requestedData.size();

    std::vector<RequestedData> finalRequests;
    RequestedData curData;
    for (const RequestedData &data : qExchange(m_requestedData, {})) {
        qCDebug(QT_REMOTEOBJECT_MODELS) << Q_FUNC_INFO << "REQUESTED start=" << data.start << "end=" << data.end << "roles=" << data.roles;

        Q_ASSERT(!data.start.isEmpty());
        Q_ASSERT(!data.end.isEmpty());
        Q_ASSERT(data.start.size() == data.end.size());
        if (curData.start.isEmpty() || curData.start.last().row == -1 || curData.start.last().column == -1)
            curData = data;
        if (curData.start.size() != data.start.size()) {
            finalRequests.push_back(curData);
            curData = data;
        } else {
            if (data.start.size() > 1) {
                for (int i = 0; i < data.start.size() - 1; ++i) {
                    if (curData.start[i].row != data.start[i].row ||
                        curData.start[i].column != data.start[i].column) {
                        finalRequests.push_back(curData);
                        curData = data;
                    }
                }
            }

            const ModelIndex curIndStart = curData.start.last();
            const ModelIndex curIndEnd = curData.end.last();
            const ModelIndex dataIndStart = data.start.last();
            const ModelIndex dataIndEnd = data.end.last();
            const ModelIndex resStart(std::min(curIndStart.row, dataIndStart.row), std::min(curIndStart.column, dataIndStart.column));
            const ModelIndex resEnd(std::max(curIndEnd.row, dataIndEnd.row), std::max(curIndEnd.column, dataIndEnd.column));
            QVector<int> roles = curData.roles;
            if (!curData.roles.isEmpty()) {
                for (int role : data.roles) {
                    if (!curData.roles.contains(role))
                        roles.append(role);
                }
            }
            QRect firstRect( QPoint(curIndStart.row, curIndStart.column), QPoint(curIndEnd.row, curIndEnd.column));
            QRect secondRect( QPoint(dataIndStart.row, dataIndStart.column), QPoint(dataIndEnd.row, dataIndEnd.column));

            const bool borders = (qAbs(curIndStart.row - dataIndStart.row) == 1) ||
                                 (qAbs(curIndStart.column - dataIndStart.column) == 1) ||
                                 (qAbs(curIndEnd.row - dataIndEnd.row) == 1) ||
                                 (qAbs(curIndEnd.column - dataIndEnd.column) == 1);

            if ((resEnd.row - resStart.row < 100) && (firstRect.intersects(secondRect) || borders)) {
                IndexList start = curData.start;
                start.pop_back();
                start.push_back(resStart);
                IndexList end = curData.end;
                end.pop_back();
                end.push_back(resEnd);
                curData.start = start;
                curData.end = end;
                curData.roles = roles;
                Q_ASSERT(!start.isEmpty());
                Q_ASSERT(!end.isEmpty());
            } else {
                finalRequests.push_back(curData);
                curData = data;
            }
        }
    }
    finalRequests.push_back(curData);
    //qCDebug(QT_REMOTEOBJECT_MODELS) << "Final requests" << finalRequests;
    int rows = 0;
                                                                        // There is no point to eat more than can chew
    for (auto it = finalRequests.rbegin(); it != finalRequests.rend() && size_t(rows) < m_rootItem.children.cacheSize; ++it) {
        qCDebug(QT_REMOTEOBJECT_MODELS) << Q_FUNC_INFO << "FINAL start=" << it->start << "end=" << it->end << "roles=" << it->roles;

        QRemoteObjectPendingReply<DataEntries> reply = replicaRowRequest(it->start, it->end, it->roles);
        RowWatcher *watcher = new RowWatcher(it->start, it->end, it->roles, reply);
        rows += 1 + it->end.first().row - it->start.first().row;
        m_pendingRequests.push_back(watcher);
        connect(watcher, &RowWatcher::finished, this, &QAbstractItemModelReplicaImplementation::requestedData);
    }
}

void QAbstractItemModelReplicaImplementation::onModelReset()
{
    if (!m_initDone)
        return;

    qCDebug(QT_REMOTEOBJECT_MODELS) << Q_FUNC_INFO;
    QRemoteObjectPendingCallWatcher *watcher = doModelReset();
    connect(watcher, &QRemoteObjectPendingCallWatcher::finished, this, &QAbstractItemModelReplicaImplementation::handleModelResetDone);
}

void QAbstractItemModelReplicaImplementation::onHeaderDataChanged(Qt::Orientation orientation, int first, int last)
{
    // TODO clean cache
    const int index = orientation == Qt::Horizontal ? 0 : 1;
    QVector<CacheEntry> &entries = m_headerData[index];
    for (int i = first; i < last; ++i )
        entries[i].data.clear();
    emit q->headerDataChanged(orientation, first, last);
}

void QAbstractItemModelReplicaImplementation::fetchPendingHeaderData()
{
    QVector<int> roles;
    QVector<int> sections;
    QVector<Qt::Orientation> orientations;
    for (const RequestedHeaderData &data : qAsConst(m_requestedHeaderData)) {
        roles.push_back(data.role);
        sections.push_back(data.section);
        orientations.push_back(data.orientation);
    }
    QRemoteObjectPendingReply<QVariantList> reply = replicaHeaderRequest(orientations, sections, roles);
    HeaderWatcher *watcher = new HeaderWatcher(orientations, sections, roles, reply);
    connect(watcher, &HeaderWatcher::finished, this, &QAbstractItemModelReplicaImplementation::requestedHeaderData);
    m_requestedHeaderData.clear();
    m_pendingRequests.push_back(watcher);
}

static inline QVector<QPair<int, int> > listRanges(const QVector<int> &list)
{
    QVector<QPair<int, int> > result;
    if (!list.isEmpty()) {
        QPair<int, int> currentElem = qMakePair(list.first(), list.first());
        QVector<int>::const_iterator end = list.end();
        for (QVector<int>::const_iterator it = list.constBegin() + 1; it != end; ++it) {
            if (currentElem.first == *it +1)
                currentElem.first = *it;
            else if ( currentElem.second == *it -1)
                currentElem.second = *it;
            else if (currentElem.first <= *it && currentElem.second >= *it)
                continue;
            else {
                result.push_back(currentElem);
                currentElem.first = *it;
                currentElem.second = *it;
            }
        }
        result.push_back(currentElem);
    }
    return result;
}

void QAbstractItemModelReplicaImplementation::requestedHeaderData(QRemoteObjectPendingCallWatcher *qobject)
{
    HeaderWatcher *watcher = static_cast<HeaderWatcher *>(qobject);
    Q_ASSERT(watcher);

    QVariantList data = watcher->returnValue().value<QVariantList>();
    Q_ASSERT(watcher->orientations.size() == data.size());
    Q_ASSERT(watcher->sections.size() == data.size());
    Q_ASSERT(watcher->roles.size() == data.size());
    QVector<int> horizontalSections;
    QVector<int> verticalSections;

    for (int i = 0; i < data.size(); ++i) {
        if (watcher->orientations[i] == Qt::Horizontal)
            horizontalSections.append(watcher->sections[i]);
        else
            verticalSections.append(watcher->sections[i]);
        const int index = watcher->orientations[i] == Qt::Horizontal ? 0 : 1;
        const int role = watcher->roles[i];
        QHash<int, QVariant> &dat = m_headerData[index][watcher->sections[i]].data;
        dat[role] = data[i];
    }
    QVector<QPair<int, int> > horRanges = listRanges(horizontalSections);
    QVector<QPair<int, int> > verRanges = listRanges(verticalSections);

    for (int i = 0; i < horRanges.size(); ++i)
        emit q->headerDataChanged(Qt::Horizontal, horRanges[i].first, horRanges[i].second);
    for (int i = 0; i < verRanges.size(); ++i)
        emit q->headerDataChanged(Qt::Vertical, verRanges[i].first, verRanges[i].second);
    m_pendingRequests.removeAll(watcher);
    delete watcher;
}

QAbstractItemModelReplica::QAbstractItemModelReplica(QAbstractItemModelReplicaImplementation *rep, QtRemoteObjects::InitialAction action, const QVector<int> &rolesHint)
    : QAbstractItemModel()
    , d(rep)
{
    d->m_initialAction = action;
    d->m_initialFetchRolesHint = rolesHint;

    rep->setModel(this);
    connect(rep, &QAbstractItemModelReplicaImplementation::initialized, d.data(), &QAbstractItemModelReplicaImplementation::init);
}

QAbstractItemModelReplica::~QAbstractItemModelReplica()
{
}

static QVariant findData(const CachedRowEntry &row, const QModelIndex &index, int role, bool *cached = 0)
{
    if (index.column() < row.size()) {
        const CacheEntry &entry = row[index.column()];
        QHash<int, QVariant>::ConstIterator it = entry.data.constFind(role);
        if (it != entry.data.constEnd()) {
            if (cached)
                *cached = true;
            return it.value();
        }
    }
    if (cached)
        *cached = false;
    return QVariant();
}

QItemSelectionModel* QAbstractItemModelReplica::selectionModel() const
{
    return d->m_selectionModel.data();
}

bool QAbstractItemModelReplica::setData(const QModelIndex &index, const QVariant &value, int role)
{
    if (role == Qt::UserRole - 1) {
        auto parent = d->cacheData(index);
        if (!parent)
            return false;
        bool ok = true;
        auto row = value.toInt(&ok);
        if (ok)
            parent->ensureChildren(row, row);
        return ok;
    }
    if (!index.isValid())
        return false;
    if (index.row() < 0 || index.row() >= rowCount(index.parent()))
        return false;
    if (index.column() < 0 || index.column() >= columnCount(index.parent()))
        return false;

    const QVector<int > &availRoles = availableRoles();
    const QVector<int>::const_iterator res = std::find(availRoles.begin(), availRoles.end(), role);
    if (res == availRoles.end()) {
        qCWarning(QT_REMOTEOBJECT_MODELS) << "Tried to setData for index" << index << "on a not supported role" << role;
        return false;
    }
    // sendInvocationRequest to change server side data;
    d->replicaSetData(toModelIndexList(index, this), value, role);
    return true;
}

QVariant QAbstractItemModelReplica::data(const QModelIndex & index, int role) const
{

    if (!d->isInitialized()) {
        qCDebug(QT_REMOTEOBJECT_MODELS)<<"Data not initialized yet";
        return QVariant();
    }

    if (!index.isValid())
        return QVariant();

    if (!availableRoles().contains(role))
        return QVariant();

    auto item = d->cacheData(index);
    if (item) {
        bool cached = false;
        QVariant result = findData(item->cachedRowEntry, index, role, &cached);
        if (cached)
            return result;
    }

    auto parentItem = d->cacheData(index.parent());
    Q_ASSERT(parentItem);
    Q_ASSERT(index.row() < parentItem->rowCount);
    const int row = index.row();
    IndexList parentList = toModelIndexList(index.parent(), this);
    IndexList start = IndexList() << parentList << ModelIndex(row, 0);
    IndexList end = IndexList() << parentList << ModelIndex(row, std::max(0, parentItem->columnCount - 1));
    Q_ASSERT(toQModelIndex(start, this).isValid());

    RequestedData data;
    QVector<int> roles;
    roles << role;
    data.start = start;
    data.end = end;
    data.roles = roles;
    d->m_requestedData.push_back(data);
    qCDebug(QT_REMOTEOBJECT_MODELS) << "FETCH PENDING DATA" << start << end << roles;
    QMetaObject::invokeMethod(d.data(), "fetchPendingData", Qt::QueuedConnection);
    return QVariant{};
}
QModelIndex QAbstractItemModelReplica::parent(const QModelIndex &index) const
{
    if (!index.isValid() || !index.internalPointer())
        return QModelIndex();
    auto parent = static_cast<CacheData*>(index.internalPointer());
    Q_ASSERT(parent);
    if (parent == &d->m_rootItem)
        return QModelIndex();
    if (d->m_activeParents.find(parent) == d->m_activeParents.end() || d->m_activeParents.find(parent->parent) == d->m_activeParents.end())
        return QModelIndex();
    int row = parent->parent->children.find(parent);
    Q_ASSERT(row >= 0);
    return createIndex(row, 0, parent->parent);
}
QModelIndex QAbstractItemModelReplica::index(int row, int column, const QModelIndex &parent) const
{
    auto parentItem = d->cacheData(parent);
    if (!parentItem)
        return QModelIndex();

    Q_ASSERT_X((parent.isValid() && parentItem && parentItem != &d->m_rootItem) || (!parent.isValid() && parentItem == &d->m_rootItem), __FUNCTION__, qPrintable(QString(QLatin1String("isValid=%1 equals=%2")).arg(parent.isValid()).arg(parentItem == &d->m_rootItem)));

    // hmpf, following works around a Q_ASSERT-bug in QAbstractItemView::setModel which does just call
    // d->model->index(0,0) without checking the range before-hand what triggers our assert in case the
    // model is empty when view::setModel is called :-/ So, work around with the following;
    if (row < 0 || row >= parentItem->rowCount)
        return QModelIndex();

    if (column < 0 || column >= parentItem->columnCount)
        return QModelIndex();

    if (parentItem != &d->m_rootItem)
        parentItem->ensureChildren(row, row);
    return createIndex(row, column, reinterpret_cast<void*>(parentItem));
}
bool QAbstractItemModelReplica::hasChildren(const QModelIndex &parent) const
{
    auto parentItem = d->cacheData(parent);
    if (parent.isValid() && parent.column() != 0)
        return false;
    else
        return parentItem ? parentItem->hasChildren : false;
}
int QAbstractItemModelReplica::rowCount(const QModelIndex &parent) const
{
    auto parentItem = d->cacheData(parent);
    const bool canHaveChildren = parentItem && parentItem->hasChildren && !parentItem->rowCount && parent.column() == 0;
    if (canHaveChildren) {
        IndexList parentList = toModelIndexList(parent, this);
        QRemoteObjectPendingReply<QSize> reply = d->replicaSizeRequest(parentList);
        SizeWatcher *watcher = new SizeWatcher(parentList, reply);
        connect(watcher, &SizeWatcher::finished, d.data(), &QAbstractItemModelReplicaImplementation::handleSizeDone);
    } else if (parent.column() > 0) {
        return 0;
    }

    return parentItem ? parentItem->rowCount : 0;
}
int QAbstractItemModelReplica::columnCount(const QModelIndex &parent) const
{
    if (parent.isValid() && parent.column() > 0)
        return 0;
    auto parentItem = d->cacheData(parent);
    if (!parentItem)
        return 0;
    while (parentItem->columnCount < 0 && parentItem->parent)
        parentItem = parentItem->parent;
    return std::max(0, parentItem->columnCount);
}

QVariant QAbstractItemModelReplica::headerData(int section, Qt::Orientation orientation, int role) const
{
    const int index = orientation == Qt::Horizontal ? 0 : 1;
    const QVector<CacheEntry> elem = d->m_headerData[index];
    if (section >= elem.size())
        return QVariant();

    const QHash<int, QVariant> &dat = elem.at(section).data;
    QHash<int, QVariant>::ConstIterator it = dat.constFind(role);
    if (it != dat.constEnd())
        return it.value();

    RequestedHeaderData data;
    data.role = role;
    data.section = section;
    data.orientation = orientation;
    d->m_requestedHeaderData.push_back(data);
    QMetaObject::invokeMethod(d.data(), "fetchPendingHeaderData", Qt::QueuedConnection);
    return QVariant();
}

Qt::ItemFlags QAbstractItemModelReplica::flags(const QModelIndex &index) const
{
    CacheEntry *entry = d->cacheEntry(index);
    return entry ? entry->flags : Qt::NoItemFlags;
}

bool QAbstractItemModelReplica::isInitialized() const
{
    return d->isInitialized();
}

bool QAbstractItemModelReplica::hasData(const QModelIndex &index, int role) const
{
    if (!d->isInitialized() || !index.isValid())
        return false;
    auto item = d->cacheData(index);
    if (!item)
        return false;
    bool cached = false;
    const CachedRowEntry &entry = item->cachedRowEntry;
    QVariant result = findData(entry, index, role, &cached);
    Q_UNUSED(result);
    return cached;
}

size_t QAbstractItemModelReplica::rootCacheSize() const
{
    return d->m_rootItem.children.cacheSize;
}

void QAbstractItemModelReplica::setRootCacheSize(size_t rootCacheSize)
{
    d->m_rootItem.children.setCacheSize(rootCacheSize);
}

QVector<int> QAbstractItemModelReplica::availableRoles() const
{
    return d->availableRoles();
}

QHash<int, QByteArray> QAbstractItemModelReplica::roleNames() const
{
    return d->roleNames();
}

QT_END_NAMESPACE
