// Copyright 2019 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 V8_CRDTP_SERIALIZER_TRAITS_H_
#define V8_CRDTP_SERIALIZER_TRAITS_H_

#include <memory>
#include <string>
#include <vector>
#include "cbor.h"
#include "maybe.h"
#include "span.h"

namespace v8_crdtp {
// =============================================================================
// SerializerTraits - Encodes field values of protocol objects in CBOR.
// =============================================================================
//
// A family of serialization functions which are used by FieldSerializerTraits
// (below) to encode field values in CBOR. Conceptually, it's this:
//
// Serialize(bool value, std::vector<uint8_t>* out);
// Serialize(int32_t value, std::vector<uint8_t>* out);
// Serialize(double value, std::vector<uint8_t>* out);
// ...
//
// However, if this was to use straight-forward overloading, implicit
// type conversions would lead to ambiguity - e.g., a bool could be
// represented as an int32_t, but it should really be encoded as a bool.
// The template parameterized / specialized structs accomplish this.
//
// SerializerTraits<bool>::Serialize(bool value, std::vector<uint8_t>* out);
// SerializerTraits<int32>::Serialize(int32_t value, std::vector<uint8_t>* out);
// SerializerTraits<double>::Serialize(double value, std::vector<uint8_t>* out);
template <typename T>
struct SerializerTraits {
  // |Serializable| (defined in serializable.h) already knows how to serialize
  // to CBOR, so we can just delegate. This covers domain specific types,
  // protocol::Binary, etc.
  // However, we use duck-typing here, because Exported, which is part of the V8
  // headers also comes with AppendSerialized, and logically it's the same type,
  // but it lives in a different namespace (v8_inspector::protocol::Exported).
  template <
      typename LikeSerializable,
      typename std::enable_if_t<std::is_member_pointer<decltype(
                                    &LikeSerializable::AppendSerialized)>{},
                                int> = 0>
  static void Serialize(const LikeSerializable& value,
                        std::vector<uint8_t>* out) {
    value.AppendSerialized(out);
  }
};

// This covers std::string, which is assumed to be UTF-8.
// The two other string implementations that are used in the protocol bindings:
// - WTF::String, for which the SerializerTraits specialization is located
//   in third_party/blink/renderer/core/inspector/v8-inspector-string.h.
// - v8_inspector::String16, implemented in v8/src/inspector/string-16.h
//   along with its SerializerTraits specialization.
template <>
struct SerializerTraits<std::string> {
  static void Serialize(const std::string& str, std::vector<uint8_t>* out) {
    cbor::EncodeString8(SpanFrom(str), out);
  }
};

template <>
struct SerializerTraits<bool> {
  static void Serialize(bool value, std::vector<uint8_t>* out) {
    out->push_back(value ? cbor::EncodeTrue() : cbor::EncodeFalse());
  }
};

template <>
struct SerializerTraits<int32_t> {
  static void Serialize(int32_t value, std::vector<uint8_t>* out) {
    cbor::EncodeInt32(value, out);
  }
};

template <>
struct SerializerTraits<double> {
  static void Serialize(double value, std::vector<uint8_t>* out) {
    cbor::EncodeDouble(value, out);
  }
};

template <typename T>
struct SerializerTraits<std::vector<T>> {
  static void Serialize(const std::vector<T>& value,
                        std::vector<uint8_t>* out) {
    out->push_back(cbor::EncodeIndefiniteLengthArrayStart());
    for (const T& element : value)
      SerializerTraits<T>::Serialize(element, out);
    out->push_back(cbor::EncodeStop());
  }
};

template <typename T>
struct SerializerTraits<std::unique_ptr<T>> {
  static void Serialize(const std::unique_ptr<T>& value,
                        std::vector<uint8_t>* out) {
    SerializerTraits<T>::Serialize(*value, out);
  }
};

// =============================================================================
// FieldSerializerTraits - Encodes fields of protocol objects in CBOR
// =============================================================================
//
// The generated code (see TypeBuilder_cpp.template) invokes SerializeField,
// which then instantiates the FieldSerializerTraits to emit the appropriate
// existence checks / dereference for the field value. This avoids emitting
// the field name if the value for an optional field isn't set.
template <typename T>
struct FieldSerializerTraits {
  static void Serialize(span<uint8_t> field_name,
                        const T& field_value,
                        std::vector<uint8_t>* out) {
    cbor::EncodeString8(field_name, out);
    SerializerTraits<T>::Serialize(field_value, out);
  }
};

template <typename T>
struct FieldSerializerTraits<detail::PtrMaybe<T>> {
  static void Serialize(span<uint8_t> field_name,
                        const detail::PtrMaybe<T>& field_value,
                        std::vector<uint8_t>* out) {
    if (!field_value.isJust())
      return;
    cbor::EncodeString8(field_name, out);
    SerializerTraits<T>::Serialize(*field_value.fromJust(), out);
  }
};

template <typename T>
struct FieldSerializerTraits<detail::ValueMaybe<T>> {
  static void Serialize(span<uint8_t> field_name,
                        const detail::ValueMaybe<T>& field_value,
                        std::vector<uint8_t>* out) {
    if (!field_value.isJust())
      return;
    cbor::EncodeString8(field_name, out);
    SerializerTraits<T>::Serialize(field_value.fromJust(), out);
  }
};

template <typename T>
void SerializeField(span<uint8_t> field_name,
                    const T& field_value,
                    std::vector<uint8_t>* out) {
  FieldSerializerTraits<T>::Serialize(field_name, field_value, out);
}
}  // namespace v8_crdtp

#endif  // V8_CRDTP_SERIALIZER_TRAITS_H_
