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

#include "chrome/browser/ash/arc/tracing/arc_system_model.h"

#include <optional>
#include <set>

#include "base/strings/string_number_conversions.h"
#include "base/strings/stringprintf.h"

namespace arc {

namespace {

constexpr char kKeyCpu[] = "cpu";
constexpr char kKeyMemory[] = "memory";
constexpr char kKeyName[] = "name";
constexpr char kKeyPid[] = "pid";
constexpr char kKeyThreads[] = "threads";

bool LoadThreads(const base::Value* value,
                 ArcSystemModel::ThreadMap* out_threads) {
  if (!value || !value->is_dict()) {
    return false;
  }

  for (const auto it : value->GetDict()) {
    int tid;
    if (!base::StringToInt(it.first, &tid)) {
      return false;
    }

    if (!it.second.is_dict()) {
      return false;
    }

    const std::string* name = it.second.GetDict().FindString(kKeyName);
    if (!name) {
      return false;
    }
    const std::optional<int> pid = it.second.GetDict().FindInt(kKeyPid);
    if (!pid) {
      return false;
    }

    (*out_threads)[tid] = ArcSystemModel::ThreadInfo(pid.value(), *name);
  }

  return true;
}

base::DictValue SerializeThreads(const ArcSystemModel::ThreadMap& threads) {
  base::DictValue result;

  for (auto& thread_info : threads) {
    base::DictValue entry;
    entry.Set(kKeyPid, base::Value(thread_info.second.pid));
    entry.Set(kKeyName, base::Value(thread_info.second.name));
    result.Set(base::StringPrintf("%d", thread_info.first), std::move(entry));
  }

  return result;
}

template <typename T>
bool CompareByTimestampPred(const T& a, const T& b) {
  return a.timestamp < b.timestamp;
}

}  // namespace

ArcSystemModel::ThreadInfo::ThreadInfo() = default;

ArcSystemModel::ThreadInfo::ThreadInfo(int pid, const std::string& name)
    : pid(pid), name(name) {}

bool ArcSystemModel::ThreadInfo::operator==(const ThreadInfo& other) const {
  return pid == other.pid && name == other.name;
}

ArcSystemModel::ArcSystemModel() = default;

ArcSystemModel::~ArcSystemModel() = default;

void ArcSystemModel::Reset() {
  thread_map_.clear();
  all_cpu_events_.clear();
  memory_events_.clear();
}

void ArcSystemModel::Trim(uint64_t trim_timestamp) {
  const ArcCpuEvent cpu_trim_point(
      trim_timestamp, ArcCpuEvent::Type::kActive /* does not matter */,
      0 /* tid, does not matter */);
  for (auto& cpu_events : all_cpu_events_) {
    if (cpu_events.empty()) {
      continue;
    }
    auto cpu_cut_pos =
        std::lower_bound(cpu_events.begin(), cpu_events.end(), cpu_trim_point,
                         CompareByTimestampPred<ArcCpuEvent>);
    if (cpu_cut_pos == cpu_events.begin()) {
      continue;  // Nothing to trim.
    }
    // Keep the last message for this CPU, that would be clamped to
    // |trim_timestamp|.
    if (cpu_cut_pos == cpu_events.end() ||
        cpu_cut_pos->timestamp != trim_timestamp) {
      --cpu_cut_pos;
      cpu_cut_pos->timestamp = trim_timestamp;
    }
    cpu_events = CpuEvents(cpu_cut_pos, cpu_events.end());
  }

  const ArcValueEvent memory_trim_point(
      trim_timestamp, ArcValueEvent::Type::kMemTotal /* does not matter */,
      0 /* value, does not matter */);
  auto memory_cut_pos = std::upper_bound(
      memory_events_.begin(), memory_events_.end(), memory_trim_point,
      CompareByTimestampPred<ArcValueEvent>);

  // Keep the last message per type, that would be trimmed to |trim_timestamp|.
  ValueEvents trimmed_memory_events;
  std::set<ArcValueEvent::Type> trimmed_types;
  auto scan_memory = memory_cut_pos;
  while (scan_memory != memory_events_.begin()) {
    --scan_memory;
    if (!trimmed_types.count(scan_memory->type)) {
      ArcValueEvent memory_event = *scan_memory;
      memory_event.timestamp = trim_timestamp;
      trimmed_memory_events.insert(trimmed_memory_events.begin(), memory_event);
      trimmed_types.insert(memory_event.type);
    }
  }
  // Add the rest after the trim point.
  trimmed_memory_events.insert(trimmed_memory_events.end(), memory_cut_pos,
                               memory_events_.end());
  memory_events_ = std::move(trimmed_memory_events);
}

void ArcSystemModel::CloseRangeForValueEvents(uint64_t max_timestamp) {
  std::map<ArcValueEvent::Type, std::pair<uint64_t, int>> last_timestamps;
  for (const auto& it : memory_events_) {
    last_timestamps[it.type] = {it.timestamp, it.value};
  }
  for (const auto& it : last_timestamps) {
    if (it.second.first < max_timestamp) {
      memory_events_.emplace_back(max_timestamp, it.first, it.second.second);
    }
  }
}

void ArcSystemModel::CopyFrom(const ArcSystemModel& other) {
  thread_map_ = other.thread_map_;
  all_cpu_events_ = other.all_cpu_events_;
  memory_events_ = other.memory_events_;
}

base::DictValue ArcSystemModel::Serialize() const {
  base::DictValue result;
  result.Set(kKeyThreads, SerializeThreads(thread_map_));
  result.Set(kKeyCpu, SerializeAllCpuEvents(all_cpu_events_));
  result.Set(kKeyMemory, SerializeValueEvents(memory_events_));
  return result;
}

bool ArcSystemModel::Load(const base::Value* root) {
  if (!root || !root->is_dict()) {
    return false;
  }

  if (!LoadThreads(root->GetDict().Find(kKeyThreads), &thread_map_)) {
    return false;
  }

  if (!LoadAllCpuEvents(root->GetDict().Find(kKeyCpu), &all_cpu_events_)) {
    return false;
  }

  if (!LoadValueEvents(root->GetDict().Find(kKeyMemory), &memory_events_)) {
    return false;
  }

  return true;
}

bool ArcSystemModel::operator==(const ArcSystemModel& other) const {
  return thread_map_ == other.thread_map_ &&
         all_cpu_events_ == other.all_cpu_events_ &&
         memory_events_ == other.memory_events_;
}

}  // namespace arc
