#pragma once

#include "fwd.h"

#include <library/cpp/deprecated/atomic/atomic.h>

#include <util/datetime/base.h>
#include <util/generic/function.h>
#include <util/generic/maybe.h>
#include <util/generic/ptr.h>
#include <util/generic/vector.h>
#include <util/generic/yexception.h>
#include <util/system/event.h>
#include <util/system/spinlock.h>

namespace NThreading {
    ////////////////////////////////////////////////////////////////////////////////

    struct TFutureException: public yexception {};

    // creates unset promise
    template <typename T>
    TPromise<T> NewPromise();
    TPromise<void> NewPromise();

    // creates preset future
    template <typename T>
    TFuture<T> MakeFuture(const T& value);
    template <typename T>
    TFuture<std::remove_reference_t<T>> MakeFuture(T&& value);
    template <typename T>
    TFuture<T> MakeFuture();
    template <typename T>
    TFuture<T> MakeErrorFuture(std::exception_ptr exception);
    TFuture<void> MakeFuture();

    ////////////////////////////////////////////////////////////////////////////////

    namespace NImpl {
        template <typename T>
        class TFutureState;

        template <typename T>
        struct TFutureType {
            using TType = T;
        };

        template <typename T>
        struct TFutureType<TFuture<T>> {
            using TType = typename TFutureType<T>::TType;
        };

        template <typename F, typename T>
        struct TFutureCallResult {
            // NOTE: separate class for msvc compatibility
            using TType = decltype(std::declval<F&>()(std::declval<const TFuture<T>&>()));
        };
    }

    template <typename F>
    using TFutureType = typename NImpl::TFutureType<F>::TType;

    template <typename F, typename T>
    using TFutureCallResult = typename NImpl::TFutureCallResult<F, T>::TType;

    //! Type of the future/promise state identifier
    class TFutureStateId;

    ////////////////////////////////////////////////////////////////////////////////

    template <typename T>
    class TFuture {
        using TFutureState = NImpl::TFutureState<T>;

    private:
        TIntrusivePtr<TFutureState> State;

    public:
        using value_type = T;

        TFuture() noexcept = default;
        TFuture(const TFuture<T>& other) noexcept = default;
        TFuture(TFuture<T>&& other) noexcept = default;
        TFuture(const TIntrusivePtr<TFutureState>& state) noexcept;

        TFuture<T>& operator=(const TFuture<T>& other) noexcept = default;
        TFuture<T>& operator=(TFuture<T>&& other) noexcept = default;
        void Swap(TFuture<T>& other);

        bool Initialized() const;

        bool HasValue() const;
        const T& GetValue(TDuration timeout = TDuration::Zero()) const;
        const T& GetValueSync() const;
        T ExtractValue(TDuration timeout = TDuration::Zero());
        T ExtractValueSync();

        void TryRethrow() const;
        bool HasException() const;

        void Wait() const;
        bool Wait(TDuration timeout) const;
        bool Wait(TInstant deadline) const;

        template <typename F>
        const TFuture<T>& Subscribe(F&& callback) const;

        // precondition: EnsureInitialized() passes
        // postcondition: std::terminate is highly unlikely
        template <typename F>
        const TFuture<T>& NoexceptSubscribe(F&& callback) const noexcept;

        template <typename F>
        TFuture<TFutureType<TFutureCallResult<F, T>>> Apply(F&& func) const;

        TFuture<void> IgnoreResult() const;

        //! If the future is initialized returns the future state identifier. Otherwise returns an empty optional
        /** The state identifier is guaranteed to be unique during the future state lifetime and could be reused after its death
        **/
        TMaybe<TFutureStateId> StateId() const noexcept;

        void EnsureInitialized() const;
    };

    ////////////////////////////////////////////////////////////////////////////////

    template <>
    class TFuture<void> {
        using TFutureState = NImpl::TFutureState<void>;

    private:
        TIntrusivePtr<TFutureState> State = nullptr;

    public:
        using value_type = void;

        TFuture() noexcept = default;
        TFuture(const TFuture<void>& other) noexcept = default;
        TFuture(TFuture<void>&& other) noexcept = default;
        TFuture(const TIntrusivePtr<TFutureState>& state) noexcept;

        TFuture<void>& operator=(const TFuture<void>& other) noexcept = default;
        TFuture<void>& operator=(TFuture<void>&& other) noexcept = default;
        void Swap(TFuture<void>& other);

        bool Initialized() const;

        bool HasValue() const;
        void GetValue(TDuration timeout = TDuration::Zero()) const;
        void GetValueSync() const;

        void TryRethrow() const;
        bool HasException() const;

        void Wait() const;
        bool Wait(TDuration timeout) const;
        bool Wait(TInstant deadline) const;

        template <typename F>
        const TFuture<void>& Subscribe(F&& callback) const;

        // precondition: EnsureInitialized() passes
        // postcondition: std::terminate is highly unlikely
        template <typename F>
        const TFuture<void>& NoexceptSubscribe(F&& callback) const noexcept;

        template <typename F>
        TFuture<TFutureType<TFutureCallResult<F, void>>> Apply(F&& func) const;

        template <typename R>
        TFuture<R> Return(const R& value) const;

        TFuture<void> IgnoreResult() const {
            return *this;
        }

        //! If the future is initialized returns the future state identifier. Otherwise returns an empty optional
        /** The state identifier is guaranteed to be unique during the future state lifetime and could be reused after its death
        **/
        TMaybe<TFutureStateId> StateId() const noexcept;

        void EnsureInitialized() const;
    };

    ////////////////////////////////////////////////////////////////////////////////

    template <typename T>
    class TPromise {
        using TFutureState = NImpl::TFutureState<T>;

    private:
        TIntrusivePtr<TFutureState> State = nullptr;

    public:
        TPromise() noexcept = default;
        TPromise(const TPromise<T>& other) noexcept = default;
        TPromise(TPromise<T>&& other) noexcept = default;
        TPromise(const TIntrusivePtr<TFutureState>& state) noexcept;

        TPromise<T>& operator=(const TPromise<T>& other) noexcept = default;
        TPromise<T>& operator=(TPromise<T>&& other) noexcept = default;
        void Swap(TPromise<T>& other);

        bool Initialized() const;

        bool HasValue() const;
        const T& GetValue() const;
        T ExtractValue();

        void SetValue(const T& value);
        void SetValue(T&& value);

        bool TrySetValue(const T& value);
        bool TrySetValue(T&& value);

        void TryRethrow() const;
        bool HasException() const;
        void SetException(const TString& e);
        void SetException(std::exception_ptr e);
        bool TrySetException(std::exception_ptr e);

        TFuture<T> GetFuture() const;
        operator TFuture<T>() const;

    private:
        void EnsureInitialized() const;
    };

    ////////////////////////////////////////////////////////////////////////////////

    template <>
    class TPromise<void> {
        using TFutureState = NImpl::TFutureState<void>;

    private:
        TIntrusivePtr<TFutureState> State;

    public:
        TPromise() noexcept = default;
        TPromise(const TPromise<void>& other) noexcept = default;
        TPromise(TPromise<void>&& other) noexcept = default;
        TPromise(const TIntrusivePtr<TFutureState>& state) noexcept;

        TPromise<void>& operator=(const TPromise<void>& other) noexcept = default;
        TPromise<void>& operator=(TPromise<void>&& other) noexcept = default;
        void Swap(TPromise<void>& other);

        bool Initialized() const;

        bool HasValue() const;
        void GetValue() const;

        void SetValue();
        bool TrySetValue();

        void TryRethrow() const;
        bool HasException() const;
        void SetException(const TString& e);
        void SetException(std::exception_ptr e);
        bool TrySetException(std::exception_ptr e);

        TFuture<void> GetFuture() const;
        operator TFuture<void>() const;

    private:
        void EnsureInitialized() const;
    };

}

#define INCLUDE_FUTURE_INL_H
#include "future-inl.h"
#undef INCLUDE_FUTURE_INL_H
