/*
 * 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;
}  // 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);

  /// 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);

  /// Copy an untyped future.
  FutureBase& operator=(FutureBase&& rhs);
#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 kFutureStatusComplete, returns the API-defined
  /// error code. Otherwise, return value is undefined.
  int error() const;

  /// When status() is 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;

  /// Register a callback that will be called at most once, when the future is
  /// completed.
  ///
  /// If you call any OnCompletion() method more than once, 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.
  void OnCompletion(CompletionCallback callback, void* user_data) const;

#if defined(FIREBASE_USE_STD_FUNCTION) || defined(DOXYGEN)
  /// Register a callback that will be called at most once, when the future is
  /// completed.
  ///
  /// If you call any OnCompletion() method more than once, 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.
  void OnCompletion(std::function<void(const FutureBase&)> callback) const;
#endif  // defined(FIREBASE_USE_STD_FUNCTION) || defined(DOXYGEN)

  /// 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());
  }

  /// Register a callback that will be called at most once, when the future is
  /// completed.
  ///
  /// If you call any OnCompletion() method more than once, 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.
  void OnCompletion(TypedCompletionCallback callback, void* user_data) const {
    FutureBase::OnCompletion(reinterpret_cast<CompletionCallback>(callback),
                             user_data);
  }

#if defined(FIREBASE_USE_STD_FUNCTION) || defined(DOXYGEN)
  /// Register a callback that will be called at most once, when the future is
  /// completed.
  ///
  /// If you call any OnCompletion() method more than once, 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.
  void OnCompletion(
      std::function<void(const Future<ResultType>&)> callback) const {
    FutureBase::OnCompletion(
        *reinterpret_cast<std::function<void(const FutureBase&)>*>(&callback));
  }
#endif  // defined(FIREBASE_USE_STD_FUNCTION) || defined(DOXYGEN)
};

// 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_
