/****************************************************************************
**
** Copyright (C) 2021 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the test suite 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 <QTest>
#include <QtTest/private/qpropertytesthelper_p.h>
#include <qabstractproxymodel.h>
#include <QItemSelection>
#include <qstandarditemmodel.h>

class tst_QAbstractProxyModel : public QObject
{
    Q_OBJECT

private slots:
    void qabstractproxymodel();
    void data_data();
    void data();
    void flags_data();
    void flags();
    void headerData_data();
    void headerData();
    void itemData_data();
    void itemData();
    void mapFromSource_data();
    void mapFromSource();
    void mapSelectionFromSource_data();
    void mapSelectionFromSource();
    void mapSelectionToSource_data();
    void mapSelectionToSource();
    void mapToSource_data();
    void mapToSource();
    void revert();
    void setSourceModel();
    void submit_data();
    void submit();
    void testRoleNames();
    void testSwappingRowsProxy();
    void testDragAndDrop();
    void sourceModelBinding();
};

// Subclass that exposes the protected functions.
class SubQAbstractProxyModel : public QAbstractProxyModel
{
public:
    // QAbstractProxyModel::mapFromSource is a pure virtual function.
    QModelIndex mapFromSource(QModelIndex const& sourceIndex) const override
        { Q_UNUSED(sourceIndex); return QModelIndex(); }

    // QAbstractProxyModel::mapToSource is a pure virtual function.
    QModelIndex mapToSource(QModelIndex const& proxyIndex) const override
        { Q_UNUSED(proxyIndex); return QModelIndex(); }

    QModelIndex index(int, int, const QModelIndex&) const override
    {
        return QModelIndex();
    }

    QModelIndex parent(const QModelIndex&) const override
    {
        return QModelIndex();
    }

    int rowCount(const QModelIndex&) const override
    {
        return 0;
    }

    int columnCount(const QModelIndex&) const override
    {
        return 0;
    }
};

void tst_QAbstractProxyModel::qabstractproxymodel()
{
    SubQAbstractProxyModel model;
    model.data(QModelIndex());
    model.flags(QModelIndex());
    model.headerData(0, Qt::Vertical, 0);
    model.itemData(QModelIndex());
    model.mapFromSource(QModelIndex());
    model.mapSelectionFromSource(QItemSelection());
    model.mapSelectionToSource(QItemSelection());
    model.mapToSource(QModelIndex());
    model.revert();
    model.setSourceModel(0);
    QCOMPARE(model.sourceModel(), (QAbstractItemModel*)0);
    model.submit();
}

void tst_QAbstractProxyModel::data_data()
{
    QTest::addColumn<QModelIndex>("proxyIndex");
    QTest::addColumn<int>("role");
    QTest::addColumn<QVariant>("data");
    QTest::newRow("null") << QModelIndex() << 0 << QVariant();
}

// public QVariant data(QModelIndex const& proxyIndex, int role = Qt::DisplayRole) const
void tst_QAbstractProxyModel::data()
{
    QFETCH(QModelIndex, proxyIndex);
    QFETCH(int, role);
    QFETCH(QVariant, data);

    SubQAbstractProxyModel model;
    QCOMPARE(model.data(proxyIndex, role), data);
}

Q_DECLARE_METATYPE(Qt::ItemFlags)
void tst_QAbstractProxyModel::flags_data()
{
    QTest::addColumn<QModelIndex>("index");
    QTest::addColumn<Qt::ItemFlags>("flags");
    QTest::newRow("null") << QModelIndex() << Qt::ItemFlags{};
}

// public Qt::ItemFlags flags(QModelIndex const& index) const
void tst_QAbstractProxyModel::flags()
{
    QFETCH(QModelIndex, index);
    QFETCH(Qt::ItemFlags, flags);

    SubQAbstractProxyModel model;
    QCOMPARE(model.flags(index), flags);
}

Q_DECLARE_METATYPE(Qt::Orientation)
Q_DECLARE_METATYPE(Qt::ItemDataRole)
void tst_QAbstractProxyModel::headerData_data()
{
    QTest::addColumn<int>("section");
    QTest::addColumn<Qt::Orientation>("orientation");
    QTest::addColumn<Qt::ItemDataRole>("role");
    QTest::addColumn<QVariant>("headerData");
    QTest::newRow("null") << 0 << Qt::Vertical << Qt::UserRole << QVariant();
}

// public QVariant headerData(int section, Qt::Orientation orientation, int role) const
void tst_QAbstractProxyModel::headerData()
{
    QFETCH(int, section);
    QFETCH(Qt::Orientation, orientation);
    QFETCH(Qt::ItemDataRole, role);
    QFETCH(QVariant, headerData);

    SubQAbstractProxyModel model;
    QCOMPARE(model.headerData(section, orientation, role), headerData);
}

void tst_QAbstractProxyModel::itemData_data()
{
    QTest::addColumn<QModelIndex>("index");
    QTest::addColumn<int>("count");

    QTest::newRow("null") << QModelIndex() << 0;
}

// public QMap<int,QVariant> itemData(QModelIndex const& index) const
void tst_QAbstractProxyModel::itemData()
{
    QFETCH(QModelIndex, index);
    QFETCH(int, count);
    SubQAbstractProxyModel model;
    QCOMPARE(model.itemData(index).count(), count);
}

void tst_QAbstractProxyModel::mapFromSource_data()
{
    QTest::addColumn<QModelIndex>("sourceIndex");
    QTest::addColumn<QModelIndex>("mapFromSource");
    QTest::newRow("null") << QModelIndex() << QModelIndex();
}

// public QModelIndex mapFromSource(QModelIndex const& sourceIndex) const
void tst_QAbstractProxyModel::mapFromSource()
{
    QFETCH(QModelIndex, sourceIndex);
    QFETCH(QModelIndex, mapFromSource);

    SubQAbstractProxyModel model;
    QCOMPARE(model.mapFromSource(sourceIndex), mapFromSource);
}

void tst_QAbstractProxyModel::mapSelectionFromSource_data()
{
    QTest::addColumn<QItemSelection>("selection");
    QTest::addColumn<QItemSelection>("mapSelectionFromSource");
    QTest::newRow("null") << QItemSelection() << QItemSelection();
    QTest::newRow("empty") << QItemSelection(QModelIndex(), QModelIndex()) << QItemSelection(QModelIndex(), QModelIndex());
}

// public QItemSelection mapSelectionFromSource(QItemSelection const& selection) const
void tst_QAbstractProxyModel::mapSelectionFromSource()
{
    QFETCH(QItemSelection, selection);
    QFETCH(QItemSelection, mapSelectionFromSource);

    SubQAbstractProxyModel model;
    QCOMPARE(model.mapSelectionFromSource(selection), mapSelectionFromSource);
}

void tst_QAbstractProxyModel::mapSelectionToSource_data()
{
    QTest::addColumn<QItemSelection>("selection");
    QTest::addColumn<QItemSelection>("mapSelectionToSource");
    QTest::newRow("null") << QItemSelection() << QItemSelection();
    QTest::newRow("empty") << QItemSelection(QModelIndex(), QModelIndex()) << QItemSelection(QModelIndex(), QModelIndex());
}

// public QItemSelection mapSelectionToSource(QItemSelection const& selection) const
void tst_QAbstractProxyModel::mapSelectionToSource()
{
    QFETCH(QItemSelection, selection);
    QFETCH(QItemSelection, mapSelectionToSource);

    SubQAbstractProxyModel model;
    QCOMPARE(model.mapSelectionToSource(selection), mapSelectionToSource);
}

void tst_QAbstractProxyModel::mapToSource_data()
{
    QTest::addColumn<QModelIndex>("proxyIndex");
    QTest::addColumn<QModelIndex>("mapToSource");
    QTest::newRow("null") << QModelIndex() << QModelIndex();
}

// public QModelIndex mapToSource(QModelIndex const& proxyIndex) const
void tst_QAbstractProxyModel::mapToSource()
{
    QFETCH(QModelIndex, proxyIndex);
    QFETCH(QModelIndex, mapToSource);

    SubQAbstractProxyModel model;
    QCOMPARE(model.mapToSource(proxyIndex), mapToSource);
}

// public void revert()
void tst_QAbstractProxyModel::revert()
{
    SubQAbstractProxyModel model;
    model.revert();
}

// public void setSourceModel(QAbstractItemModel* sourceModel)
void tst_QAbstractProxyModel::setSourceModel()
{
    SubQAbstractProxyModel model;

    QCOMPARE(model.property("sourceModel"), QVariant::fromValue<QAbstractItemModel*>(0));
    QStandardItemModel *sourceModel = new QStandardItemModel(&model);
    model.setSourceModel(sourceModel);
    QCOMPARE(model.sourceModel(), static_cast<QAbstractItemModel*>(sourceModel));

    QCOMPARE(model.property("sourceModel").value<QObject*>(), static_cast<QObject*>(sourceModel));
    QCOMPARE(model.property("sourceModel").value<QAbstractItemModel*>(), sourceModel);

    QStandardItemModel *sourceModel2 = new QStandardItemModel(&model);
    model.setSourceModel(sourceModel2);
    QCOMPARE(model.sourceModel(), static_cast<QAbstractItemModel*>(sourceModel2));

    QCOMPARE(model.property("sourceModel").value<QObject*>(), static_cast<QObject*>(sourceModel2));
    QCOMPARE(model.property("sourceModel").value<QAbstractItemModel*>(), sourceModel2);

    delete sourceModel2;
    QCOMPARE(model.sourceModel(), static_cast<QAbstractItemModel*>(0));
}

void tst_QAbstractProxyModel::submit_data()
{
    QTest::addColumn<bool>("submit");
    QTest::newRow("null") << true;
}

// public bool submit()
void tst_QAbstractProxyModel::submit()
{
    QFETCH(bool, submit);

    SubQAbstractProxyModel model;
    QCOMPARE(model.submit(), submit);
}

class StandardItemModelWithCustomRoleNames : public QStandardItemModel
{
public:
    enum CustomRole {
        CustomRole1 = Qt::UserRole,
        CustomRole2
    };

    QHash<int, QByteArray> roleNames() const override
    {
        auto result = QStandardItemModel::roleNames();
        result.insert(CustomRole1, QByteArrayLiteral("custom1"));
        result.insert(CustomRole2, QByteArrayLiteral("custom2"));
        return result;
    }
};

class AnotherStandardItemModelWithCustomRoleNames : public QStandardItemModel
{
public:
    enum CustomRole {
        AnotherCustomRole1 = Qt::UserRole + 10,  // Different to StandardItemModelWithCustomRoleNames::CustomRole1
        AnotherCustomRole2
    };

    QHash<int, QByteArray> roleNames() const override
    {
        return {{AnotherCustomRole1, QByteArrayLiteral("another_custom1")},
                {AnotherCustomRole2, QByteArrayLiteral("another_custom2")}};
    }
};

/**
    Verifies that @p subSet is a subset of @p superSet. That is, all keys in @p subSet exist in @p superSet and have the same values.
*/
static void verifySubSetOf(const QHash<int, QByteArray> &superSet, const QHash<int, QByteArray> &subSet)
{
    QHash<int, QByteArray>::const_iterator it = subSet.constBegin();
    const QHash<int, QByteArray>::const_iterator end = subSet.constEnd();
    for ( ; it != end; ++it ) {
        QVERIFY(superSet.contains(it.key()));
        QCOMPARE(it.value(), superSet.value(it.key()));
    }
}

void tst_QAbstractProxyModel::testRoleNames()
{
    QStandardItemModel defaultModel;
    StandardItemModelWithCustomRoleNames model;
    QHash<int, QByteArray> rootModelRoleNames = model.roleNames();
    QHash<int, QByteArray> defaultModelRoleNames = defaultModel.roleNames();

    verifySubSetOf( rootModelRoleNames, defaultModelRoleNames);
    QVERIFY( rootModelRoleNames.size() == defaultModelRoleNames.size() + 2 );
    QVERIFY( rootModelRoleNames.contains(StandardItemModelWithCustomRoleNames::CustomRole1));
    QVERIFY( rootModelRoleNames.contains(StandardItemModelWithCustomRoleNames::CustomRole2));
    QVERIFY( rootModelRoleNames.value(StandardItemModelWithCustomRoleNames::CustomRole1) == "custom1" );
    QVERIFY( rootModelRoleNames.value(StandardItemModelWithCustomRoleNames::CustomRole2) == "custom2" );

    SubQAbstractProxyModel proxy1;
    proxy1.setSourceModel(&model);
    QHash<int, QByteArray> proxy1RoleNames = proxy1.roleNames();
    verifySubSetOf( proxy1RoleNames, defaultModelRoleNames );
    QVERIFY( proxy1RoleNames.size() == defaultModelRoleNames.size() + 2 );
    QVERIFY( proxy1RoleNames.contains(StandardItemModelWithCustomRoleNames::CustomRole1));
    QVERIFY( proxy1RoleNames.contains(StandardItemModelWithCustomRoleNames::CustomRole2));
    QVERIFY( proxy1RoleNames.value(StandardItemModelWithCustomRoleNames::CustomRole1) == "custom1" );
    QVERIFY( proxy1RoleNames.value(StandardItemModelWithCustomRoleNames::CustomRole2) == "custom2" );

    SubQAbstractProxyModel proxy2;
    proxy2.setSourceModel(&proxy1);
    QHash<int, QByteArray> proxy2RoleNames = proxy2.roleNames();
    verifySubSetOf( proxy2RoleNames, defaultModelRoleNames );
    QVERIFY( proxy2RoleNames.size() == defaultModelRoleNames.size() + 2 );
    QVERIFY( proxy2RoleNames.contains(StandardItemModelWithCustomRoleNames::CustomRole1));
    QVERIFY( proxy2RoleNames.contains(StandardItemModelWithCustomRoleNames::CustomRole2));
    QVERIFY( proxy2RoleNames.value(StandardItemModelWithCustomRoleNames::CustomRole1) == "custom1" );
    QVERIFY( proxy2RoleNames.value(StandardItemModelWithCustomRoleNames::CustomRole2) == "custom2" );
}

// This class only supports very simple table models
class SwappingProxy : public QAbstractProxyModel
{
    static int swapRow(const int row)
    {
        if (row == 2) {
            return 3;
        } else if (row == 3) {
            return 2;
        } else {
            return row;
        }
    }
public:
    virtual QModelIndex index(int row, int column, const QModelIndex &parentIdx) const override
    {
        if (!sourceModel())
            return QModelIndex();
        if (row < 0 || column < 0)
            return QModelIndex();
        if (row >= sourceModel()->rowCount())
            return QModelIndex();
        if (column >= sourceModel()->columnCount())
            return QModelIndex();
        return createIndex(row, column, parentIdx.internalPointer());
    }

    virtual QModelIndex parent(const QModelIndex &parentIdx) const override
    {
        // well, we're a 2D model
        Q_UNUSED(parentIdx);
        return QModelIndex();
    }

    virtual int rowCount(const QModelIndex &parentIdx) const override
    {
        if (parentIdx.isValid() || !sourceModel())
            return 0;
        return sourceModel()->rowCount();
    }

    virtual int columnCount(const QModelIndex &parentIdx) const override
    {
        if (parentIdx.isValid() || !sourceModel())
            return 0;
        return sourceModel()->rowCount();
    }

    virtual QModelIndex mapToSource(const QModelIndex &proxyIndex) const override
    {
        if (!proxyIndex.isValid())
            return QModelIndex();
        if (!sourceModel())
            return QModelIndex();
        Q_ASSERT(!proxyIndex.parent().isValid());
        return sourceModel()->index(swapRow(proxyIndex.row()), proxyIndex.column(), QModelIndex());
    }

    virtual QModelIndex mapFromSource(const QModelIndex &sourceIndex) const override
    {
        if (!sourceIndex.isValid())
            return QModelIndex();
        if (!sourceModel())
            return QModelIndex();
        Q_ASSERT(!sourceIndex.parent().isValid());
        return index(swapRow(sourceIndex.row()), sourceIndex.column(), QModelIndex());
    }
};

void tst_QAbstractProxyModel::testSwappingRowsProxy()
{
    QStandardItemModel defaultModel;
    defaultModel.setRowCount(4);
    defaultModel.setColumnCount(2);
    for (int row = 0; row < defaultModel.rowCount(); ++row) {
        defaultModel.setItem(row, 0, new QStandardItem(QString::number(row) + QLatin1Char('A')));
        defaultModel.setItem(row, 1, new QStandardItem(QString::number(row) + QLatin1Char('B')));
    }
    SwappingProxy proxy;
    proxy.setSourceModel(&defaultModel);
    QCOMPARE(proxy.data(proxy.index(0, 0, QModelIndex())), QVariant("0A"));
    QCOMPARE(proxy.data(proxy.index(0, 1, QModelIndex())), QVariant("0B"));
    QCOMPARE(proxy.data(proxy.index(1, 0, QModelIndex())), QVariant("1A"));
    QCOMPARE(proxy.data(proxy.index(1, 1, QModelIndex())), QVariant("1B"));
    QCOMPARE(proxy.data(proxy.index(2, 0, QModelIndex())), QVariant("3A"));
    QCOMPARE(proxy.data(proxy.index(2, 1, QModelIndex())), QVariant("3B"));
    QCOMPARE(proxy.data(proxy.index(3, 0, QModelIndex())), QVariant("2A"));
    QCOMPARE(proxy.data(proxy.index(3, 1, QModelIndex())), QVariant("2B"));

    for (int row = 0; row < defaultModel.rowCount(); ++row) {
        QModelIndex left = proxy.index(row, 0, QModelIndex());
        QModelIndex right = proxy.index(row, 1, QModelIndex());
        QCOMPARE(left.siblingAtColumn(1), right);
        QCOMPARE(right.siblingAtColumn(0), left);
    }
}

class StandardItemModelWithCustomDragAndDrop : public QStandardItemModel
{
public:
    QStringList mimeTypes() const override { return QStringList() << QStringLiteral("foo/mimetype"); }
    Qt::DropActions supportedDragActions() const override { return Qt::CopyAction | Qt::LinkAction; }
    Qt::DropActions supportedDropActions() const override { return Qt::MoveAction; }
};

void tst_QAbstractProxyModel::testDragAndDrop()
{
    StandardItemModelWithCustomDragAndDrop sourceModel;
    SubQAbstractProxyModel proxy;
    proxy.setSourceModel(&sourceModel);
    QCOMPARE(proxy.mimeTypes(), sourceModel.mimeTypes());
    QCOMPARE(proxy.supportedDragActions(), sourceModel.supportedDragActions());
    QCOMPARE(proxy.supportedDropActions(), sourceModel.supportedDropActions());
}

void tst_QAbstractProxyModel::sourceModelBinding()
{
    SubQAbstractProxyModel proxy;
    QStandardItemModel model1;
    QStandardItemModel model2;
    QTestPrivate::testReadWritePropertyBasics<QAbstractProxyModel, QAbstractItemModel *>(
            proxy, &model1, &model2, "sourceModel");
    if (QTest::currentTestFailed()) {
        qDebug("Failed model - model test");
        return;
    }

    proxy.setSourceModel(&model2);
    QTestPrivate::testReadWritePropertyBasics<QAbstractProxyModel, QAbstractItemModel *>(
            proxy, &model1, nullptr, "sourceModel");
    if (QTest::currentTestFailed()) {
        qDebug("Failed model - nullptr test");
        return;
    }

    proxy.setSourceModel(&model1);
    QTestPrivate::testReadWritePropertyBasics<QAbstractProxyModel, QAbstractItemModel *>(
            proxy, nullptr, &model2, "sourceModel");
    if (QTest::currentTestFailed()) {
        qDebug("Failed nullptr - model test");
        return;
    }
}

QTEST_MAIN(tst_QAbstractProxyModel)
#include "tst_qabstractproxymodel.moc"

