/*
 * Copyright (C) 2022 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.
 */
#include "src/trace_processor/importers/proto/statsd_module.h"

#include "perfetto/ext/base/string_utils.h"
#include "protos/perfetto/trace/statsd/statsd_atom.pbzero.h"
#include "protos/perfetto/trace/trace_packet.pbzero.h"
#include "src/trace_processor/importers/common/slice_tracker.h"
#include "src/trace_processor/importers/common/track_tracker.h"
#include "src/trace_processor/importers/json/json_utils.h"
#include "src/trace_processor/importers/proto/async_track_set_tracker.h"
#include "src/trace_processor/storage/trace_storage.h"
#include "src/trace_processor/trace_sorter.h"
#include "src/trace_processor/util/descriptors.h"

#include "src/trace_processor/importers/atoms.descriptor.h"

namespace perfetto {
namespace trace_processor {
namespace {

constexpr const char* kAtomProtoName = ".android.os.statsd.Atom";

using BoundInserter = ArgsTracker::BoundInserter;

class InserterDelegate : public util::ProtoToArgsParser::Delegate {
 public:
  InserterDelegate(BoundInserter& inserter, TraceStorage& storage)
      : inserter_(inserter), storage_(storage) {}
  ~InserterDelegate() override = default;

  using Key = util::ProtoToArgsParser::Key;

  void AddInteger(const Key& key, int64_t value) override {
    StringId flat_key_id =
        storage_.InternString(base::StringView(key.flat_key));
    StringId key_id = storage_.InternString(base::StringView(key.key));
    Variadic variadic_val = Variadic::Integer(value);
    inserter_.AddArg(flat_key_id, key_id, variadic_val);
  }

  void AddUnsignedInteger(const Key& key, uint64_t value) override {
    StringId flat_key_id =
        storage_.InternString(base::StringView(key.flat_key));
    StringId key_id = storage_.InternString(base::StringView(key.key));
    Variadic variadic_val = Variadic::UnsignedInteger(value);
    inserter_.AddArg(flat_key_id, key_id, variadic_val);
  }

  void AddString(const Key& key, const protozero::ConstChars& value) override {
    StringId flat_key_id =
        storage_.InternString(base::StringView(key.flat_key));
    StringId key_id = storage_.InternString(base::StringView(key.key));
    Variadic variadic_val = Variadic::String(storage_.InternString(value));
    inserter_.AddArg(flat_key_id, key_id, variadic_val);
  }

  void AddString(const Key& key, const std::string& value) override {
    StringId flat_key_id =
        storage_.InternString(base::StringView(key.flat_key));
    StringId key_id = storage_.InternString(base::StringView(key.key));
    Variadic variadic_val =
        Variadic::String(storage_.InternString(base::StringView(value)));
    inserter_.AddArg(flat_key_id, key_id, variadic_val);
  }

  void AddDouble(const Key& key, double value) override {
    StringId flat_key_id =
        storage_.InternString(base::StringView(key.flat_key));
    StringId key_id = storage_.InternString(base::StringView(key.key));
    Variadic variadic_val = Variadic::Real(value);
    inserter_.AddArg(flat_key_id, key_id, variadic_val);
  }

  void AddPointer(const Key& key, const void* value) override {
    StringId flat_key_id =
        storage_.InternString(base::StringView(key.flat_key));
    StringId key_id = storage_.InternString(base::StringView(key.key));
    Variadic variadic_val =
        Variadic::Pointer(reinterpret_cast<uintptr_t>(value));
    inserter_.AddArg(flat_key_id, key_id, variadic_val);
  }

  void AddBoolean(const Key& key, bool value) override {
    StringId flat_key_id =
        storage_.InternString(base::StringView(key.flat_key));
    StringId key_id = storage_.InternString(base::StringView(key.key));
    Variadic variadic_val = Variadic::Boolean(value);
    inserter_.AddArg(flat_key_id, key_id, variadic_val);
  }

  bool AddJson(const Key& key, const protozero::ConstChars& value) override {
    auto json_value = json::ParseJsonString(value);
    if (!json_value) {
      return false;
    }
    return json::AddJsonValueToArgs(*json_value, base::StringView(key.flat_key),
                                    base::StringView(key.key), &storage_,
                                    &inserter_);
  }

  void AddNull(const Key& key) override {
    StringId flat_key_id =
        storage_.InternString(base::StringView(key.flat_key));
    StringId key_id = storage_.InternString(base::StringView(key.key));
    Variadic variadic_val = Variadic::Null();
    inserter_.AddArg(flat_key_id, key_id, variadic_val);
  }

  size_t GetArrayEntryIndex(const std::string& array_key) override {
    base::ignore_result(array_key);
    return 0;
  }

  size_t IncrementArrayEntryIndex(const std::string& array_key) override {
    base::ignore_result(array_key);
    return 0;
  }

  PacketSequenceStateGeneration* seq_state() override { return nullptr; }

 protected:
  InternedMessageView* GetInternedMessageView(uint32_t field_id,
                                              uint64_t iid) override {
    base::ignore_result(field_id);
    base::ignore_result(iid);
    return nullptr;
  }

 private:
  BoundInserter& inserter_;
  TraceStorage& storage_;
};

}  // namespace

using perfetto::protos::pbzero::TracePacket;

PoolAndDescriptor::PoolAndDescriptor(const uint8_t* data,
                                     size_t size,
                                     const char* name) {
  pool_.AddFromFileDescriptorSet(data, size);
  base::Optional<uint32_t> opt_idx = pool_.FindDescriptorIdx(name);
  if (opt_idx.has_value()) {
    descriptor_ = &pool_.descriptors()[opt_idx.value()];
  }
}

PoolAndDescriptor::~PoolAndDescriptor() = default;

StatsdModule::StatsdModule(TraceProcessorContext* context)
    : context_(context),
      pool_(kAtomsDescriptor.data(), kAtomsDescriptor.size(), kAtomProtoName),
      args_parser_(*(pool_.pool())) {
  RegisterForField(TracePacket::kStatsdAtomFieldNumber, context);
}

StatsdModule::~StatsdModule() = default;

void StatsdModule::ParseTracePacketData(const TracePacket::Decoder& decoder,
                                        int64_t ts,
                                        const TracePacketData&,
                                        uint32_t field_id) {
  if (field_id != TracePacket::kStatsdAtomFieldNumber) {
    return;
  }
  const auto& atoms_wrapper =
      protos::pbzero::StatsdAtom::Decoder(decoder.statsd_atom());
  for (auto it = atoms_wrapper.nested(); it; ++it) {
    ParseAtom(ts, *it);
  }
}

void StatsdModule::ParseAtom(int64_t ts, protozero::ConstBytes nested_bytes) {
  // nested_bytes is an Atom proto. We (deliberately) don't generate
  // decoding code for every kind of atom (or the parent Atom proto)
  // and instead use the descriptor to parse the args/name.

  // Atom is a giant oneof of all the possible 'kinds' of atom so here
  // we use the protozero decoder implementation to grab the first
  // field id which we we use to look up the field name:
  protozero::ProtoDecoder nested_decoder(nested_bytes);
  protozero::Field field = nested_decoder.ReadField();
  uint32_t nested_field_id = 0;
  if (field.valid()) {
    nested_field_id = field.id();
  }
  StringId atom_name = GetAtomName(nested_field_id);

  AsyncTrackSetTracker::TrackSetId track_set = InternAsyncTrackSetId();
  TrackId track = context_->async_track_set_tracker->Scoped(track_set, ts, 0);
  base::Optional<SliceId> opt_slice =
      context_->slice_tracker->Scoped(ts, track, kNullStringId, atom_name, 0);
  if (!opt_slice) {
    return;
  }
  SliceId slice = opt_slice.value();
  auto inserter = context_->args_tracker->AddArgsTo(slice);
  InserterDelegate delgate(inserter, *context_->storage.get());
  args_parser_.ParseMessage(nested_bytes, kAtomProtoName,
                            nullptr /* parse all fields */, delgate);
}

StringId StatsdModule::GetAtomName(uint32_t atom_field_id) {
  StringId* cached_name = atom_names_.Find(atom_field_id);
  if (cached_name == nullptr) {
    if (pool_.descriptor() == nullptr) {
      return context_->storage->InternString("Could not load atom descriptor");
    }

    const auto& fields = pool_.descriptor()->fields();
    const auto& field_it = fields.find(atom_field_id);
    if (field_it == fields.end()) {
      return context_->storage->InternString("Unknown atom");
    }

    const FieldDescriptor& field = field_it->second;
    StringId name =
        context_->storage->InternString(base::StringView(field.name()));
    atom_names_[atom_field_id] = name;
    return name;
  }
  return *cached_name;
}

AsyncTrackSetTracker::TrackSetId StatsdModule::InternAsyncTrackSetId() {
  if (!track_set_id_) {
    StringId name = context_->storage->InternString("Statsd Atoms");
    track_set_id_ =
        context_->async_track_set_tracker->InternGlobalTrackSet(name);
  }
  return track_set_id_.value();
}

}  // namespace trace_processor
}  // namespace perfetto
