// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0

#include <utils/runextensions.h>

#include <QtTest>

#if !defined(Q_CC_MSVC) || _MSC_VER >= 1900 // MSVC2015
#define SUPPORTS_MOVE
#endif

class tst_RunExtensions : public QObject
{
    Q_OBJECT

private slots:
    void runAsync();
    void runInThreadPool();
#ifdef SUPPORTS_MOVE
    void moveOnlyType();
#endif
    void threadPriority();
    void threadSize();
    void threadSizeAndPriority();
    void runAsyncNoFutureInterface();
    void crefFunction();
    void onResultReady();
};

void report3(QFutureInterface<int> &fi)
{
    fi.reportResults({0, 2, 1});
}

void reportN(QFutureInterface<double> &fi, int n)
{
    fi.reportResults(QVector<double>(n, 0));
}

void reportString1(QFutureInterface<QString> &fi, const QString &s)
{
    fi.reportResult(s);
}

void reportString2(QFutureInterface<QString> &fi, QString s)
{
    fi.reportResult(s);
}

class Callable {
public:
    void operator()(QFutureInterface<double> &fi, int n) const
    {
        fi.reportResults(QVector<double>(n, 0));
    }
};

class MyObject {
public:
    static void staticMember0(QFutureInterface<double> &fi)
    {
        fi.reportResults({0, 2, 1});
    }

    static void staticMember1(QFutureInterface<double> &fi, int n)
    {
        fi.reportResults(QVector<double>(n, 0));
    }

    void member0(QFutureInterface<double> &fi) const
    {
        fi.reportResults({0, 2, 1});
    }

    void member1(QFutureInterface<double> &fi, int n) const
    {
        fi.reportResults(QVector<double>(n, 0));
    }

    void memberString1(QFutureInterface<QString> &fi, const QString &s) const
    {
        fi.reportResult(s);
    }

    void memberString2(QFutureInterface<QString> &fi, QString s) const
    {
        fi.reportResult(s);
    }

    void nonConstMember(QFutureInterface<double> &fi)
    {
        fi.reportResults({0, 2, 1});
    }
};

void voidFunction(bool *value) // can be useful to get QFuture for watching when it is finished
{
    *value = true;
}

int one()
{
    return 1;
}

int identity(int input)
{
    return input;
}

QString stringIdentity1(const QString &s)
{
    return s;
}

QString stringIdentity2(QString s)
{
    return s;
}

class CallableWithoutQFutureInterface {
public:
    void operator()(bool *value) const
    {
        *value = true;
    }
};

class MyObjectWithoutQFutureInterface {
public:
    static void staticMember0(bool *value)
    {
        *value = true;
    }

    static double staticMember1(int n)
    {
        return n;
    }

    void member0(bool *value) const
    {
        *value = true;
    }

    double member1(int n) const
    {
        return n;
    }

    QString memberString1(const QString &s) const
    {
        return s;
    }

    QString memberString2(QString s) const
    {
        return s;
    }

    double nonConstMember(int n)
    {
        return n;
    }
};

void tst_RunExtensions::runAsync()
{
    // free function pointer
    QCOMPARE(Utils::runAsync(&report3).results(),
             QList<int>({0, 2, 1}));
    QCOMPARE(Utils::runAsync(report3).results(),
             QList<int>({0, 2, 1}));

    QCOMPARE(Utils::runAsync(reportN, 4).results(),
             QList<double>({0, 0, 0, 0}));
    QCOMPARE(Utils::runAsync(reportN, 2).results(),
             QList<double>({0, 0}));

    QString s = QLatin1String("string");
    const QString &crs = QLatin1String("cr string");
    const QString cs = QLatin1String("c string");

    QCOMPARE(Utils::runAsync(reportString1, s).results(),
             QList<QString>({s}));
    QCOMPARE(Utils::runAsync(reportString1, crs).results(),
             QList<QString>({crs}));
    QCOMPARE(Utils::runAsync(reportString1, cs).results(),
             QList<QString>({cs}));
    QCOMPARE(Utils::runAsync(reportString1, QString(QLatin1String("rvalue"))).results(),
             QList<QString>({QString(QLatin1String("rvalue"))}));

    QCOMPARE(Utils::runAsync(reportString2, s).results(),
             QList<QString>({s}));
    QCOMPARE(Utils::runAsync(reportString2, crs).results(),
             QList<QString>({crs}));
    QCOMPARE(Utils::runAsync(reportString2, cs).results(),
             QList<QString>({cs}));
    QCOMPARE(Utils::runAsync(reportString2, QString(QLatin1String("rvalue"))).results(),
             QList<QString>({QString(QLatin1String("rvalue"))}));

    // lambda
    QCOMPARE(Utils::runAsync([](QFutureInterface<double> &fi, int n) {
                 fi.reportResults(QVector<double>(n, 0));
             }, 3).results(),
             QList<double>({0, 0, 0}));

    // std::function
    const std::function<void(QFutureInterface<double>&,int)> fun = [](QFutureInterface<double> &fi, int n) {
        fi.reportResults(QVector<double>(n, 0));
    };
    QCOMPARE(Utils::runAsync(fun, 2).results(),
             QList<double>({0, 0}));

    // operator()
    QCOMPARE(Utils::runAsync(Callable(), 3).results(),
             QList<double>({0, 0, 0}));
    const Callable c{};
    QCOMPARE(Utils::runAsync(c, 2).results(),
             QList<double>({0, 0}));

    // static member functions
    QCOMPARE(Utils::runAsync(&MyObject::staticMember0).results(),
             QList<double>({0, 2, 1}));
    QCOMPARE(Utils::runAsync(&MyObject::staticMember1, 2).results(),
             QList<double>({0, 0}));

    // member functions
    const MyObject obj{};
    QCOMPARE(Utils::runAsync(&MyObject::member0, &obj).results(),
             QList<double>({0, 2, 1}));
    QCOMPARE(Utils::runAsync(&MyObject::member1, &obj, 4).results(),
             QList<double>({0, 0, 0, 0}));
    QCOMPARE(Utils::runAsync(&MyObject::memberString1, &obj, s).results(),
             QList<QString>({s}));
    QCOMPARE(Utils::runAsync(&MyObject::memberString1, &obj, crs).results(),
             QList<QString>({crs}));
    QCOMPARE(Utils::runAsync(&MyObject::memberString1, &obj, cs).results(),
             QList<QString>({cs}));
    QCOMPARE(Utils::runAsync(&MyObject::memberString1, &obj, QString(QLatin1String("rvalue"))).results(),
             QList<QString>({QString(QLatin1String("rvalue"))}));
    QCOMPARE(Utils::runAsync(&MyObject::memberString2, &obj, s).results(),
             QList<QString>({s}));
    QCOMPARE(Utils::runAsync(&MyObject::memberString2, &obj, crs).results(),
             QList<QString>({crs}));
    QCOMPARE(Utils::runAsync(&MyObject::memberString2, &obj, cs).results(),
             QList<QString>({cs}));
    QCOMPARE(Utils::runAsync(&MyObject::memberString2, &obj, QString(QLatin1String("rvalue"))).results(),
             QList<QString>({QString(QLatin1String("rvalue"))}));
    MyObject nonConstObj{};
    QCOMPARE(Utils::runAsync(&MyObject::nonConstMember, &nonConstObj).results(),
             QList<double>({0, 2, 1}));
}

void tst_RunExtensions::runInThreadPool()
{
    QScopedPointer<QThreadPool> pool(new QThreadPool);
    // free function pointer
    QCOMPARE(Utils::runAsync(pool.data(), &report3).results(),
             QList<int>({0, 2, 1}));
    QCOMPARE(Utils::runAsync(pool.data(), report3).results(),
             QList<int>({0, 2, 1}));

    QCOMPARE(Utils::runAsync(pool.data(), reportN, 4).results(),
             QList<double>({0, 0, 0, 0}));
    QCOMPARE(Utils::runAsync(pool.data(), reportN, 2).results(),
             QList<double>({0, 0}));

    QString s = QLatin1String("string");
    const QString &crs = QLatin1String("cr string");
    const QString cs = QLatin1String("c string");

    QCOMPARE(Utils::runAsync(pool.data(), reportString1, s).results(),
             QList<QString>({s}));
    QCOMPARE(Utils::runAsync(pool.data(), reportString1, crs).results(),
             QList<QString>({crs}));
    QCOMPARE(Utils::runAsync(pool.data(), reportString1, cs).results(),
             QList<QString>({cs}));
    QCOMPARE(Utils::runAsync(pool.data(), reportString1, QString(QLatin1String("rvalue"))).results(),
             QList<QString>({QString(QLatin1String("rvalue"))}));

    QCOMPARE(Utils::runAsync(pool.data(), reportString2, s).results(),
             QList<QString>({s}));
    QCOMPARE(Utils::runAsync(pool.data(), reportString2, crs).results(),
             QList<QString>({crs}));
    QCOMPARE(Utils::runAsync(pool.data(), reportString2, cs).results(),
             QList<QString>({cs}));
    QCOMPARE(Utils::runAsync(pool.data(), reportString2, QString(QLatin1String("rvalue"))).results(),
             QList<QString>({QString(QLatin1String("rvalue"))}));

    // lambda
    QCOMPARE(Utils::runAsync(pool.data(), [](QFutureInterface<double> &fi, int n) {
                 fi.reportResults(QVector<double>(n, 0));
             }, 3).results(),
             QList<double>({0, 0, 0}));

    // std::function
    const std::function<void(QFutureInterface<double>&,int)> fun = [](QFutureInterface<double> &fi, int n) {
        fi.reportResults(QVector<double>(n, 0));
    };
    QCOMPARE(Utils::runAsync(pool.data(), fun, 2).results(),
             QList<double>({0, 0}));

    // operator()
    QCOMPARE(Utils::runAsync(pool.data(), Callable(), 3).results(),
             QList<double>({0, 0, 0}));
    const Callable c{};
    QCOMPARE(Utils::runAsync(pool.data(), c, 2).results(),
             QList<double>({0, 0}));

    // static member functions
    QCOMPARE(Utils::runAsync(pool.data(), &MyObject::staticMember0).results(),
             QList<double>({0, 2, 1}));
    QCOMPARE(Utils::runAsync(pool.data(), &MyObject::staticMember1, 2).results(),
             QList<double>({0, 0}));

    // member functions
    const MyObject obj{};
    QCOMPARE(Utils::runAsync(pool.data(), &MyObject::member0, &obj).results(),
             QList<double>({0, 2, 1}));
    QCOMPARE(Utils::runAsync(pool.data(), &MyObject::member1, &obj, 4).results(),
             QList<double>({0, 0, 0, 0}));
    QCOMPARE(Utils::runAsync(pool.data(), &MyObject::memberString1, &obj, s).results(),
             QList<QString>({s}));
    QCOMPARE(Utils::runAsync(pool.data(), &MyObject::memberString1, &obj, crs).results(),
             QList<QString>({crs}));
    QCOMPARE(Utils::runAsync(pool.data(), &MyObject::memberString1, &obj, cs).results(),
             QList<QString>({cs}));
    QCOMPARE(Utils::runAsync(pool.data(), &MyObject::memberString1, &obj, QString(QLatin1String("rvalue"))).results(),
             QList<QString>({QString(QLatin1String("rvalue"))}));
    QCOMPARE(Utils::runAsync(pool.data(), &MyObject::memberString2, &obj, s).results(),
             QList<QString>({s}));
    QCOMPARE(Utils::runAsync(pool.data(), &MyObject::memberString2, &obj, crs).results(),
             QList<QString>({crs}));
    QCOMPARE(Utils::runAsync(pool.data(), &MyObject::memberString2, &obj, cs).results(),
             QList<QString>({cs}));
    QCOMPARE(Utils::runAsync(pool.data(), &MyObject::memberString2, &obj, QString(QLatin1String("rvalue"))).results(),
             QList<QString>({QString(QLatin1String("rvalue"))}));
}

#ifdef SUPPORTS_MOVE

class MoveOnlyType
{
public:
    MoveOnlyType() = default;
    MoveOnlyType(const MoveOnlyType &) = delete;
    MoveOnlyType(MoveOnlyType &&) = default;
    MoveOnlyType &operator=(const MoveOnlyType &) = delete;
    MoveOnlyType &operator=(MoveOnlyType &&) = default;
};

class MoveOnlyCallable : public MoveOnlyType
{
public:
    void operator()(QFutureInterface<int> &fi, const MoveOnlyType &)
    {
        fi.reportResult(1);
    }
};

void tst_RunExtensions::moveOnlyType()
{
    QCOMPARE(Utils::runAsync(MoveOnlyCallable(), MoveOnlyType()).results(),
             QList<int>({1}));
}

#endif

void tst_RunExtensions::threadPriority()
{
    QScopedPointer<QThreadPool> pool(new QThreadPool);
    // with pool
    QCOMPARE(Utils::runAsync(pool.data(), QThread::LowestPriority, &report3).results(),
             QList<int>({0, 2, 1}));

    // without pool
    QCOMPARE(Utils::runAsync(QThread::LowestPriority, report3).results(),
             QList<int>({0, 2, 1}));
}

void tst_RunExtensions::threadSize()
{
    QCOMPARE(Utils::runAsync(Utils::StackSizeInBytes(1024 * 1024), report3).results(),
             QList<int>({0, 2, 1}));
}

void tst_RunExtensions::threadSizeAndPriority()
{
    QCOMPARE(Utils::runAsync(Utils::StackSizeInBytes(1024 * 1024), QThread::LowestPriority, report3)
                 .results(),
             QList<int>({0, 2, 1}));
}

void tst_RunExtensions::runAsyncNoFutureInterface()
{
    // free function pointer
    bool value = false;
    Utils::runAsync(voidFunction, &value).waitForFinished();
    QCOMPARE(value, true);

    QCOMPARE(Utils::runAsync(one).results(),
             QList<int>({1}));
    QCOMPARE(Utils::runAsync(identity, 5).results(),
             QList<int>({5}));

    QString s = QLatin1String("string");
    const QString &crs = QLatin1String("cr string");
    const QString cs = QLatin1String("c string");

    QCOMPARE(Utils::runAsync(stringIdentity1, s).results(),
             QList<QString>({s}));
    QCOMPARE(Utils::runAsync(stringIdentity1, crs).results(),
             QList<QString>({crs}));
    QCOMPARE(Utils::runAsync(stringIdentity1, cs).results(),
             QList<QString>({cs}));
    QCOMPARE(Utils::runAsync(stringIdentity1, QString(QLatin1String("rvalue"))).results(),
             QList<QString>({QString(QLatin1String("rvalue"))}));
    QCOMPARE(Utils::runAsync(stringIdentity2, s).results(),
             QList<QString>({s}));
    QCOMPARE(Utils::runAsync(stringIdentity2, crs).results(),
             QList<QString>({crs}));
    QCOMPARE(Utils::runAsync(stringIdentity2, cs).results(),
             QList<QString>({cs}));
    QCOMPARE(Utils::runAsync(stringIdentity2, QString(QLatin1String("rvalue"))).results(),
             QList<QString>({QString(QLatin1String("rvalue"))}));

    // lambda
    QCOMPARE(Utils::runAsync([](int n) -> double {
                 return n + 1;
             }, 3).results(),
             QList<double>({4}));

    // std::function
    const std::function<double(int)> fun = [](int n) {
        return n + 1;
    };
    QCOMPARE(Utils::runAsync(fun, 2).results(),
             QList<double>({3}));

    // operator()
    value = false;
    Utils::runAsync(CallableWithoutQFutureInterface(), &value).waitForFinished();
    QCOMPARE(value, true);
    value = false;
    const CallableWithoutQFutureInterface c{};
    Utils::runAsync(c, &value).waitForFinished();
    QCOMPARE(value, true);

    // static member functions
    value = false;
    Utils::runAsync(&MyObjectWithoutQFutureInterface::staticMember0, &value).waitForFinished();
    QCOMPARE(value, true);
    QCOMPARE(Utils::runAsync(&MyObjectWithoutQFutureInterface::staticMember1, 2).results(),
             QList<double>({2}));

    // member functions
    const MyObjectWithoutQFutureInterface obj{};
    value = false;
    Utils::runAsync(&MyObjectWithoutQFutureInterface::member0, &obj, &value).waitForFinished();
    QCOMPARE(value, true);
    QCOMPARE(Utils::runAsync(&MyObjectWithoutQFutureInterface::member1, &obj, 4).results(),
             QList<double>({4}));
    QCOMPARE(Utils::runAsync(&MyObjectWithoutQFutureInterface::memberString1, &obj, s).results(),
             QList<QString>({s}));
    QCOMPARE(Utils::runAsync(&MyObjectWithoutQFutureInterface::memberString1, &obj, crs).results(),
             QList<QString>({crs}));
    QCOMPARE(Utils::runAsync(&MyObjectWithoutQFutureInterface::memberString1, &obj, cs).results(),
             QList<QString>({cs}));
    QCOMPARE(Utils::runAsync(&MyObjectWithoutQFutureInterface::memberString1, &obj, QString(QLatin1String("rvalue"))).results(),
             QList<QString>({QString(QLatin1String("rvalue"))}));
    QCOMPARE(Utils::runAsync(&MyObjectWithoutQFutureInterface::memberString2, &obj, s).results(),
             QList<QString>({s}));
    QCOMPARE(Utils::runAsync(&MyObjectWithoutQFutureInterface::memberString2, &obj, crs).results(),
             QList<QString>({crs}));
    QCOMPARE(Utils::runAsync(&MyObjectWithoutQFutureInterface::memberString2, &obj, cs).results(),
             QList<QString>({cs}));
    QCOMPARE(Utils::runAsync(&MyObjectWithoutQFutureInterface::memberString2, &obj, QString(QLatin1String("rvalue"))).results(),
             QList<QString>({QString(QLatin1String("rvalue"))}));
    MyObjectWithoutQFutureInterface nonConstObj{};
    QCOMPARE(Utils::runAsync(&MyObjectWithoutQFutureInterface::nonConstMember, &nonConstObj, 4).results(),
             QList<double>({4}));
}

void tst_RunExtensions::crefFunction()
{
    // free function pointer with future interface
    auto fun = &report3;
    QCOMPARE(Utils::runAsync(std::cref(fun)).results(),
             QList<int>({0, 2, 1}));

    // lambda with future interface
    auto lambda = [](QFutureInterface<double> &fi, int n) {
        fi.reportResults(QVector<double>(n, 0));
    };
    QCOMPARE(Utils::runAsync(std::cref(lambda), 3).results(),
             QList<double>({0, 0, 0}));

    // std::function with future interface
    const std::function<void(QFutureInterface<double>&,int)> funObj = [](QFutureInterface<double> &fi, int n) {
        fi.reportResults(QVector<double>(n, 0));
    };
    QCOMPARE(Utils::runAsync(std::cref(funObj), 2).results(),
             QList<double>({0, 0}));

    // callable with future interface
    const Callable c{};
    QCOMPARE(Utils::runAsync(std::cref(c), 2).results(),
             QList<double>({0, 0}));

    // member functions with future interface
    auto member = &MyObject::member0;
    const MyObject obj{};
    QCOMPARE(Utils::runAsync(std::cref(member), &obj).results(),
             QList<double>({0, 2, 1}));

    // free function pointer without future interface
    bool value = false;
    auto voidFun = &voidFunction;
    Utils::runAsync(std::cref(voidFun), &value).waitForFinished();
    QCOMPARE(value, true);

    auto oneFun = &one;
    QCOMPARE(Utils::runAsync(std::cref(oneFun)).results(),
             QList<int>({1}));

    // lambda without future interface
    auto lambda2 = [](int n) -> double {
        return n + 1;
    };
    QCOMPARE(Utils::runAsync(std::cref(lambda2), 3).results(),
             QList<double>({4}));

    // std::function
    const std::function<double(int)> funObj2 = [](int n) {
        return n + 1;
    };
    QCOMPARE(Utils::runAsync(std::cref(funObj2), 2).results(),
             QList<double>({3}));

    // callable without future interface
    const CallableWithoutQFutureInterface c2{};
    Utils::runAsync(std::cref(c2), &value).waitForFinished();
    QCOMPARE(value, true);

    // member functions without future interface
    const MyObjectWithoutQFutureInterface obj2{};
    auto member2 = &MyObjectWithoutQFutureInterface::member0;
    value = false;
    Utils::runAsync(std::cref(member2), &obj2, &value).waitForFinished();
    QCOMPARE(value, true);
}

class ObjWithProperty : public QObject
{
    Q_OBJECT

public slots:
    void setValue(const QString &s)
    {
        value = s;
    }

public:
    QString value;
};

void tst_RunExtensions::onResultReady()
{
    { // lambda
        QFuture<QString> f = Utils::runAsync([](QFutureInterface<QString> &fi) {
                fi.reportResult("Hi");
                fi.reportResult("there");
        });
        int count = 0;
        QString res;
        Utils::onResultReady(f, [&count, &res](const QString &s) {
            ++count;
            res = s;
        });
        f.waitForFinished();
        QCoreApplication::processEvents();
        QCOMPARE(count, 2);
        QCOMPARE(res, QString("there"));
    }
    { // lambda with guard
        QFuture<QString> f = Utils::runAsync([](QFutureInterface<QString> &fi) {
                fi.reportResult("Hi");
                fi.reportResult("there");
        });
        int count = 0;
        ObjWithProperty obj;
        Utils::onResultReady(f, &obj, [&count, &obj](const QString &s) {
            ++count;
            obj.setValue(s);
        });
        f.waitForFinished();
        QCoreApplication::processEvents();
        QCOMPARE(count, 2);
        QCOMPARE(obj.value, QString("there"));
    }
    { // member
        QFuture<QString> f = Utils::runAsync([]() { return QString("Hi"); });
        ObjWithProperty obj;
        Utils::onResultReady(f, &obj, &ObjWithProperty::setValue);
        f.waitForFinished();
        QCoreApplication::processEvents();
        QCOMPARE(obj.value, QString("Hi"));
    }
}

QTEST_GUILESS_MAIN(tst_RunExtensions)

#include "tst_runextensions.moc"
