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

#ifndef COMPONENTS_CBOR_VALUES_H_
#define COMPONENTS_CBOR_VALUES_H_

#include <stdint.h>

#include <string>
#include <tuple>
#include <vector>

#include "base/containers/flat_map.h"
#include "base/containers/span.h"
#include "base/macros.h"
#include "base/strings/string_piece.h"
#include "components/cbor/cbor_export.h"

namespace cbor {

// A class for Concise Binary Object Representation (CBOR) values.
// This does not support:
//  * Floating-point numbers.
//  * Indefinite-length encodings.
class CBOR_EXPORT Value {
 public:
  struct Less {
    // Comparison predicate to order keys in a dictionary as required by the
    // canonical CBOR order defined in
    // https://tools.ietf.org/html/rfc7049#section-3.9
    // TODO(808022): Clarify where this stands.
    bool operator()(const Value& a, const Value& b) const {
      // The current implementation only supports integer, text string,
      // and byte string keys.
      DCHECK((a.is_integer() || a.is_string() || a.is_bytestring()) &&
             (b.is_integer() || b.is_string() || b.is_bytestring()));

      // Below text from https://tools.ietf.org/html/rfc7049 errata 4409:
      // *  If the major types are different, the one with the lower value
      //    in numerical order sorts earlier.
      if (a.type() != b.type())
        return a.type() < b.type();

      // *  If two keys have different lengths, the shorter one sorts
      //    earlier;
      // *  If two keys have the same length, the one with the lower value
      //    in (byte-wise) lexical order sorts earlier.
      switch (a.type()) {
        case Type::UNSIGNED:
          // For unsigned integers, the smaller value has shorter length,
          // and (byte-wise) lexical representation.
          return a.GetInteger() < b.GetInteger();
        case Type::NEGATIVE:
          // For negative integers, the value closer to zero has shorter length,
          // and (byte-wise) lexical representation.
          return a.GetInteger() > b.GetInteger();
        case Type::STRING: {
          const auto& a_str = a.GetString();
          const size_t a_length = a_str.size();
          const auto& b_str = b.GetString();
          const size_t b_length = b_str.size();
          return std::tie(a_length, a_str) < std::tie(b_length, b_str);
        }
        case Type::BYTE_STRING: {
          const auto& a_str = a.GetBytestring();
          const size_t a_length = a_str.size();
          const auto& b_str = b.GetBytestring();
          const size_t b_length = b_str.size();
          return std::tie(a_length, a_str) < std::tie(b_length, b_str);
        }
        default:
          break;
      }

      NOTREACHED();
      return false;
    }

    using is_transparent = void;
  };

  using BinaryValue = std::vector<uint8_t>;
  using ArrayValue = std::vector<Value>;
  using MapValue = base::flat_map<Value, Value, Less>;

  enum class Type {
    UNSIGNED = 0,
    NEGATIVE = 1,
    BYTE_STRING = 2,
    STRING = 3,
    ARRAY = 4,
    MAP = 5,
    TAG = 6,
    SIMPLE_VALUE = 7,
    NONE = -1,
    INVALID_UTF8 = -2,
  };

  enum class SimpleValue {
    FALSE_VALUE = 20,
    TRUE_VALUE = 21,
    NULL_VALUE = 22,
    UNDEFINED = 23,
  };

  // Returns a Value with Type::INVALID_UTF8. This factory method lets tests
  // encode such a value as a CBOR string. It should never be used outside of
  // tests since encoding may yield invalid CBOR data.
  static Value InvalidUTF8StringValueForTesting(base::StringPiece in_string);

  Value(Value&& that) noexcept;
  Value() noexcept;  // A NONE value.

  explicit Value(Type type);

  explicit Value(SimpleValue in_simple);
  explicit Value(bool boolean_value);

  explicit Value(int integer_value);
  explicit Value(int64_t integer_value);
  explicit Value(uint64_t integer_value) = delete;

  explicit Value(base::span<const uint8_t> in_bytes);
  explicit Value(BinaryValue&& in_bytes) noexcept;

  explicit Value(const char* in_string, Type type = Type::STRING);
  explicit Value(std::string&& in_string, Type type = Type::STRING) noexcept;
  explicit Value(base::StringPiece in_string, Type type = Type::STRING);

  explicit Value(const ArrayValue& in_array);
  explicit Value(ArrayValue&& in_array) noexcept;

  explicit Value(const MapValue& in_map);
  explicit Value(MapValue&& in_map) noexcept;

  Value& operator=(Value&& that) noexcept;

  ~Value();

  // Value's copy constructor and copy assignment operator are deleted.
  // Use this to obtain a deep copy explicitly.
  Value Clone() const;

  // Returns the type of the value stored by the current Value object.
  Type type() const { return type_; }

  // Returns true if the current object represents a given type.
  bool is_type(Type type) const { return type == type_; }
  bool is_none() const { return type() == Type::NONE; }
  bool is_invalid_utf8() const { return type() == Type::INVALID_UTF8; }
  bool is_simple() const { return type() == Type::SIMPLE_VALUE; }
  bool is_bool() const {
    return is_simple() && (simple_value_ == SimpleValue::TRUE_VALUE ||
                           simple_value_ == SimpleValue::FALSE_VALUE);
  }
  bool is_unsigned() const { return type() == Type::UNSIGNED; }
  bool is_negative() const { return type() == Type::NEGATIVE; }
  bool is_integer() const { return is_unsigned() || is_negative(); }
  bool is_bytestring() const { return type() == Type::BYTE_STRING; }
  bool is_string() const { return type() == Type::STRING; }
  bool is_array() const { return type() == Type::ARRAY; }
  bool is_map() const { return type() == Type::MAP; }

  // These will all fatally assert if the type doesn't match.
  SimpleValue GetSimpleValue() const;
  bool GetBool() const;
  const int64_t& GetInteger() const;
  const int64_t& GetUnsigned() const;
  const int64_t& GetNegative() const;
  const BinaryValue& GetBytestring() const;
  base::StringPiece GetBytestringAsString() const;
  // Returned string may contain NUL characters.
  const std::string& GetString() const;
  const ArrayValue& GetArray() const;
  const MapValue& GetMap() const;
  const BinaryValue& GetInvalidUTF8() const;

 private:
  friend class Reader;
  // This constructor allows INVALID_UTF8 values to be created, which only
  // |Reader| and InvalidUTF8StringValueForTesting() may do.
  Value(base::span<const uint8_t> in_bytes, Type type);

  Type type_;

  union {
    SimpleValue simple_value_;
    int64_t integer_value_;
    BinaryValue bytestring_value_;
    std::string string_value_;
    ArrayValue array_value_;
    MapValue map_value_;
  };

  void InternalMoveConstructFrom(Value&& that);
  void InternalCleanup();

  DISALLOW_COPY_AND_ASSIGN(Value);
};

}  // namespace cbor

#endif  // COMPONENTS_CBOR_VALUES_H_
