/*
 * Copyright 2016 Google LLC
 *
 * 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 FIREBASE_APP_CLIENT_CPP_SRC_INCLUDE_FIREBASE_FUTURE_H_
#define FIREBASE_APP_CLIENT_CPP_SRC_INCLUDE_FIREBASE_FUTURE_H_

#include <stddef.h>
#include <stdint.h>
#include <utility>

#include "firebase/internal/common.h"

#ifdef FIREBASE_USE_STD_FUNCTION
#include <functional>
#endif


namespace firebase {

// Predeclarations.
/// @cond FIREBASE_APP_INTERNAL
namespace detail {
class FutureApiInterface;
class CompletionCallbackHandle;
}  // namespace detail
/// @endcond

/// Asynchronous call status.
enum FutureStatus {
  /// Results are ready.
  kFutureStatusComplete,

  /// Result is still being processed.
  kFutureStatusPending,

  /// No result is pending.
  /// FutureBase::Release() or move operator was called.
  kFutureStatusInvalid
};

/// Handle that the API uses to identify an asynchronous call.
/// The exact interpretation of the handle is up to the API.
typedef uintptr_t FutureHandle;

/// @brief Type-independent return type of asynchronous calls.
///
/// @see Future for code samples.
///
/// @cond FIREBASE_APP_INTERNAL
/// Notes:
///   - Futures have pointers back to the API, but the API does not maintain
///     pointers to its Futures. Therefore, all Futures must be destroyed
///     *before* the API is destroyed.
///   - Futures can be moved or copied. Call results are reference counted,
///     and are destroyed when they are long longer referenced by any Futures.
///   - The actual `Status`, `Error`, and `Result` values are kept inside the
///     API. This makes synchronization and data management easier.
///
/// WARNING: This class should remain POD (plain old data). It should not have
///          virtual methods. Nor should the derived Future<T> class add any
///          data. Internally, we static_cast FutureBase to Future<T>,
///          so the underlying data should remain the same.
/// @endcond
class FutureBase {
 public:
  /// Function pointer for a completion callback. When we call this, we will
  /// send the completed future, along with the user data that you specified
  /// when you set up the callback.
  typedef void (*CompletionCallback)(const FutureBase& result_data,
                                     void* user_data);

#if defined(INTERNAL_EXPERIMENTAL)
  /// Handle, representing a completion callback, that can be passed to
  /// RemoveOnCompletion.
  using CompletionCallbackHandle = detail::CompletionCallbackHandle;
#endif

  /// Construct an untyped future.
  FutureBase();

  /// @cond FIREBASE_APP_INTERNAL

  /// Construct an untyped future using the specified API and handle.
  ///
  /// @param api API class used to provide the future implementation.
  /// @param handle Handle to the future.
  FutureBase(detail::FutureApiInterface* api, FutureHandle handle);

  /// @endcond

  ~FutureBase();

  /// Copy constructor and operator.
  /// Increment the reference count when creating a copy of the future.
  FutureBase(const FutureBase& rhs);

  /// Copy an untyped future.
  FutureBase& operator=(const FutureBase& rhs);

#if defined(FIREBASE_USE_MOVE_OPERATORS)
  /// Move constructor and operator.
  /// Move is more efficient than copy and delete because we don't touch the
  /// reference counting in the API.
  FutureBase(FutureBase&& rhs) noexcept;

  /// Copy an untyped future.
  FutureBase& operator=(FutureBase&& rhs) noexcept;
#endif  // defined(FIREBASE_USE_MOVE_OPERATORS)

  /// Explicitly release the internal resources for a future.
  /// Future will become invalid.
  void Release();

  /// Completion status of the asynchronous call.
  FutureStatus status() const;

  /// When status() is firebase::kFutureStatusComplete, returns the API-defined
  /// error code. Otherwise, return value is undefined.
  int error() const;

  /// When status() is firebase::kFutureStatusComplete, returns the API-defined
  /// error message, as human-readable text, or an empty string if the API does
  /// not provide a human readable description of the error.
  ///
  /// @note The returned pointer is only valid for the lifetime of the Future
  ///       or its copies.
  const char* error_message() const;

  /// Result of the asynchronous call, or nullptr if the result is still
  /// pending. Cast is required since GetFutureResult() returns void*.
  const void* result_void() const;

#if defined(INTERNAL_EXPERIMENTAL)
  /// Special timeout value indicating an infinite timeout.
  ///
  /// Passing this value to FutureBase::Wait() or Future<T>::Await() will cause
  /// those methods to wait until the future is complete.
  ///
  /// @Warning It is inadvisable to use this from code that could be called
  /// from an event loop.
  static const int kWaitTimeoutInfinite;

  /// Block (i.e. suspend the current thread) until either the future is
  /// completed or the specified timeout period (in milliseconds) has elapsed.
  /// If `timeout_milliseconds` is `kWaitTimeoutInfinite`, then the timeout
  /// period is treated as being infinite, i.e. this will block until the
  /// future is completed.
  ///
  /// @return True if the future completed, or
  ///         false if the timeout period elapsed before the future completed.
  bool Wait(int timeout_milliseconds) const;
#endif  // defined(INTERNAL_EXPERIMENTAL)

  /// Register a single callback that will be called at most once, when the
  /// future is completed.
  ///
  /// If you call any OnCompletion() method more than once on the same future,
  /// only the most recent callback you registered with OnCompletion() will be
  /// called.
#if defined(INTERNAL_EXPERIMENTAL)
  /// However completions registered with AddCompletion() will still be
  /// called even if there is a subsequent call to OnCompletion().
  ///
  /// When the future completes, first the most recent callback registered with
  /// OnCompletion(), if any, will be called; then all callbacks registered with
  /// AddCompletion() will be called, in the order that they were registered.
#endif
  ///
  /// When your callback is called, the user_data that you supplied here will be
  /// passed back as the second parameter.
  ///
  /// @param[in] callback Function pointer to your callback.
  /// @param[in] user_data Optional user data. We will pass this back to your
  /// callback.
  void OnCompletion(CompletionCallback callback, void* user_data) const;

#if defined(FIREBASE_USE_STD_FUNCTION) || defined(DOXYGEN)
  /// Register a single callback that will be called at most once, when the
  /// future is completed.
  ///
  /// If you call any OnCompletion() method more than once on the same future,
  /// only the most recent callback you registered with OnCompletion() will be
  /// called.
#if defined(INTERNAL_EXPERIMENTAL)
  /// However completions registered with AddCompletion() will still be
  /// called even if there is a subsequent call to OnCompletion().
  ///
  /// When the future completes, first the most recent callback registered with
  /// OnCompletion(), if any, will be called; then all callbacks registered with
  /// AddCompletion() will be called, in the order that they were registered.
#endif
  ///
  /// @param[in] callback Function or lambda to call.
  ///
  /// @note This method is not available when using STLPort on Android, as
  /// `std::function` is not supported on STLPort.
  void OnCompletion(std::function<void(const FutureBase&)> callback) const;
#endif  // defined(FIREBASE_USE_STD_FUNCTION) || defined(DOXYGEN)

#if defined(INTERNAL_EXPERIMENTAL)
  /// Like OnCompletion, but allows adding multiple callbacks.
  ///
  /// If you call AddCompletion() more than once, all of the completions that
  /// you register will be called, when the future is completed.  However, any
  /// callbacks which were subsequently removed by calling RemoveOnCompletion
  /// will not be called.
  ///
  /// When the future completes, first the most recent callback registered with
  /// OnCompletion(), if any, will be called; then all callbacks registered with
  /// AddCompletion() will be called, in the order that they were registered.
  ///
  /// @param[in] callback Function pointer to your callback.
  /// @param[in] user_data Optional user data. We will pass this back to your
  /// callback.
  /// @return A handle that can be passed to RemoveOnCompletion.
  CompletionCallbackHandle
  AddOnCompletion(CompletionCallback callback, void* user_data) const;

#if defined(FIREBASE_USE_STD_FUNCTION) || defined(DOXYGEN)
  /// Like OnCompletion, but allows adding multiple callbacks.
  ///
  /// If you call AddCompletion() more than once, all of the completions that
  /// you register will be called, when the future is completed.  However, any
  /// callbacks which were subsequently removed by calling RemoveOnCompletion
  /// will not be called.
  ///
  /// When the future completes, first the most recent callback registered with
  /// OnCompletion(), if any, will be called; then all callbacks registered with
  /// AddCompletion() will be called, in the order that they were registered.
  ///
  /// @param[in] callback Function or lambda to call.
  /// @return A handle that can be passed to RemoveOnCompletion.
  ///
  /// @note This method is not available when using STLPort on Android, as
  /// `std::function` is not supported on STLPort.
  CompletionCallbackHandle AddOnCompletion(
      std::function<void(const FutureBase&)> callback) const;

#endif  // defined(FIREBASE_USE_STD_FUNCTION) || defined(DOXYGEN)

  /// Unregisters a callback that was previously registered with
  /// AddOnCompletion.
  ///
  /// @param[in] completion_handle The return value of a previous call to
  ///                              AddOnCompletion.
  void RemoveOnCompletion(CompletionCallbackHandle completion_handle) const;
#endif  // defined(INTERNAL_EXPERIMENTAL)

  /// Returns true if the two Futures reference the same result.
  bool operator==(const FutureBase& rhs) const {
    return api_ == rhs.api_ && handle_ == rhs.handle_;
  }

  /// Returns true if the two Futures reference different results.
  bool operator!=(const FutureBase& rhs) const { return !operator==(rhs); }

#if defined(INTERNAL_EXPERIMENTAL)
  /// Returns the API-specific handle. Should only be called by the API.
  FutureHandle GetHandle() const { return handle_; }
#endif  // defined(INTERNAL_EXPERIMENTAL)

 protected:
  /// @cond FIREBASE_APP_INTERNAL

  /// Backpointer to the issuing API class.
  /// Set to nullptr when Future is invalidated.
  detail::FutureApiInterface* api_;

  /// API-specified handle type.
  FutureHandle handle_;

  /// @endcond
};

/// @brief Type-specific version of FutureBase.
///
/// The Firebase C++ SDK uses this class to return results from asynchronous
/// operations. All Firebase C++ functions and method calls that operate
/// asynchronously return a Future, and provide a "LastResult" function to
/// retrieve the most recent Future result.
///
/// @code
/// // You can retrieve the Future from the function call directly, like this:
/// Future< SampleResultType > future = firebase::SampleAsyncOperation();
///
/// // Or you can retrieve it later, like this:
/// firebase::SampleAsyncOperation();
/// // [...]
/// Future< SampleResultType > future =
///     firebase::SampleAsyncOperationLastResult();
/// @endcode
///
/// When you have a Future from an asynchronous operation, it will eventually
/// complete. Once it is complete, you can check for errors (a nonzero error()
/// means an error occurred) and get the result data if no error occurred by
/// calling result().
///
/// There are two ways to find out that a Future has completed. You can poll
/// its status(), or set an OnCompletion() callback:
///
/// @code
/// // Check whether the status is kFutureStatusComplete.
/// if (future.status() == firebase::kFutureStatusComplete) {
///   if (future.error() == 0) {
///     DoSomethingWithResultData(future.result());
///   }
///   else {
///     LogMessage("Error %d: %s", future.error(), future.error_message());
///   }
/// }
///
/// // Or, set an OnCompletion callback, which accepts a C++11 lambda or
/// // function pointer. You can pass your own user data to the callback. In
/// // most cases, the callback will be running in a different thread, so take
/// // care to make sure your code is thread-safe.
/// future.OnCompletion([](const Future< SampleResultType >& completed_future,
///                        void* user_data) {
///   // We are probably in a different thread right now.
///   if (completed_future.error() == 0) {
///     DoSomethingWithResultData(completed_future.result());
///   }
///   else {
///     LogMessage("Error %d: %s",
///                completed_future.error(),
///                completed_future.error_message());
///   }
/// }, user_data);
/// @endcode
///
/// @tparam ResultType The type of this Future's result.
//
// WARNING: This class should not have virtual methods or data members.
//          See the warning in FutureBase for further details.
template <typename ResultType>
class Future : public FutureBase {
 public:
  /// Function pointer for a completion callback. When we call this, we will
  /// send the completed future, along with the user data that you specified
  /// when you set up the callback.
  typedef void (*TypedCompletionCallback)(const Future<ResultType>& result_data,
                                          void* user_data);

  /// Construct a future.
  Future() {}

  /// @cond FIREBASE_APP_INTERNAL

  /// Construct a future using the specified API and handle.
  ///
  /// @param api API class used to provide the future implementation.
  /// @param handle Handle to the future.
  Future(detail::FutureApiInterface* api, FutureHandle handle)
      : FutureBase(api, handle) {}

  /// @endcond

  /// Result of the asynchronous call, or nullptr if the result is still
  /// pending. Allows the API to provide a type-specific interface.
  ///
  const ResultType* result() const {
    return static_cast<const ResultType*>(result_void());
  }

#if defined(INTERNAL_EXPERIMENTAL)
  /// Waits (blocks) until either the future is completed, or the specified
  /// timeout period (in milliseconds) has elapsed, then returns the result of
  /// the asynchronous call.
  ///
  /// This is a convenience method that calls Wait() and then returns result().
  ///
  /// If `timeout_milliseconds` is `kWaitTimeoutInfinite`, then the timeout
  /// period is treated as being infinite, i.e. this will block until the
  /// future is completed.
  const ResultType* Await(int timeout_milliseconds) const {
    Wait(timeout_milliseconds);
    return result();
  }
#endif  // defined(INTERNAL_EXPERIMENTAL)

  /// Register a single callback that will be called at most once, when the
  /// future is completed.
  ///
  /// If you call any OnCompletion() method more than once on the same future,
  /// only the most recent callback you registered will be called.
  ///
  /// When your callback is called, the user_data that you supplied here will be
  /// passed back as the second parameter.
  ///
  /// @param[in] callback Function pointer to your callback.
  /// @param[in] user_data Optional user data. We will pass this back to your
  /// callback.
  ///
  /// @note This is the same callback as FutureBase::OnCompletion(), so you
  /// can't expect to set both and have both run; again, only the most recently
  /// registered one will run.
  inline void
  OnCompletion(TypedCompletionCallback callback, void* user_data) const;

#if defined(FIREBASE_USE_STD_FUNCTION) || defined(DOXYGEN)
  /// Register a single callback that will be called at most once, when the
  /// future is completed.
  ///
  /// If you call any OnCompletion() method more than once on the same future,
  /// only the most recent callback you registered will be called.
  ///
  /// @param[in] callback Function or lambda to call.
  ///
  /// @note This method is not available when using STLPort on Android, as
  /// `std::function` is not supported on STLPort.
  ///
  /// @note This is the same callback as FutureBase::OnCompletion(), so you
  /// can't expect to set both and have both run; again, only the most recently
  /// registered one will run.
  inline void
  OnCompletion(std::function<void(const Future<ResultType>&)> callback) const;
#endif  // defined(FIREBASE_USE_STD_FUNCTION) || defined(DOXYGEN)

#if defined(INTERNAL_EXPERIMENTAL)
  /// Like OnCompletion, but allows adding multiple callbacks.
  ///
  /// If you call AddCompletion() more than once, all of the completions that
  /// you register will be called, when the future is completed.  However, any
  /// callbacks which were subsequently removed by calling RemoveOnCompletion
  /// will not be called.
  ///
  /// When the future completes, first the most recent callback registered with
  /// OnCompletion(), if any, will be called; then all callbacks registered with
  /// AddCompletion() will be called, in the order that they were registered.
  ///
  /// @param[in] callback Function pointer to your callback.
  /// @param[in] user_data Optional user data. We will pass this back to your
  /// callback.
  /// @return A handle that can be passed to RemoveOnCompletion.
  inline CompletionCallbackHandle
  AddOnCompletion(TypedCompletionCallback callback, void* user_data) const;

#if defined(FIREBASE_USE_STD_FUNCTION) || defined(DOXYGEN)
  /// Like OnCompletion, but allows adding multiple callbacks.
  ///
  /// If you call AddCompletion() more than once, all of the completions that
  /// you register will be called, when the future is completed.  However, any
  /// callbacks which were subsequently removed by calling RemoveOnCompletion
  /// will not be called.
  ///
  /// When the future completes, first the most recent callback registered with
  /// OnCompletion(), if any, will be called; then all callbacks registered with
  /// AddCompletion() will be called, in the order that they were registered.
  ///
  /// @param[in] callback Function or lambda to call.
  /// @return A handle that can be passed to RemoveOnCompletion.
  ///
  /// @note This method is not available when using STLPort on Android, as
  /// `std::function` is not supported on STLPort.
  inline CompletionCallbackHandle
  AddOnCompletion(std::function<void(const Future<ResultType>&)> callback)
      const;
#endif  // defined(FIREBASE_USE_STD_FUNCTION) || defined(DOXYGEN)
#endif  // defined(INTERNAL_EXPERIMENTAL)
};

// NOLINTNEXTLINE - allow namespace overridden
}  // namespace firebase

// Include the inline implementation.
#include "firebase/internal/future_impl.h"

#endif  // FIREBASE_APP_CLIENT_CPP_SRC_INCLUDE_FIREBASE_FUTURE_H_
