/*
 * Copyright (c) 2024 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#ifndef META_API_FUTURE_H
#define META_API_FUTURE_H

#include <base/containers/type_traits.h>

#include <meta/interface/detail/any.h>
#include <meta/interface/interface_helpers.h>
#include <meta/interface/intf_future.h>

META_BEGIN_NAMESPACE()

/**
 * @brief Callable implementation for continuation functions used with futures.
 */
template<typename Func>
class ContinuationFunction : public IntroduceInterfaces<IFutureContinuation> {
    META_INTERFACE(
        IntroduceInterfaces<IFutureContinuation>, ContinuationFunction, "f4736552-7365-4c8f-bbe9-a065e2c30382");

public:
    ContinuationFunction(Func func) : func_(BASE_NS::move(func))
    {}

    IAny::Ptr Invoke(const IAny::Ptr& value) override
    {
        using Result = BASE_NS::remove_reference_t<decltype(func_(value))>;
        if constexpr (!BASE_NS::is_same_v<Result, void>) {
            return IAny::Ptr(new Any<Result>(func_(value)));
        } else {
            func_(value);
            return nullptr;
        }
    }

private:
    Func func_;
};

/**
 * @brief Create continuation function from callable entity (e.g. lambda).
 * The callable entity has to take IAny::Ptr as parameter which is the value from the future.
 */
template<typename Func>
IFutureContinuation::Ptr CreateContinuation(Func func)
{
    return IFutureContinuation::Ptr(new ContinuationFunction(BASE_NS::move(func)));
}

template<typename Param>
struct ContinuationTypedFuntionTypeImpl {
    using Type = void(Param);
};

template<>
struct ContinuationTypedFuntionTypeImpl<void> {
    using Type = void();
};

template<typename Param>
using ContinuationTypedFuntionType = typename ContinuationTypedFuntionTypeImpl<Param>::Type;

template<typename Func, typename Param>
struct ResultTypeImpl {
    using Type = decltype(BASE_NS::declval<Func>()(BASE_NS::declval<Param>()));
};

template<typename Func>
struct ResultTypeImpl<Func, void> {
    using Type = decltype(BASE_NS::declval<Func>()());
};

template<typename Func, typename ParamType>
using ResultType = typename ResultTypeImpl<Func, ParamType>::Type;

template<typename Result, typename Param, typename Func>
Result CallResultHelper(Func func, const IAny::Ptr& v)
{
    if constexpr (!BASE_NS::is_same_v<Param, void>) {
        if (v) {
            Param value{};
            if (v->GetValue(value)) {
                return func(value);
            }
            CORE_LOG_W("Type mismatch for future then");
        }
    } else {
        return func();
    }
    if constexpr (!BASE_NS::is_same_v<Result, void>) {
        return Result{};
    }
}

template<typename Type>
class Future {
public:
    using StateType = IFuture::StateType;

    Future(IFuture::Ptr fut = nullptr) : fut_(BASE_NS::move(fut))
    {}
    /// @see IFuture::GetState
    StateType GetState() const
    {
        return fut_ ? fut_->GetState() : IFuture::ABANDONED;
    }
    /// @see IFuture::Wait
    StateType Wait() const
    {
        return fut_ ? fut_->Wait() : IFuture::ABANDONED;
    }
    /// @see IFuture::WaitFor
    StateType WaitFor(const TimeSpan& time) const
    {
        return fut_ ? fut_->WaitFor(time) : IFuture::ABANDONED;
    }
    /// @see IFuture::Then
    IFuture::Ptr Then(const IFutureContinuation::Ptr& func, const BASE_NS::shared_ptr<ITaskQueue>& queue)
    {
        return fut_ ? fut_->Then(func, queue) : nullptr;
    }
    /**
     * @see IFuture::Then
     * @note Runs the continuation function in the same task queue where the future is processed.
     */
    IFuture::Ptr Then(const IFutureContinuation::Ptr& func)
    {
        return Then(func, nullptr);
    }
    /// Helper function which enables specifying the continuation task as a lambda function
    template<typename Func, typename = EnableIfCanInvokeWithArguments<Func, IFutureContinuation::FunctionType>>
    auto Then(Func func, const BASE_NS::shared_ptr<ITaskQueue>& queue) -> Future<decltype(func(nullptr))>
    {
        return fut_ ? fut_->Then(CreateContinuation(func), queue) : nullptr;
    }
    /// Helper function which enables specifying the continuation task as a lambda function
    template<typename Func, typename ParamType = Type,
        typename = EnableIfCanInvokeWithArguments<Func, ContinuationTypedFuntionType<ParamType>>>
    auto Then(Func func, const BASE_NS::shared_ptr<ITaskQueue>& queue, int = 0) -> Future<ResultType<Func, ParamType>>
    {
        using ReturnType = ResultType<Func, ParamType>;
        auto cont = CreateContinuation([f = BASE_NS::move(func)](const IAny::Ptr& v) mutable {
            return CallResultHelper<ReturnType, ParamType>(BASE_NS::move(f), v);
        });
        return fut_ ? fut_->Then(cont, queue) : nullptr;
    }
    /// Helper function which enables specifying the continuation task as a lambda function
    template<typename Func>
    auto Then(Func func)
    {
        return Then(BASE_NS::move(func), nullptr);
    }
    /// Returns the result of the future
    Type GetResult() const
    {
        if (fut_) {
            return fut_->GetResultOr<Type>(Type{});
        }
        return Type{};
    }
    /// Returns the underlying IFuture object
    IFuture::Ptr GetFuture() const
    {
        return fut_;
    }
    operator IFuture::Ptr() const
    {
        return fut_;
    }
    explicit operator bool() const
    {
        return fut_ != nullptr;
    }

private:
    IFuture::Ptr fut_;
};

template<typename Type>
Type GetResultOr(const Future<Type>& f, NonDeduced_t<Type> def)
{
    auto fut = f.GetFuture();
    if (fut) {
        if (auto p = fut->GetResult()) {
            return GetValue<Type>(*p, BASE_NS::move(def));
        }
    }
    return BASE_NS::move(def);
}

META_END_NAMESPACE()

#endif