/*
 * Copyright (C) 2021 The Android Open Source Project
 *
 * 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
 *
 *      http://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 INCLUDE_PERFETTO_TRACING_TRACED_PROTO_H_
#define INCLUDE_PERFETTO_TRACING_TRACED_PROTO_H_

#include "perfetto/base/template_util.h"
#include "perfetto/protozero/field_writer.h"
#include "perfetto/protozero/proto_utils.h"
#include "perfetto/tracing/traced_value.h"

namespace perfetto {
class EventContext;
namespace internal {
template <typename FieldMetadata,
          bool is_message,
          protozero::proto_utils::RepetitionType repetition_type>
struct TypedProtoWriterImpl;
}

// A Wrapper around a protozero message to allow C++ classes to specify how it
// should be serialised into the trace:
//
// class Foo {
//  public:
//   void WriteIntoTrace(perfetto::TracedProto<pbzero::Foo> message) {
//     message->set_int_field(int_field_);
//   }
// };
//
// This class also exposes EventContext, e.g. to enable data interning.
//
// NOTE: the functionality below is not ready yet.
// TODO(altimin): Make the interop below possible.
// TracedProto also provides a seamless integration with writing untyped
// values via TracedValue / TracedDictionary / TracedArray:
//
// - TracedValue can be converted to a TracedProto, either by calling
//   TracedValue::WriteProto<T>() or implicitly.
// - If a proto message has a repeating DebugAnnotation debug_annotations
//   field, it can be filled using the TracedDictionary obtained from
//   TracedProto::AddDebugAnnotations.
template <typename MessageType>
class TracedProto {
 public:
  // implicit
  TracedProto(TracedValue&& value)
      : TracedProto(std::move(value).WriteProto<MessageType>()) {}
  ~TracedProto() = default;

  TracedProto(const TracedProto&) = delete;
  TracedProto& operator=(const TracedProto&) = delete;
  TracedProto& operator=(TracedProto&&) = delete;
  TracedProto(TracedProto&&) = default;

  MessageType* operator->() const { return message_; }

  MessageType* message() { return message_; }

  // Write additional untyped values into the same context, which is useful
  // when a given C++ class has a typed representation, but also either has
  // members which can only be written into an untyped context (e.g. they are
  // autogenerated) or it's desirable to have a way to quickly extend the
  // trace representation of this class (e.g. for debugging).
  //
  // The usage of the returned TracedDictionary should not be interleaved with
  // writing into |message| as this results in an inefficient proto layout. To
  // enforce this, AddDebugAnnotations should be called on TracedProto&&, i.e.
  // std::move(message).AddDebugAnnotations().
  //
  // This requires a 'repeated DebugAnnotations debug_annotations' field in
  // MessageType.
  template <typename Check = void>
  TracedDictionary AddDebugAnnotations() && {
    static_assert(
        std::is_base_of<
            protozero::proto_utils::FieldMetadataBase,
            typename MessageType::FieldMetadata_DebugAnnotations>::value,
        "This message does not have a |debug_annotations| field. Please add a"
        "'repeated perfetto.protos.DebugAnnotation debug_annnotations = N;' "
        "field to your message.");
    return TracedDictionary(message_, MessageType::kDebugAnnotations, context_,
                            nullptr);
  }

  // Start writing a single entry corresponding to the given |field| and return
  // TracedProto should be used to populate this further.
  // This method requires |field|'s type to be a nested message, but both
  // repeated and non-repeated complex fields are supported.
  template <typename FieldMetadata>
  TracedProto<typename FieldMetadata::cpp_field_type> WriteNestedMessage(
      protozero::proto_utils::internal::FieldMetadataHelper<FieldMetadata>) {
    static_assert(std::is_base_of<MessageType,
                                  typename FieldMetadata::message_type>::value,
                  "Field should belong to the current message");
    static_assert(
        FieldMetadata::kProtoFieldType ==
            protozero::proto_utils::ProtoSchemaType::kMessage,
        "AddItem() can be used only for nested message fields. To write a "
        "primitive field, use traced_proto->set_field() or traced_proto.Set()");
    return Wrap(
        message_->template BeginNestedMessage<
            typename FieldMetadata::cpp_field_type>(FieldMetadata::kFieldId));
  }

  // Write a given |value| into proto  as a new |field| of the current message.
  // This method supports both nested messages and primitive types (i.e. int or
  // string), but requires the |field| to be non-repeateable (i.e. optional).
  // For repeatable fields, AppendValue or AppendFrom should be used.
  template <typename FieldMetadata, typename ValueType>
  void Set(protozero::proto_utils::internal::FieldMetadataHelper<FieldMetadata>,
           ValueType&& value) {
    static_assert(std::is_base_of<MessageType,
                                  typename FieldMetadata::message_type>::value,
                  "Field should belong to the current message");
    static_assert(
        FieldMetadata::kRepetitionType ==
            protozero::proto_utils::RepetitionType::kNotRepeated,
        "Set() can't be used with repeated fields due to ambiguity between "
        "writing |value| as a single entry or treating |value| as a container "
        "and writing all contained items as multiple entries. Please use "
        "dedicated AppendValue() or AppendFrom() methods to differentiate "
        "between "
        "these two situations");

    internal::TypedProtoWriterImpl<
        FieldMetadata,
        FieldMetadata::kProtoFieldType ==
            protozero::proto_utils::ProtoSchemaType::kMessage,
        protozero::proto_utils::RepetitionType::kNotRepeated>::
        Write(*this, std::forward<ValueType>(value));
  }

  // Write a given |value| a single entry into the repeated |field| of the
  // current message. If the field is not repeated, Set() should be used
  // instead.
  template <typename FieldMetadata, typename ValueType>
  void AppendValue(
      protozero::proto_utils::internal::FieldMetadataHelper<FieldMetadata>,
      ValueType&& value) {
    static_assert(std::is_base_of<MessageType,
                                  typename FieldMetadata::message_type>::value,
                  "Field should belong to the current message");
    static_assert(
        FieldMetadata::kRepetitionType ==
            protozero::proto_utils::RepetitionType::kRepeatedNotPacked,
        "Append*() methods can be used only with repeated fields. "
        "Please use Set() for non-repeated");

    // Write a single value into a given repeated field by explicitly passing
    // "kNotRepeated" to the TypedProtoWriterImpl.
    internal::TypedProtoWriterImpl<
        FieldMetadata,
        FieldMetadata::kProtoFieldType ==
            protozero::proto_utils::ProtoSchemaType::kMessage,
        protozero::proto_utils::RepetitionType::kNotRepeated>::
        Write(*this, std::forward<ValueType>(value));
  }

  // Write a given |value| as a set of entries into the repeated |field| of the
  // current message. If the field is not repeated, Set() should be used
  // instead.
  template <typename FieldMetadata, typename ValueType>
  void AppendFrom(
      protozero::proto_utils::internal::FieldMetadataHelper<FieldMetadata>,
      ValueType&& value) {
    static_assert(std::is_base_of<MessageType,
                                  typename FieldMetadata::message_type>::value,
                  "Field should belong to the current message");
    static_assert(
        FieldMetadata::kRepetitionType ==
            protozero::proto_utils::RepetitionType::kRepeatedNotPacked,
        "Append*() methods can be used only with repeated fields. "
        "Please use Set() for non-repeated");

    internal::TypedProtoWriterImpl<
        FieldMetadata,
        FieldMetadata::kProtoFieldType ==
            protozero::proto_utils::ProtoSchemaType::kMessage,
        protozero::proto_utils::RepetitionType::kRepeatedNotPacked>::
        Write(*this, std::forward<ValueType>(value));
  }

  // Write a nested message into a field according to the provided metadata.
  // TODO(altimin): Replace the current usages in Chrome with the functions
  // above and make these methods private.
  template <typename FieldMetadata>
  TracedProto<typename FieldMetadata::cpp_field_type> WriteNestedMessage() {
    return WriteNestedMessage(
        protozero::proto_utils::internal::FieldMetadataHelper<FieldMetadata>());
  }

 private:
  friend class EventContext;
  friend class TracedValue;
  // Allow TracedProto<Foo> to create TracedProto<Bar>.
  template <typename T>
  friend class TracedProto;

  // Wraps a raw protozero message using the same context as the current object.
  template <typename ChildMessageType>
  TracedProto<ChildMessageType> Wrap(ChildMessageType* message) {
    return TracedProto<ChildMessageType>(message, context_);
  }

  // Context might be null here when writing typed message which is
  // nested into untyped legacy trace event macro argument.
  // TODO(altimin): Turn this into EventContext& when this case is eliminated
  // and expose it in public API.
  EventContext* context() const { return context_; }

  TracedProto(MessageType* message, EventContext* context)
      : message_(message), context_(context) {}

  MessageType* const message_;
  EventContext* context_;
};

template <typename MessageType, typename ValueType>
void WriteIntoTracedProto(TracedProto<MessageType> message, ValueType&& value);

namespace internal {

template <typename FieldMetadata,
          bool is_message,
          protozero::proto_utils::RepetitionType repetition_type>
struct TypedProtoWriterImpl;

// Simple non-repeated field.
template <typename FieldMetadata>
struct TypedProtoWriterImpl<
    FieldMetadata,
    /*is_message=*/false,
    protozero::proto_utils::RepetitionType::kNotRepeated> {
  template <typename Proto, typename ValueType>
  static void Write(TracedProto<Proto>& context, ValueType&& value) {
    protozero::internal::FieldWriter<FieldMetadata::kProtoFieldType>::Append(
        *context.message(), FieldMetadata::kFieldId, value);
  }
};

// Simple repeated non-packed field.
template <typename FieldMetadata>
struct TypedProtoWriterImpl<
    FieldMetadata,
    /*is_message=*/false,
    protozero::proto_utils::RepetitionType::kRepeatedNotPacked> {
  template <typename Proto, typename ValueType>
  static void Write(TracedProto<Proto>& context, ValueType&& value) {
    for (auto&& item : value) {
      protozero::internal::FieldWriter<FieldMetadata::kProtoFieldType>::Append(
          *context.message(), FieldMetadata::kFieldId, item);
    }
  }
};

// Nested repeated non-packed field.
template <typename FieldMetadata>
struct TypedProtoWriterImpl<
    FieldMetadata,
    /*is_message=*/true,
    protozero::proto_utils::RepetitionType::kNotRepeated> {
  template <typename Proto, typename ValueType>
  static void Write(TracedProto<Proto>& context, ValueType&& value) {
    WriteIntoTracedProto(context.template WriteNestedMessage<FieldMetadata>(),
                         std::forward<ValueType>(value));
  }
};

// Nested repeated non-packed field.
template <typename FieldMetadata>
struct TypedProtoWriterImpl<
    FieldMetadata,
    /*is_message=*/true,
    protozero::proto_utils::RepetitionType::kRepeatedNotPacked> {
  template <typename Proto, typename ValueType>
  static void Write(TracedProto<Proto>& context, ValueType&& value) {
    for (auto&& item : value) {
      WriteIntoTracedProto(context.template WriteNestedMessage<FieldMetadata>(),
                           item);
    }
  }
};

constexpr int kMaxWriteTracedProtoImplPriority = 1;

// If perfetto::TraceFormatTraits<T>::WriteIntoTrace(TracedProto<MessageType>,
// T) is available, use it.
template <typename MessageType, typename T>
decltype(TraceFormatTraits<base::remove_cvref_t<T>>::WriteIntoTrace(
             std::declval<TracedProto<MessageType>>(),
             std::declval<T>()),
         void())
WriteIntoTracedProtoImpl(base::priority_tag<1>,
                         TracedProto<MessageType> message,
                         T&& value) {
  TraceFormatTraits<base::remove_cvref_t<T>>::WriteIntoTrace(
      std::move(message), std::forward<T>(value));
}

// If T has WriteIntoTrace(TracedProto<MessageType>) method, use it.
template <typename MessageType, typename T>
decltype(
    std::declval<T>().WriteIntoTrace(std::declval<TracedProto<MessageType>>()),
    void())
WriteIntoTracedProtoImpl(base::priority_tag<0>,
                         TracedProto<MessageType> message,
                         T&& value) {
  value.WriteIntoTrace(std::move(message));
}

// TypedProtoWriter takes the protozero message (TracedProto<MessageType>),
// field description (FieldMetadata) and value and writes the given value
// into the given field of the given protozero message.
//
// This is primarily used for inline writing of typed messages:
// TRACE_EVENT(..., pbzero::Message:kField, value);
//
// Ideally we would use a function here and not a struct, but passing template
// arguments directly to the function (e.g. foo<void>()) isn't supported until
// C++20, so we have to use a helper struct here.
template <typename FieldMetadata>
struct TypedProtoWriter {
 private:
  using ProtoSchemaType = protozero::proto_utils::ProtoSchemaType;
  using RepetitionType = protozero::proto_utils::RepetitionType;

  static_assert(FieldMetadata::kRepetitionType !=
                    RepetitionType::kRepeatedPacked,
                "writing packed fields isn't supported yet");

  template <bool is_message, RepetitionType repetition_type>
  struct Writer;

 public:
  template <typename Proto, typename ValueType>
  static void Write(TracedProto<Proto>& context, ValueType&& value) {
    TypedProtoWriterImpl<
        FieldMetadata,
        FieldMetadata::kProtoFieldType == ProtoSchemaType::kMessage,
        FieldMetadata::kRepetitionType>::Write(context,
                                               std::forward<ValueType>(value));
  }
};

}  // namespace internal

// Helper template to determine if a given type can be passed to
// perfetto::WriteIntoTracedProto. These templates will fail to resolve if the
// class does not have necesary support, so they are useful for SFINAE and for
// producing helpful compiler error messages.
template <typename MessageType, typename ValueType, typename Result = void>
using check_traced_proto_support_t =
    decltype(internal::WriteIntoTracedProtoImpl(
        std::declval<
            base::priority_tag<internal::kMaxWriteTracedProtoImplPriority>>(),
        std::declval<TracedProto<MessageType>>(),
        std::declval<ValueType>()));

// check_traced_proto_support<MessageType, T, V>::type is defined (and equal to
// V) iff T supports being passed to WriteIntoTracedProto together with
// TracedProto<MessageType>. See the comment in traced_value_forward.h for more
// details.
template <typename MessageType, typename ValueType, class Result>
struct check_traced_proto_support<
    MessageType,
    ValueType,
    Result,
    check_traced_proto_support_t<MessageType, ValueType, Result>> {
  static constexpr bool value = true;
  using type = Result;
};

template <typename MessageType, typename ValueType>
void WriteIntoTracedProto(TracedProto<MessageType> message, ValueType&& value) {
  // TODO(altimin): Add a URL to the documentation and a list of common failure
  // patterns.
  static_assert(
      std::is_same<check_traced_proto_support_t<MessageType, ValueType>,
                   void>::value,
      "The provided type does not support being serialised into the "
      "provided protozero message. Please see the comment in traced_proto.h "
      "for more details.");

  internal::WriteIntoTracedProtoImpl(
      base::priority_tag<internal::kMaxWriteTracedProtoImplPriority>(),
      std::move(message), std::forward<ValueType>(value));
}

template <typename MessageType, typename FieldMetadataType, typename ValueType>
void WriteTracedProtoField(
    TracedProto<MessageType>& message,
    protozero::proto_utils::internal::FieldMetadataHelper<FieldMetadataType>,
    ValueType&& value) {
  static_assert(
      std::is_base_of<protozero::proto_utils::FieldMetadataBase,
                      FieldMetadataType>::value,
      "Field name should be a protozero::internal::FieldMetadata<...>");
  static_assert(
      std::is_base_of<MessageType,
                      typename FieldMetadataType::message_type>::value,
      "Field's parent type should match the context.");

  internal::TypedProtoWriter<FieldMetadataType>::Write(
      message, std::forward<ValueType>(value));
}

}  // namespace perfetto

#endif  // INCLUDE_PERFETTO_TRACING_TRACED_PROTO_H_
