// Copyright 2018 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
//
//     https://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 GOOGLE_CLOUD_CPP_GOOGLE_CLOUD_STATUS_OR_H
#define GOOGLE_CLOUD_CPP_GOOGLE_CLOUD_STATUS_OR_H

#include "google/cloud/internal/throw_delegate.h"
#include "google/cloud/status.h"
#include "google/cloud/version.h"
#include "absl/types/optional.h"
#include <type_traits>
#include <utility>

namespace google {
namespace cloud {
GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_BEGIN

/**
 * Holds a value or a `Status` indicating why there is no value.
 *
 * `StatusOr<T>` represents either a usable `T` value or a `Status` object
 * explaining why a `T` value is not present. Typical usage of `StatusOr<T>`
 * looks like usage of a smart pointer, or even a `std::optional<T>`, in that
 * you first check its validity using a conversion to bool (or by calling
 * `StatusOr::ok()`), then you may dereference the object to access the
 * contained value.
 *
 * It is undefined behavior (UB) to dereference a `StatusOr<T>` that is not
 * "ok". For example:
 *
 * @code
 * StatusOr<Foo> foo = FetchFoo();
 * if (!foo) {  // Same as !foo.ok()
 *   // handle error and probably look at foo.status()
 * } else {
 *   foo->DoSomethingFooey();  // UB if !foo
 * }
 * @endcode
 *
 * Alternatively, you may call the `StatusOr::value()` member function,
 * which is defined to: (1) throw an exception if there is no `T` value, or (2)
 * crash the program if exceptions are disabled. It is never UB to call
 * `value()`.
 *
 * @code
 * StatusOr<Foo> foo = FetchFoo();
 * foo.value().DoSomethingFooey();  // May throw/crash if there is no value
 * @endcode
 *
 * Functions that can fail will often return a `StatusOr<T>` instead of
 * returning an error code and taking a `T` out-param, or rather than directly
 * returning the `T` and throwing an exception on error. `StatusOr<T>` is used
 * so that callers can choose whether they want to explicitly check for errors,
 * crash the program, or throw exceptions.
 *
 * Since constructors do not have a return value, they should be designed in
 * such a way that they cannot fail by moving the object's complex
 * initialization logic into a separate factory function that itself can return
 * a `StatusOr<T>`. For example:
 *
 * @code
 * class Bar {
 *  public:
 *   Bar(Arg arg);
 *   ...
 * };
 * StatusOr<Bar> MakeBar() {
 *   ... complicated logic that might fail
 *   return Bar(std::move(arg));
 * }
 * @endcode
 *
 * `StatusOr<T>` supports equality comparisons if the underlying type `T` does.
 *
 * @tparam T the type of the value.
 */
template <typename T>
class StatusOr final {
 public:
  static_assert(!std::is_reference<T>::value,
                "StatusOr<T> requires T to **not** be a reference type");

  /**
   * A `value_type` member for use in generic programming.
   *
   * This is analogous to that of `std::optional::value_type`.
   */
  using value_type = T;

  /**
   * Initializes with an error status (`StatusCode::kUnknown`).
   */
  StatusOr() : StatusOr(MakeDefaultStatus()) {}

  StatusOr(StatusOr const&) = default;
  StatusOr& operator=(StatusOr const&) = default;
  // NOLINTNEXTLINE(performance-noexcept-move-constructor)
  StatusOr(StatusOr&& other)
      : status_(std::move(other.status_)), value_(std::move(other.value_)) {
    other.status_ = MakeDefaultStatus();
  }
  // NOLINTNEXTLINE(performance-noexcept-move-constructor)
  StatusOr& operator=(StatusOr&& other) {
    status_ = std::move(other.status_);
    value_ = std::move(other.value_);
    other.status_ = MakeDefaultStatus();
    return *this;
  }

  /**
   * Creates a new `StatusOr<T>` holding the error condition @p rhs.
   *
   * @par Post-conditions
   * `ok() == false` and `status() == rhs`.
   *
   * @param rhs the status to initialize the object.
   * @throws std::invalid_argument if `rhs.ok()`. If exceptions are disabled the
   *     program terminates via `google::cloud::Terminate()`
   */
  // NOLINTNEXTLINE(google-explicit-constructor)
  StatusOr(Status rhs) : status_(std::move(rhs)) {
    if (status_.ok()) {
      google::cloud::internal::ThrowInvalidArgument(__func__);
    }
  }

  /**
   * Assigns the given non-OK Status to this `StatusOr<T>`.
   *
   * @throws std::invalid_argument if `status.ok()`. If exceptions are disabled
   *     the program terminates via `google::cloud::Terminate()`
   */
  StatusOr& operator=(Status status) {
    *this = StatusOr(std::move(status));
    return *this;
  }

  /**
   * Assign a `T` (or anything convertible to `T`) into the `StatusOr`.
   *
   * This function does not participate in overload resolution if `U` is equal
   * to `StatusOr<T>` (or to a cv-ref-qualified `StatusOr<T>`).
   *
   * @return a reference to this object.
   * @tparam U a type convertible to @p T.
   */
  template <
      typename U = T,
      /// @cond implementation_details
      std::enable_if_t<!std::is_same<StatusOr, std::decay_t<U>>::value, int> = 0
      /// @endcond
      >
  StatusOr& operator=(U&& rhs) {
    status_ = Status();
    value_ = std::forward<U>(rhs);
    return *this;
  }

  /**
   * Creates a new `StatusOr<T>` holding the value @p rhs.
   *
   * @par Post-conditions
   * `ok() == true` and `value() == rhs`.
   *
   * @param rhs the value used to initialize the object.
   *
   * @throws ... If `T`'s move constructor throws.
   */
  // NOLINTNEXTLINE(google-explicit-constructor)
  StatusOr(T&& rhs) : value_(std::move(rhs)) {}

  /**
   * Creates a new `StatusOr<T>` holding the value @p rhs.
   *
   * @par Post-conditions
   * `ok() == true` and `value() == rhs`.
   *
   * @param rhs the value used to initialize the object.
   *
   * @throws ... If `T` copy constructor throws.
   */
  // NOLINTNEXTLINE(google-explicit-constructor)
  StatusOr(T const& rhs) : value_(rhs) {}

  /// Returns `true` when `this` holds a value.
  bool ok() const { return status_.ok(); }

  /// Returns `true` when `this` holds a value.
  explicit operator bool() const { return status_.ok(); }

  ///@{
  /**
   * @name Dereference operators.
   *
   * @par Pre-conditions
   * @parblock
   * `ok() == true`
   *
   * @warning Using these operators when `ok() == false` results in undefined
   *     behavior.
   * @endparblock
   *
   * @return A properly ref and const-qualified reference to the underlying
   *     value.
   */
  T& operator*() & { return *value_; }

  T const& operator*() const& { return *value_; }

  T&& operator*() && { return *std::move(value_); }

  T const&& operator*() const&& { return *std::move(value_); }
  ///@}

  ///@{
  /**
   * @name Member access operators.
   *
   * @par Pre-conditions
   * @parblock
   * `ok() == true`
   *
   * @warning Using these operators when `ok() == false` results in undefined
   *     behavior.
   * @endparblock
   *
   * @return A properly ref and const-qualified pointer to the underlying value.
   */
  T* operator->() & { return &*value_; }

  T const* operator->() const& { return &*value_; }
  ///@}

  ///@{
  /**
   * @name Value accessors.
   *
   * @return All these member functions return a (properly ref and
   *     const-qualified) reference to the underlying value.
   *
   * @throws RuntimeStatusError with the contents of `status()` if the object
   *   does not contain a value, i.e., if `ok() == false`.
   */
  T& value() & {
    CheckHasValue();
    return **this;
  }

  T const& value() const& {
    CheckHasValue();
    return **this;
  }

  T&& value() && {
    CheckHasValue();
    return std::move(**this);
  }

  T const&& value() const&& {
    CheckHasValue();
    return std::move(**this);
  }
  ///@}

  ///@{
  /**
   * @name Status accessors.
   *
   * @return A reference to the contained `Status`.
   */
  Status const& status() const& { return status_; }
  Status&& status() && { return std::move(status_); }
  ///@}

 private:
  static Status MakeDefaultStatus() {
    return Status{StatusCode::kUnknown, "default"};
  }

  void CheckHasValue() const& {
    if (!ok()) {
      internal::ThrowStatus(status_);
    }
  }

  // When possible, do not copy the status.
  void CheckHasValue() && {
    if (!ok()) {
      internal::ThrowStatus(std::move(status_));
    }
  }

  Status status_;
  absl::optional<T> value_;
};

// Returns true IFF both `StatusOr<T>` objects hold an equal `Status` or an
// equal instance  of `T`. This function requires that `T` supports equality.
template <typename T>
bool operator==(StatusOr<T> const& a, StatusOr<T> const& b) {
  if (!a || !b) return a.status() == b.status();
  return *a == *b;
}

// Returns true of `a` and `b` are not equal. See `operator==` docs above for
// the definition of equal.
template <typename T>
bool operator!=(StatusOr<T> const& a, StatusOr<T> const& b) {
  return !(a == b);
}

template <typename T>
StatusOr<T> make_status_or(T rhs) {
  return StatusOr<T>(std::move(rhs));
}

GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_END
}  // namespace cloud
}  // namespace google

#endif  // GOOGLE_CLOUD_CPP_GOOGLE_CLOUD_STATUS_OR_H
