// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef PLATFORM_BASE_ERROR_H_
#define PLATFORM_BASE_ERROR_H_

#include <cassert>
#include <ostream>
#include <string>
#include <utility>

#include "platform/base/macros.h"

namespace openscreen {

// Represents an error returned by an OSP library operation.  An error has a
// code and an optional message.
class Error {
 public:
  // TODO(crbug.com/openscreen/65): Group/rename OSP-specific errors
  // NOTE: new values should be added to the end of the of enum and existing
  // values should not be changed.
  enum class Code : int8_t {
    // No error occurred.
    kNone = 0,

    // A transient condition prevented the operation from proceeding (e.g.,
    // cannot send on a non-blocking socket without blocking). This indicates
    // the caller should try again later.
    kAgain = -1,

    // CBOR errors.
    kCborParsing = 1,
    kCborEncoding = 2,
    kCborIncompleteMessage = 3,
    kCborInvalidResponseId = 4,
    kCborInvalidMessage = 5,

    // Presentation start errors.
    kNoAvailableReceivers = 6,
    kRequestCancelled = 7,
    kNoPresentationFound = 8,
    kPreviousStartInProgress = 9,
    kUnknownStartError = 10,
    kUnknownRequestId = 11,

    kAddressInUse = 12,
    kDomainNameTooLong = 13,
    kDomainNameLabelTooLong = 14,

    kIOFailure = 15,
    kInitializationFailure = 16,
    kInvalidIPV4Address = 17,
    kInvalidIPV6Address = 18,
    kConnectionFailed = 19,

    kSocketOptionSettingFailure = 20,
    kSocketAcceptFailure = 21,
    kSocketBindFailure = 22,
    kSocketClosedFailure = 23,
    kSocketConnectFailure = 24,
    kSocketInvalidState = 25,
    kSocketListenFailure = 26,
    kSocketReadFailure = 27,
    kSocketSendFailure = 28,

    // MDNS errors.
    kMdnsRegisterFailure = 29,
    kMdnsReadFailure = 30,
    kMdnsNonConformingFailure = 31,

    kParseError = 32,
    kUnknownMessageType = 33,

    kNoActiveConnection = 34,
    kAlreadyClosed = 35,
    kInvalidConnectionState = 36,
    kNoStartedPresentation = 37,
    kPresentationAlreadyStarted = 38,

    kJsonParseError = 39,
    kJsonWriteError = 40,

    // OpenSSL errors.

    // Was unable to generate an RSA key.
    kRSAKeyGenerationFailure = 41,
    kRSAKeyParseError = 42,

    // Was unable to initialize an EVP_PKEY type.
    kEVPInitializationError = 43,

    // Was unable to generate a certificate.
    kCertificateCreationError = 44,

    // Certificate failed validation.
    kCertificateValidationError = 45,

    // Failed to produce a hashing digest.
    kSha256HashFailure = 46,

    // A non-recoverable SSL library error has occurred.
    kFatalSSLError = 47,
    kFileLoadFailure = 48,

    // Cast certificate errors.

    // Certificates were not provided for verification.
    kErrCertsMissing = 49,

    // The certificates provided could not be parsed.
    kErrCertsParse = 50,

    // Key usage is missing or is not set to Digital Signature.
    // This error could also be thrown if the CN is missing.
    kErrCertsRestrictions = 51,

    // The current date is before the notBefore date or after the notAfter date.
    kErrCertsDateInvalid = 52,

    // The certificate failed to chain to a trusted root.
    kErrCertsVerifyGeneric = 53,

    // The certificate was not found in the trust store.
    kErrCertsVerifyUntrustedCert = 54,

    // The CRL is missing or failed to verify.
    kErrCrlInvalid = 55,

    // One of the certificates in the chain is revoked.
    kErrCertsRevoked = 56,

    // The pathlen constraint of the root certificate was exceeded.
    kErrCertsPathlen = 57,

    // The certificate provided could not be serialized.
    kErrCertSerialize = 58,

    // Cast authentication errors.
    kCastV2PeerCertEmpty = 59,
    kCastV2WrongPayloadType = 60,
    kCastV2NoPayload = 61,
    kCastV2PayloadParsingFailed = 62,
    kCastV2MessageError = 63,
    kCastV2NoResponse = 64,
    kCastV2FingerprintNotFound = 65,
    kCastV2CertNotSignedByTrustedCa = 66,
    kCastV2CannotExtractPublicKey = 67,
    kCastV2SignedBlobsMismatch = 68,
    kCastV2TlsCertValidityPeriodTooLong = 69,
    kCastV2TlsCertValidStartDateInFuture = 70,
    kCastV2TlsCertExpired = 71,
    kCastV2SenderNonceMismatch = 72,
    kCastV2DigestUnsupported = 73,
    kCastV2SignatureEmpty = 74,

    // Cast channel errors.
    kCastV2ChannelNotOpen = 75,
    kCastV2AuthenticationError = 76,
    kCastV2ConnectError = 77,
    kCastV2CastSocketError = 78,
    kCastV2TransportError = 79,
    kCastV2InvalidMessage = 80,
    kCastV2InvalidChannelId = 81,
    kCastV2ConnectTimeout = 82,
    kCastV2PingTimeout = 83,
    kCastV2ChannelPolicyMismatch = 84,

    kCreateSignatureFailed = 85,

    // Discovery errors.
    kUpdateReceivedRecordFailure = 86,
    kRecordPublicationError = 87,
    kProcessReceivedRecordFailure = 88,

    // Generic errors.
    kUnknownError = 89,
    kNotImplemented = 90,
    kInsufficientBuffer = 91,
    kParameterInvalid = 92,
    kParameterOutOfRange = 93,
    kParameterNullPointer = 94,
    kIndexOutOfBounds = 95,
    kItemAlreadyExists = 96,
    kItemNotFound = 97,
    kOperationInvalid = 98,
    kOperationInProgress = 99,
    kOperationCancelled = 100,
    kInterrupted = 101,

    // Cast streaming errors.
    kUnknownCodec = 102,
    kInvalidCodecParameter = 103,
    kSocketFailure = 104,
    kUnencryptedOffer = 105,
    kRemotingNotSupported = 106,
    kNoStreamSelected = 107,

    // An Answer timeout means that the receiver failed to reply to our Offer
    // within a reasonable amount of time.
    kAnswerTimeout = 108,

    // Received an ANSWER, but it was invalid.
    kInvalidAnswer = 109,

    // A generic message timeout occured.
    kMessageTimeout = 110,
  };

  Error();
  Error(const Error& error);
  Error(Error&& error) noexcept;

  Error(Code code);  // NOLINT
  Error(Code code, const std::string& message);
  Error(Code code, std::string&& message);
  ~Error();

  Error& operator=(const Error& other);
  Error& operator=(Error&& other);
  bool operator==(const Error& other) const;
  bool operator!=(const Error& other) const;

  // Special case comparison with codes. Without this case, comparisons will
  // not work as expected, e.g.
  // const Error foo(Error::Code::kItemNotFound, "Didn't find an item");
  // foo == Error::Code::kItemNotFound is actually false.
  bool operator==(Code code) const;
  bool operator!=(Code code) const;
  bool ok() const { return code_ == Code::kNone; }

  Code code() const { return code_; }
  const std::string& message() const { return message_; }
  std::string& message() { return message_; }

  static const Error& None();

  std::string ToString() const;

 private:
  Code code_ = Code::kNone;
  std::string message_;
};

std::string ToString(openscreen::Error::Code code);
std::ostream& operator<<(std::ostream& os, const Error::Code& code);
std::ostream& operator<<(std::ostream& out, const Error& error);

// A convenience function to return a single value from a function that can
// return a value or an error.  For normal results, construct with a ValueType*
// (ErrorOr takes ownership) and the Error will be kNone with an empty message.
// For Error results, construct with an error code and value.
//
// Example:
//
// ErrorOr<Bar> Foo::DoSomething() {
//   if (success) {
//     return Bar();
//   } else {
//     return Error(kBadThingHappened, "No can do");
//   }
// }
//
// TODO(mfoltz): Add support for type conversions.
template <typename ValueType>
class ErrorOr {
 public:
  static ErrorOr<ValueType> None() {
    static ErrorOr<ValueType> error(Error::Code::kNone);
    return error;
  }

  ErrorOr(const ValueType& value) : value_(value), is_value_(true) {}  // NOLINT
  ErrorOr(ValueType&& value) noexcept                                  // NOLINT
      : value_(std::move(value)), is_value_(true) {}

  ErrorOr(const Error& error) : error_(error), is_value_(false) {  // NOLINT
    assert(error_.code() != Error::Code::kNone);
  }
  ErrorOr(Error&& error) noexcept  // NOLINT
      : error_(std::move(error)), is_value_(false) {
    assert(error_.code() != Error::Code::kNone);
  }
  ErrorOr(Error::Code code) : error_(code), is_value_(false) {  // NOLINT
    assert(error_.code() != Error::Code::kNone);
  }
  ErrorOr(Error::Code code, std::string message)
      : error_(code, std::move(message)), is_value_(false) {
    assert(error_.code() != Error::Code::kNone);
  }

  ErrorOr(const ErrorOr& other) = delete;
  ErrorOr(ErrorOr&& other) noexcept : is_value_(other.is_value_) {
    // NB: Both |value_| and |error_| are uninitialized memory at this point!
    // Unlike the other constructors, the compiler will not auto-generate
    // constructor calls for either union member because neither appeared in
    // this constructor's initializer list.
    if (other.is_value_) {
      new (&value_) ValueType(std::move(other.value_));
    } else {
      new (&error_) Error(std::move(other.error_));
    }
  }

  ErrorOr& operator=(const ErrorOr& other) = delete;
  ErrorOr& operator=(ErrorOr&& other) noexcept {
    this->~ErrorOr<ValueType>();
    new (this) ErrorOr<ValueType>(std::move(other));
    return *this;
  }

  ~ErrorOr() {
    // NB: |value_| or |error_| must be explicitly destroyed since the compiler
    // will not auto-generate the destructor calls for union members.
    if (is_value_) {
      value_.~ValueType();
    } else {
      error_.~Error();
    }
  }

  bool is_error() const { return !is_value_; }
  bool is_value() const { return is_value_; }

  // Unlike Error, we CAN provide an operator bool here, since it is
  // more obvious to callers that ErrorOr<Foo> will be true if it's Foo.
  operator bool() const { return is_value_; }

  const Error& error() const {
    assert(!is_value_);
    return error_;
  }
  Error& error() {
    assert(!is_value_);
    return error_;
  }

  const ValueType& value() const {
    assert(is_value_);
    return value_;
  }
  ValueType& value() {
    assert(is_value_);
    return value_;
  }

  // Move only value or fallback
  ValueType&& value(ValueType&& fallback) {
    if (is_value()) {
      return std::move(value());
    }
    return std::forward<ValueType>(fallback);
  }

  // Copy only value or fallback
  ValueType value(ValueType fallback) const {
    if (is_value()) {
      return value();
    }
    return std::move(fallback);
  }

 private:
  // Only one of these is an active member, determined by |is_value_|. Since
  // they are union'ed, they must be explicitly constructed and destroyed.
  union {
    ValueType value_;
    Error error_;
  };

  // If true, |value_| is initialized and active. Otherwise, |error_| is
  // initialized and active.
  const bool is_value_;
};

// Define comparison operators using SFINAE.
template <typename ValueType>
bool operator<(const ErrorOr<ValueType>& lhs, const ErrorOr<ValueType>& rhs) {
  // Handle the cases where one side is an error.
  if (lhs.is_error() != rhs.is_error()) {
    return lhs.is_error();
  }

  // Handle the case where both sides are errors.
  if (lhs.is_error()) {
    return static_cast<int8_t>(lhs.error().code()) <
           static_cast<int8_t>(rhs.error().code());
  }

  // Handle the case where both are values.
  return lhs.value() < rhs.value();
}

template <typename ValueType>
bool operator>(const ErrorOr<ValueType>& lhs, const ErrorOr<ValueType>& rhs) {
  return rhs < lhs;
}

template <typename ValueType>
bool operator<=(const ErrorOr<ValueType>& lhs, const ErrorOr<ValueType>& rhs) {
  return !(lhs > rhs);
}

template <typename ValueType>
bool operator>=(const ErrorOr<ValueType>& lhs, const ErrorOr<ValueType>& rhs) {
  return !(rhs < lhs);
}

template <typename ValueType>
bool operator==(const ErrorOr<ValueType>& lhs, const ErrorOr<ValueType>& rhs) {
  // Handle the cases where one side is an error.
  if (lhs.is_error() != rhs.is_error()) {
    return false;
  }

  // Handle the case where both sides are errors.
  if (lhs.is_error()) {
    return lhs.error() == rhs.error();
  }

  // Handle the case where both are values.
  return lhs.value() == rhs.value();
}

template <typename ValueType>
bool operator!=(const ErrorOr<ValueType>& lhs, const ErrorOr<ValueType>& rhs) {
  return !(lhs == rhs);
}

}  // namespace openscreen

#endif  // PLATFORM_BASE_ERROR_H_
