/*
 * Copyright (c) Simon Brunel, https://github.com/simonbrunel
 *
 * This source code is licensed under the MIT license found in
 * the LICENSE file in the root directory of this source tree.
 */

#include "qpromise.h"
#include "qpromisehelpers.h"

#include <QtCore/QCoreApplication>
#include <QtCore/QSharedPointer>
#include <QtCore/QTimer>

namespace QtPromise {

template<typename T>
template<typename F, typename std::enable_if<QtPromisePrivate::ArgsOf<F>::count == 1, int>::type>
inline QPromiseBase<T>::QPromiseBase(F callback) : m_d{new QtPromisePrivate::PromiseData<T>{}}
{
    QtPromisePrivate::PromiseResolver<T> resolver{*this};

    try {
        callback(QPromiseResolve<T>(resolver));
    } catch (...) {
        resolver.reject(std::current_exception());
    }
}

template<typename T>
template<typename F, typename std::enable_if<QtPromisePrivate::ArgsOf<F>::count != 1, int>::type>
inline QPromiseBase<T>::QPromiseBase(F callback) : m_d{new QtPromisePrivate::PromiseData<T>{}}
{
    // To prevent infinite recursion at runtime when resolving the QPromise template
    // constructor, we don't explicitly check for ArgsOf<F>::count == 2 so that this
    // method is called for ALL callbacks other than the ones with a single typed
    // argument. This includes valid callbacks such as with two args, variadic or
    // auto args (c++14) but also invalid callbacks which are not functions or with
    // 0 or more than 2 arguments, in which case this method MUST fail to compile.

    QtPromisePrivate::PromiseResolver<T> resolver{*this};

    try {
        callback(QPromiseResolve<T>(resolver), QPromiseReject<T>(resolver));
    } catch (...) {
        resolver.reject(std::current_exception());
    }
}

template<typename T>
template<typename TFulfilled, typename TRejected>
inline typename QtPromisePrivate::PromiseHandler<T, TFulfilled>::Promise
QPromiseBase<T>::then(const TFulfilled& fulfilled, const TRejected& rejected) const
{
    using namespace QtPromisePrivate;
    using PromiseType = typename PromiseHandler<T, TFulfilled>::Promise;

    PromiseType next([&](const QPromiseResolve<typename PromiseType::Type>& resolve,
                         const QPromiseReject<typename PromiseType::Type>& reject) {
        m_d->addHandler(PromiseHandler<T, TFulfilled>::create(fulfilled, resolve, reject));
        m_d->addCatcher(PromiseCatcher<T, TRejected>::create(rejected, resolve, reject));
    });

    if (!m_d->isPending()) {
        m_d->dispatch();
    }

    return next;
}

template<typename T>
template<typename TFulfilled>
inline typename QtPromisePrivate::PromiseHandler<T, TFulfilled>::Promise
QPromiseBase<T>::then(TFulfilled&& fulfilled) const
{
    return then(std::forward<TFulfilled>(fulfilled), nullptr);
}

template<typename T>
template<typename TRejected>
inline typename QtPromisePrivate::PromiseHandler<T, std::nullptr_t>::Promise
QPromiseBase<T>::fail(TRejected&& rejected) const
{
    return then(nullptr, std::forward<TRejected>(rejected));
}

template<typename T>
template<typename THandler>
inline QPromise<T> QPromiseBase<T>::finally(THandler handler) const
{
    QPromise<T> p = *this;
    return p.then(handler, handler).then([=]() {
        return p;
    });
}

template<typename T>
template<typename THandler>
inline QPromise<T> QPromiseBase<T>::tap(THandler handler) const
{
    QPromise<T> p = *this;
    return p.then(handler).then([=]() {
        return p;
    });
}

template<typename T>
template<typename THandler>
inline QPromise<T> QPromiseBase<T>::tapFail(THandler handler) const
{
    QPromise<T> p = *this;
    return p.then([]() {}, handler).then([=]() {
        return p;
    });
}

template<typename T>
template<typename E>
inline QPromise<T> QPromiseBase<T>::timeout(int msec, E&& error) const
{
    QPromise<T> p = *this;
    return QPromise<T>{[&](const QPromiseResolve<T>& resolve, const QPromiseReject<T>& reject) {
        QTimer::singleShot(msec, [=]() {
            // we don't need to verify the current promise state, reject()
            // takes care of checking if the promise is already resolved,
            // and thus will ignore this rejection.
            reject(std::move(error));
        });

        QtPromisePrivate::PromiseFulfill<QPromise<T>>::call(p, resolve, reject);
    }};
}

template<typename T>
template<typename E>
inline QPromise<T> QPromiseBase<T>::timeout(std::chrono::milliseconds msec, E&& error) const
{
    return timeout(static_cast<int>(msec.count()), std::forward<E>(error));
}

template<typename T>
inline QPromise<T> QPromiseBase<T>::delay(int msec) const
{
    return tap([=]() {
        return QPromise<void>{[&](const QPromiseResolve<void>& resolve) {
            QTimer::singleShot(msec, resolve);
        }};
    });
}

template<typename T>
inline QPromise<T> QPromiseBase<T>::delay(std::chrono::milliseconds msec) const
{
    return delay(static_cast<int>(msec.count()));
}

template<typename T>
inline QPromise<T> QPromiseBase<T>::wait() const
{
    // @TODO wait timeout + global timeout
    while (m_d->isPending()) {
        QCoreApplication::processEvents(QEventLoop::AllEvents);
        QCoreApplication::sendPostedEvents(nullptr, QEvent::DeferredDelete);
    }

    return *this;
}

template<typename T>
template<typename E>
inline QPromise<T> QPromiseBase<T>::reject(E&& error)
{
    return QPromise<T>{[&](const QPromiseResolve<T>&, const QPromiseReject<T>& reject) {
        reject(std::forward<E>(error));
    }};
}

template<typename T>
template<typename Functor>
inline QPromise<T> QPromise<T>::each(Functor fn)
{
    return this->tap([=](const T& values) {
        int i = 0;

        std::vector<QPromise<void>> promises;
        for (const auto& v : values) {
            promises.push_back(QtPromise::attempt(fn, v, i).then([]() {
                // Cast to void in case fn returns a non promise value.
                // TODO remove when implicit cast is implemented.
            }));

            i++;
        }

        return QtPromise::all(promises);
    });
}

template<typename T>
template<typename Functor>
inline QPromise<T> QPromise<T>::filter(Functor fn)
{
    return this->then([=](const T& values) {
        return QtPromise::filter(values, fn);
    });
}

template<typename T>
template<typename Functor>
inline typename QtPromisePrivate::PromiseMapper<T, Functor>::PromiseType
QPromise<T>::map(Functor fn)
{
    return this->then([=](const T& values) {
        return QtPromise::map(values, fn);
    });
}

template<typename T>
template<typename Functor, typename Input>
inline typename QtPromisePrivate::PromiseDeduce<Input>::Type
QPromise<T>::reduce(Functor fn, Input initial)
{
    return this->then([=](const T& values) {
        return QtPromise::reduce(values, fn, initial);
    });
}

template<typename T>
template<typename Functor, typename U>
inline typename QtPromisePrivate::PromiseDeduce<typename U::value_type>::Type
QPromise<T>::reduce(Functor fn)
{
    return this->then([=](const T& values) {
        return QtPromise::reduce(values, fn);
    });
}

template<typename T>
template<template<typename, typename...> class Sequence, typename... Args>
inline QPromise<QVector<T>> QPromise<T>::all(const Sequence<QPromise<T>, Args...>& promises)
{
    return QtPromise::all(promises);
}

template<typename T>
template<typename U>
inline QPromise<U> QPromise<T>::convert() const
{
    return QPromiseBase<T>::then(QtPromisePrivate::PromiseConverter<T, U>::create());
}

template<typename T>
inline QPromise<T> QPromise<T>::resolve(const T& value)
{
    return QPromise<T>{[&](const QPromiseResolve<T>& resolve) {
        resolve(value);
    }};
}

template<typename T>
inline QPromise<T> QPromise<T>::resolve(T&& value)
{
    return QPromise<T>{[&](const QPromiseResolve<T>& resolve) {
        resolve(std::forward<T>(value));
    }};
}

template<template<typename, typename...> class Sequence, typename... Args>
inline QPromise<void> QPromise<void>::all(const Sequence<QPromise<void>, Args...>& promises)
{
    return QtPromise::all(promises);
}

inline QPromise<void> QPromise<void>::resolve()
{
    return QtPromise::resolve();
}

} // namespace QtPromise
