/*
 * Copyright (C) 2019 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 SRC_TRACE_PROCESSOR_PROTO_INCREMENTAL_STATE_H_
#define SRC_TRACE_PROCESSOR_PROTO_INCREMENTAL_STATE_H_

#include <stdint.h>

#include <map>
#include <unordered_map>

#include "perfetto/base/optional.h"
#include "perfetto/protozero/proto_decoder.h"
#include "src/trace_processor/trace_blob_view.h"
#include "src/trace_processor/trace_storage.h"

#include "perfetto/trace/track_event/debug_annotation.pbzero.h"
#include "perfetto/trace/track_event/task_execution.pbzero.h"
#include "perfetto/trace/track_event/track_event.pbzero.h"

namespace perfetto {
namespace trace_processor {

// Specialization of member types is forbidden inside their parent class, so
// define the StorageReferences class outside in an internal namespace.
namespace proto_incremental_state_internal {

template <typename MessageType>
struct StorageReferences;

template <>
struct StorageReferences<protos::pbzero::EventCategory> {
  StringId name_id;
};

template <>
struct StorageReferences<protos::pbzero::LegacyEventName> {
  StringId name_id;
};

template <>
struct StorageReferences<protos::pbzero::DebugAnnotationName> {
  StringId name_id;
};

template <>
struct StorageReferences<protos::pbzero::SourceLocation> {
  StringId file_name_id;
  StringId function_name_id;
};

}  // namespace proto_incremental_state_internal

// Stores per-packet-sequence incremental state during trace parsing, such as
// reference timestamps for delta timestamp calculation and interned messages.
class ProtoIncrementalState {
 public:
  template <typename MessageType>
  using StorageReferences =
      proto_incremental_state_internal::StorageReferences<MessageType>;

  // Entry in an interning index, refers to the interned message.
  template <typename MessageType>
  struct InternedDataView {
    InternedDataView(TraceBlobView msg) : message(std::move(msg)) {}

    typename MessageType::Decoder CreateDecoder() {
      return typename MessageType::Decoder(message.data(), message.length());
    }

    TraceBlobView message;

    // If the data in this entry was already stored into the trace storage, this
    // field contains message-type-specific references into the storage which
    // can be used to look up the entry's data (e.g. indexes of interned
    // strings).
    base::Optional<StorageReferences<MessageType>> storage_refs;
  };

  template <typename MessageType>
  using InternedDataMap =
      std::unordered_map<uint32_t, InternedDataView<MessageType>>;

  class PacketSequenceState {
   public:
    int64_t IncrementAndGetTrackEventTimeNs(int64_t delta_ns) {
      PERFETTO_DCHECK(IsTrackEventStateValid());
      track_event_timestamp_ns_ += delta_ns;
      return track_event_timestamp_ns_;
    }

    int64_t IncrementAndGetTrackEventThreadTimeNs(int64_t delta_ns) {
      PERFETTO_DCHECK(IsTrackEventStateValid());
      track_event_thread_timestamp_ns_ += delta_ns;
      return track_event_thread_timestamp_ns_;
    }

    void OnPacketLoss() {
      packet_loss_ = true;
      thread_descriptor_seen_ = false;
    }

    void OnIncrementalStateCleared() { packet_loss_ = false; }

    void SetThreadDescriptor(int32_t pid,
                             int32_t tid,
                             int64_t timestamp_ns,
                             int64_t thread_timestamp_ns) {
      thread_descriptor_seen_ = true;
      pid_ = pid;
      tid_ = tid;
      track_event_timestamp_ns_ = timestamp_ns;
      track_event_thread_timestamp_ns_ = thread_timestamp_ns;
    }

    bool IsIncrementalStateValid() const { return !packet_loss_; }

    bool IsTrackEventStateValid() const {
      return IsIncrementalStateValid() && thread_descriptor_seen_;
    }

    int32_t pid() const { return pid_; }
    int32_t tid() const { return tid_; }

    template <typename MessageType>
    InternedDataMap<MessageType>* GetInternedDataMap();

   private:
    // If true, incremental state on the sequence is considered invalid until we
    // see the next packet with incremental_state_cleared. We assume that we
    // missed some packets at the beginning of the trace.
    bool packet_loss_ = true;

    // We can only consider TrackEvent delta timestamps to be correct after we
    // have observed a thread descriptor (since the last packet loss).
    bool thread_descriptor_seen_ = false;

    // Process/thread ID of the packet sequence. Used as default values for
    // TrackEvents that don't specify a pid/tid override. Only valid while
    // |seen_thread_descriptor_| is true.
    int32_t pid_ = 0;
    int32_t tid_ = 0;

    // Current wall/thread timestamps used as reference for the next TrackEvent
    // delta timestamp.
    int64_t track_event_timestamp_ns_ = 0;
    int64_t track_event_thread_timestamp_ns_ = 0;

    InternedDataMap<protos::pbzero::EventCategory> event_categories_;
    InternedDataMap<protos::pbzero::LegacyEventName> legacy_event_names_;
    InternedDataMap<protos::pbzero::DebugAnnotationName>
        debug_annotation_names_;
    InternedDataMap<protos::pbzero::SourceLocation> source_locations_;
  };

  // Returns the PacketSequenceState for the packet sequence with the given id.
  // If this is a new sequence which we haven't tracked before, initializes and
  // inserts a new PacketSequenceState into the state map.
  PacketSequenceState* GetOrCreateStateForPacketSequence(uint32_t sequence_id) {
    auto& ptr = packet_sequence_states_[sequence_id];
    if (!ptr)
      ptr.reset(new PacketSequenceState());
    return ptr.get();
  }

 private:
  // Stores unique_ptrs to ensure that pointers to a PacketSequenceState remain
  // valid even if the map rehashes.
  std::map<uint32_t, std::unique_ptr<PacketSequenceState>>
      packet_sequence_states_;
};

template <>
inline ProtoIncrementalState::InternedDataMap<protos::pbzero::EventCategory>*
ProtoIncrementalState::PacketSequenceState::GetInternedDataMap<
    protos::pbzero::EventCategory>() {
  return &event_categories_;
}

template <>
inline ProtoIncrementalState::InternedDataMap<protos::pbzero::LegacyEventName>*
ProtoIncrementalState::PacketSequenceState::GetInternedDataMap<
    protos::pbzero::LegacyEventName>() {
  return &legacy_event_names_;
}

template <>
inline ProtoIncrementalState::InternedDataMap<
    protos::pbzero::DebugAnnotationName>*
ProtoIncrementalState::PacketSequenceState::GetInternedDataMap<
    protos::pbzero::DebugAnnotationName>() {
  return &debug_annotation_names_;
}

template <>
inline ProtoIncrementalState::InternedDataMap<protos::pbzero::SourceLocation>*
ProtoIncrementalState::PacketSequenceState::GetInternedDataMap<
    protos::pbzero::SourceLocation>() {
  return &source_locations_;
}

}  // namespace trace_processor
}  // namespace perfetto

#endif  // SRC_TRACE_PROCESSOR_PROTO_INCREMENTAL_STATE_H_