/*
 *  Copyright 2017 The WebRTC project authors. All Rights Reserved.
 *
 *  Use of this source code is governed by a BSD-style license
 *  that can be found in the LICENSE file in the root of the source
 *  tree. An additional intellectual property rights grant can be found
 *  in the file PATENTS.  All contributing project authors may
 *  be found in the AUTHORS file in the root of the source tree.
 */

#ifndef API_RTC_ERROR_H_
#define API_RTC_ERROR_H_

#include <stdint.h>

#include <optional>
#include <string>
#include <type_traits>
#include <utility>  // For std::move.

#include "absl/strings/str_cat.h"
#include "absl/strings/string_view.h"
#include "rtc_base/checks.h"
#include "rtc_base/logging.h"
#include "rtc_base/strings/string_builder.h"
#include "rtc_base/system/rtc_export.h"

namespace webrtc {

// Enumeration to represent distinct classes of errors that an application
// may wish to act upon differently. These roughly map to DOMExceptions or
// RTCError "errorDetailEnum" values in the web API, as described in the
// comments below.
enum class RTCErrorType {
  // No error.
  NONE,

  // An operation is valid, but currently unsupported.
  // Maps to OperationError DOMException.
  UNSUPPORTED_OPERATION,

  // A supplied parameter is valid, but currently unsupported.
  // Maps to OperationError DOMException.
  UNSUPPORTED_PARAMETER,

  // General error indicating that a supplied parameter is invalid.
  // Maps to InvalidAccessError or TypeError DOMException depending on context.
  INVALID_PARAMETER,

  // Slightly more specific than INVALID_PARAMETER; a parameter's value was
  // outside the allowed range.
  // Maps to RangeError DOMException.
  INVALID_RANGE,

  // Slightly more specific than INVALID_PARAMETER; an error occurred while
  // parsing string input.
  // Maps to SyntaxError DOMException.
  SYNTAX_ERROR,

  // The object does not support this operation in its current state.
  // Maps to InvalidStateError DOMException.
  INVALID_STATE,

  // An attempt was made to modify the object in an invalid way.
  // Maps to InvalidModificationError DOMException.
  INVALID_MODIFICATION,

  // An error occurred within an underlying network protocol.
  // Maps to NetworkError DOMException.
  NETWORK_ERROR,

  // Some resource has been exhausted; file handles, hardware resources, ports,
  // etc.
  // Maps to OperationError DOMException.
  RESOURCE_EXHAUSTED,

  // The operation failed due to an internal error.
  // Maps to OperationError DOMException.
  INTERNAL_ERROR,

  // An error occured that has additional data.
  // The additional data is specified in
  // https://w3c.github.io/webrtc-pc/#rtcerror-interface
  // Maps to RTCError DOMException.
  OPERATION_ERROR_WITH_DATA,
};

// Detail information, showing what further information should be present.
// https://w3c.github.io/webrtc-pc/#rtcerrordetailtype-enum
enum class RTCErrorDetailType {
  NONE,
  DATA_CHANNEL_FAILURE,
  DTLS_FAILURE,
  FINGERPRINT_FAILURE,
  SCTP_FAILURE,
  SDP_SYNTAX_ERROR,
  HARDWARE_ENCODER_NOT_AVAILABLE,
  HARDWARE_ENCODER_ERROR,
};

// Outputs the error as a friendly string. Update this method when adding a new
// error type.
//
// Only intended to be used for logging/diagnostics. The returned char* points
// to literal strings that live for the whole duration of the program.
RTC_EXPORT absl::string_view ToString(RTCErrorType error);
RTC_EXPORT absl::string_view ToString(RTCErrorDetailType error);

template <typename Sink>
void AbslStringify(Sink& sink, RTCErrorType error) {
  sink.Append(ToString(error));
}

template <typename Sink>
void AbslStringify(Sink& sink, RTCErrorDetailType error_detail) {
  sink.Append(ToString(error_detail));
}

// Roughly corresponds to RTCError in the web api. Holds an error type, a
// message, and possibly additional information specific to that error.
//
// Doesn't contain anything beyond a type and message now, but will in the
// future as more errors are implemented.
class RTC_EXPORT RTCError {
 public:
  // Constructors.

  // Creates a "no error" error.
  RTCError() = default;
  explicit RTCError(RTCErrorType type) : type_(type) {}

  RTCError(RTCErrorType type, absl::string_view message)
      : type_(type), message_(message) {}

  // In many use cases, it is better to use move than copy,
  // but copy and assignment are provided for those cases that need it.
  // Note that this has extra overhead because it copies strings.
  RTCError(const RTCError& other) = default;
  RTCError(RTCError&&) = default;
  RTCError& operator=(const RTCError& other) = default;
  RTCError& operator=(RTCError&&) = default;

  // Identical to default constructed error.
  //
  // Preferred over the default constructor for code readability.
  static RTCError OK();
  static RTCError InvalidParameter(absl::string_view message = "") {
    return RTCError(RTCErrorType::INVALID_PARAMETER, message);
  }
  static RTCError InvalidState(absl::string_view message = "") {
    return RTCError(RTCErrorType::INVALID_STATE, message);
  }
  static RTCError InvalidModification(absl::string_view message = "") {
    return RTCError(RTCErrorType::INVALID_MODIFICATION, message);
  }
  static RTCError UnsupportedOperation(absl::string_view message = "") {
    return RTCError(RTCErrorType::UNSUPPORTED_OPERATION, message);
  }
  static RTCError UnsupportedParameter(absl::string_view message = "") {
    return RTCError(RTCErrorType::UNSUPPORTED_PARAMETER, message);
  }
  static RTCError InvalidRange(absl::string_view message = "") {
    return RTCError(RTCErrorType::INVALID_RANGE, message);
  }
  static RTCError SyntaxError(absl::string_view message = "") {
    return RTCError(RTCErrorType::SYNTAX_ERROR, message);
  }
  static RTCError NetworkError(absl::string_view message = "") {
    return RTCError(RTCErrorType::NETWORK_ERROR, message);
  }
  static RTCError ResourceExhausted(absl::string_view message = "") {
    return RTCError(RTCErrorType::RESOURCE_EXHAUSTED, message);
  }
  static RTCError InternalError(absl::string_view message = "") {
    return RTCError(RTCErrorType::INTERNAL_ERROR, message);
  }
  static RTCError OperationErrorWithData(absl::string_view message = "") {
    return RTCError(RTCErrorType::OPERATION_ERROR_WITH_DATA, message);
  }

  // Error type.
  RTCErrorType type() const { return type_; }
  void set_type(RTCErrorType type) { type_ = type; }

  // Human-readable message describing the error. Shouldn't be used for
  // anything but logging/diagnostics, since messages are not guaranteed to be
  // stable.
  const char* message() const;

  // For when you want to interact directly with the internal StringBuilder.
  // E.g. when you need to use operator<<() with a named variable.
  StringBuilder& string_builder() { return message_; }

  void set_message(absl::string_view message);

  // Overload `operator<<` for temporaries (r-values).
  // The '&&' at the end means that using operator<< is restricted to temporary
  // objects. It won't work with named variables. So, for formatting a message
  // for a temporary RTCError object, you can do:
  //
  //   return RTCError::InvalidParameter() << "Argument x was set to " << x;
  //
  // Whereas for a named RTCError object, you'll do:
  //
  // RTCError error(RTCErrorType::INVALID_PARAMETER);
  // error.string_builder() << "Argument x was set to " << x;
  //
  // If we implement both `RTCError&& operator<<(const T& val) &&` and
  // `RTCError& operator<<(const T& val)` in the RTCError class, we'll run into
  // compiler errors due to ambiguity. Based on how RTCError is most often used,
  // we're picking the && version over the other alternative.
  template <typename T>
  RTCError&& operator<<(const T& val) && {
    message_ << val;
    return std::move(*this);
  }

  RTCErrorDetailType error_detail() const { return error_detail_; }
  void set_error_detail(RTCErrorDetailType detail) { error_detail_ = detail; }
  std::optional<uint16_t> sctp_cause_code() const { return sctp_cause_code_; }
  void set_sctp_cause_code(uint16_t cause_code) {
    sctp_cause_code_ = cause_code;
  }

  // Convenience method for situations where you only care whether or not an
  // error occurred.
  bool ok() const { return type_ == RTCErrorType::NONE; }

  template <typename Sink>
  friend void AbslStringify(Sink& sink, const RTCError& error) {
    sink.Append(ToString(error.type_));
    if (!error.message_.str().empty()) {
      sink.Append(" with message: \"");
      sink.Append(error.message_.str());
      sink.Append("\"");
    }
  }

 private:
  RTCErrorType type_ = RTCErrorType::NONE;
  StringBuilder message_;
  RTCErrorDetailType error_detail_ = RTCErrorDetailType::NONE;
  std::optional<uint16_t> sctp_cause_code_;
};

inline RTCError LogErrorImpl(RTCError error,
                             LoggingSeverity severity,
                             const char* file,
                             int line) {
#if defined(RTC_LOG_FILE_LINE)
  // WebRTC default.
  RTC_LOG_FILE_LINE(severity, file, line)
      << error.message() << " (" << ToString(error.type()) << ")";
#else
  // Compatibility with the chromium build but file:line won't be accurate.
  RTC_LOG_V(severity) << error.message() << " (" << ToString(error.type())
                      << ")";
#endif
  return error;
}

// This approach does not hide the return statement and also allows for
// constructing/formatting the error string inline.
//
// Example usage:
//
// if (failed) {
//   return RTC_LOG_ERROR(RTCError(RTCErrorType::INVALID_STATE) << "Yikes");
// }
//
#define RTC_LOG_ERROR(x) LogErrorImpl(x, LS_ERROR, __FILE__, __LINE__)

// RTCErrorOr<T> is the union of an RTCError object and a T object. RTCErrorOr
// models the concept of an object that is either a usable value, or an error
// Status explaining why such a value is not present. To this end RTCErrorOr<T>
// does not allow its RTCErrorType value to be RTCErrorType::NONE. This is
// enforced by a debug check in most cases.
//
// The primary use-case for RTCErrorOr<T> is as the return value of a function
// which may fail. For example, CreateRtpSender will fail if the parameters
// could not be successfully applied at the media engine level, but if
// successful will return a unique_ptr to an RtpSender.
//
// Example client usage for a RTCErrorOr<std::unique_ptr<T>>:
//
//  RTCErrorOr<std::unique_ptr<Foo>> result = FooFactory::MakeNewFoo(arg);
//  if (result.ok()) {
//    std::unique_ptr<Foo> foo = result.ConsumeValue();
//    foo->DoSomethingCool();
//  } else {
//    RTC_LOG(LS_ERROR) << result.error();
//  }
//
// Example factory implementation returning RTCErrorOr<std::unique_ptr<T>>:
//
//  RTCErrorOr<std::unique_ptr<Foo>> FooFactory::MakeNewFoo(int arg) {
//    if (arg <= 0) {
//      return RTCError(RTCErrorType::INVALID_RANGE, "Arg must be positive");
//    } else {
//      return std::unique_ptr<Foo>(new Foo(arg));
//    }
//  }
//
template <typename T>
class RTCErrorOr {
  // Used to convert between RTCErrorOr<Foo>/RtcErrorOr<Bar>, when an implicit
  // conversion from Foo to Bar exists.
  template <typename U>
  friend class RTCErrorOr;

 public:
  typedef T element_type;

  // Constructs a new RTCErrorOr with RTCErrorType::INTERNAL_ERROR error. This
  // is marked 'explicit' to try to catch cases like 'return {};', where people
  // think RTCErrorOr<std::vector<int>> will be initialized with an empty
  // vector, instead of a RTCErrorType::INTERNAL_ERROR error.
  RTCErrorOr() : error_(RTCErrorType::INTERNAL_ERROR) {}

  // Constructs a new RTCErrorOr with the given non-ok error. After calling
  // this constructor, calls to value() will DCHECK-fail.
  //
  // NOTE: Not explicit - we want to use RTCErrorOr<T> as a return
  // value, so it is convenient and sensible to be able to do 'return
  // RTCError(...)' when the return type is RTCErrorOr<T>.
  //
  // REQUIRES: !error.ok(). This requirement is DCHECKed.
  RTCErrorOr(RTCError&& error) : error_(std::move(error)) {  // NOLINT
    RTC_DCHECK(!error_.ok());
  }

  // Constructs a new RTCErrorOr with the given value. After calling this
  // constructor, calls to value() will succeed, and calls to error() will
  // return a default-constructed RTCError.
  //
  // NOTE: Not explicit - we want to use RTCErrorOr<T> as a return type
  // so it is convenient and sensible to be able to do 'return T()'
  // when the return type is RTCErrorOr<T>.
  RTCErrorOr(const T& value) : value_(value) {}        // NOLINT
  RTCErrorOr(T&& value) : value_(std::move(value)) {}  // NOLINT

  // Delete the copy constructor and assignment operator; there aren't any use
  // cases where you should need to copy an RTCErrorOr, as opposed to moving
  // it. Can revisit this decision if use cases arise in the future.
  RTCErrorOr(const RTCErrorOr& other) = delete;
  RTCErrorOr& operator=(const RTCErrorOr& other) = delete;

  // Move constructor and move-assignment operator.
  //
  // Visual Studio doesn't support "= default" with move constructors or
  // assignment operators (even though they compile, they segfault), so define
  // them explicitly.
  RTCErrorOr(RTCErrorOr&& other)
      : error_(std::move(other.error_)), value_(std::move(other.value_)) {}
  RTCErrorOr& operator=(RTCErrorOr&& other) {
    error_ = std::move(other.error_);
    value_ = std::move(other.value_);
    return *this;
  }

  // Conversion constructor and assignment operator; T must be copy or move
  // constructible from U.
  template <typename U>
  RTCErrorOr(RTCErrorOr<U> other)  // NOLINT
      : error_(std::move(other.error_)), value_(std::move(other.value_)) {}
  template <typename U>
  RTCErrorOr& operator=(RTCErrorOr<U> other) {
    error_ = std::move(other.error_);
    value_ = std::move(other.value_);
    return *this;
  }

  // Returns a reference to our error. If this contains a T, then returns
  // default-constructed RTCError.
  const RTCError& error() const { return error_; }

  // Moves the error. Can be useful if, say "CreateFoo" returns an
  // RTCErrorOr<Foo>, and internally calls "CreateBar" which returns an
  // RTCErrorOr<Bar>, and wants to forward the error up the stack.
  RTCError MoveError() { return std::move(error_); }

  // Returns this->error().ok()
  bool ok() const { return error_.ok(); }

  // Returns a reference to our current value, or DCHECK-fails if !this->ok().
  //
  // Can be convenient for the implementation; for example, a method may want
  // to access the value in some way before returning it to the next method on
  // the stack.
  const T& value() const {
    RTC_DCHECK(ok());
    return *value_;
  }
  T& value() {
    RTC_DCHECK(ok());
    return *value_;
  }

  // Moves our current value out of this object and returns it, or DCHECK-fails
  // if !this->ok().
  T MoveValue() {
    RTC_DCHECK(ok());
    return std::move(*value_);
  }

  template <typename Sink>
  friend void AbslStringify(Sink& sink, const RTCErrorOr<T>& error_or) {
    if (error_or.ok()) {
      sink.Append("OK");
      if constexpr (std::is_convertible_v<T, absl::AlphaNum>) {
        sink.Append(" with value: ");
        sink.Append(absl::StrCat(error_or.value()));
      }
    } else {
      sink.Append(absl::StrCat(error_or.error()));
    }
  }

 private:
  RTCError error_;
  std::optional<T> value_;
};

}  // namespace webrtc

#endif  // API_RTC_ERROR_H_
