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

#include "components/performance_manager/graph/graph_impl.h"

#include <string_view>
#include <utility>

#include "base/check_op.h"
#include "base/containers/flat_map.h"
#include "base/containers/flat_set.h"
#include "base/containers/map_util.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/notreached.h"
#include "base/numerics/safe_conversions.h"
#include "components/performance_manager/graph/frame_node_impl.h"
#include "components/performance_manager/graph/node_base.h"
#include "components/performance_manager/graph/page_node_impl.h"
#include "components/performance_manager/graph/process_node_impl.h"
#include "components/performance_manager/graph/system_node_impl.h"
#include "components/performance_manager/graph/worker_node_impl.h"
#include "components/performance_manager/public/graph/node_data_describer.h"
#include "components/performance_manager/public/graph/node_data_describer_registry.h"

namespace ukm {
class UkmEntryBuilder;
}  // namespace ukm

namespace performance_manager {

namespace {

// A unique type ID for this implementation.
const uintptr_t kGraphImplType = reinterpret_cast<uintptr_t>(&kGraphImplType);

base::DictValue DescribeNodeWithDescriber(const NodeDataDescriber& describer,
                                          const Node* node) {
  switch (node->GetNodeType()) {
    case NodeTypeEnum::kFrame:
      return describer.DescribeNodeData(FrameNodeImpl::FromNode(node));
    case NodeTypeEnum::kPage:
      return describer.DescribeNodeData(PageNodeImpl::FromNode(node));
    case NodeTypeEnum::kProcess:
      return describer.DescribeNodeData(ProcessNodeImpl::FromNode(node));
    case NodeTypeEnum::kSystem:
      return describer.DescribeNodeData(SystemNodeImpl::FromNode(node));
    case NodeTypeEnum::kWorker:
      return describer.DescribeNodeData(WorkerNodeImpl::FromNode(node));
  }
  NOTREACHED();
}

class NodeDataDescriberRegistryImpl : public NodeDataDescriberRegistry {
 public:
  ~NodeDataDescriberRegistryImpl() override;

  // NodeDataDescriberRegistry impl:
  void RegisterDescriber(const NodeDataDescriber* describer,
                         std::string_view name) override;
  void UnregisterDescriber(const NodeDataDescriber* describer) override;
  base::DictValue DescribeNodeData(const Node* node) const override;

  size_t size() const {
    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
    return describers_.size();
  }

 private:
  base::flat_map<const NodeDataDescriber*, std::string> describers_
      GUARDED_BY_CONTEXT(sequence_checker_);
  SEQUENCE_CHECKER(sequence_checker_);
};

NodeDataDescriberRegistryImpl::~NodeDataDescriberRegistryImpl() {
  // All describers should have unregistered before the graph is destroyed.
  DCHECK(describers_.empty());
}

void NodeDataDescriberRegistryImpl::RegisterDescriber(
    const NodeDataDescriber* describer,
    std::string_view name) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
#if DCHECK_IS_ON()
  for (const auto& kv : describers_) {
    DCHECK_NE(kv.second, name) << "Name must be unique";
  }
#endif
  bool inserted =
      describers_.insert(std::make_pair(describer, std::string(name))).second;
  DCHECK(inserted);
}

void NodeDataDescriberRegistryImpl::UnregisterDescriber(
    const NodeDataDescriber* describer) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  size_t erased = describers_.erase(describer);
  DCHECK_EQ(1u, erased);
}

base::DictValue NodeDataDescriberRegistryImpl::DescribeNodeData(
    const Node* node) const {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  base::DictValue result;
  for (const auto& [describer, describer_name] : describers_) {
    base::DictValue description = DescribeNodeWithDescriber(*describer, node);
    if (!description.empty()) {
      DCHECK_EQ(nullptr, result.FindDict(describer_name));
      result.Set(describer_name, std::move(description));
    }
  }
  return result;
}

}  // namespace

GraphImpl::GraphImpl() {
  DETACH_FROM_SEQUENCE(sequence_checker_);
}

GraphImpl::~GraphImpl() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  CHECK_EQ(lifecycle_state_, LifecycleState::kTearDownCalled);

  // All graph registered and owned objects should have been cleaned up.
  DCHECK(graph_owned_.empty());
  DCHECK(registered_objects_.empty());

  // All process and frame nodes should have been removed already.
  DCHECK(processes_by_pid_.empty());
  DCHECK(frames_by_id_.empty());

  // All nodes should have been removed.
  for (const NodeSet& nodes : nodes_) {
    DCHECK(nodes.empty());
  }
}

void GraphImpl::SetUp() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  CreateSystemNode();

  execution_context_registry_impl_.SetUp(this);

  CHECK_EQ(lifecycle_state_, LifecycleState::kBeforeSetUp);
  lifecycle_state_ = LifecycleState::kSetUpCalled;
}

void GraphImpl::TearDown() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  // Clean up graph owned objects. This causes their TakeFromGraph callbacks to
  // be invoked, and ideally they clean up any observers they may have, etc.
  graph_owned_.ReleaseObjects(this);

  execution_context_registry_impl_.TearDown(this);

  // At this point, all typed observers should be empty.
  DCHECK(frame_node_observers_.empty());
  DCHECK(page_node_observers_.empty());
  DCHECK(process_node_observers_.empty());
  DCHECK(system_node_observers_.empty());

  // Remove the system node from the graph, this should be the only node left.
  ReleaseSystemNode();

  for (const NodeSet& nodes : nodes_) {
    DCHECK(nodes.empty());
  }

  CHECK_EQ(lifecycle_state_, LifecycleState::kSetUpCalled);
  lifecycle_state_ = LifecycleState::kTearDownCalled;
}

void GraphImpl::AddFrameNodeObserver(FrameNodeObserver* observer) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  frame_node_observers_.AddObserver(observer);
}

void GraphImpl::AddPageNodeObserver(PageNodeObserver* observer) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  page_node_observers_.AddObserver(observer);
}

void GraphImpl::AddProcessNodeObserver(ProcessNodeObserver* observer) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  process_node_observers_.AddObserver(observer);
}

void GraphImpl::AddSystemNodeObserver(SystemNodeObserver* observer) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  system_node_observers_.AddObserver(observer);
}

void GraphImpl::AddWorkerNodeObserver(WorkerNodeObserver* observer) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  worker_node_observers_.AddObserver(observer);
}

void GraphImpl::RemoveFrameNodeObserver(FrameNodeObserver* observer) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  frame_node_observers_.RemoveObserver(observer);
}

void GraphImpl::RemovePageNodeObserver(PageNodeObserver* observer) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  page_node_observers_.RemoveObserver(observer);
}

void GraphImpl::RemoveProcessNodeObserver(ProcessNodeObserver* observer) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  process_node_observers_.RemoveObserver(observer);
}

void GraphImpl::RemoveSystemNodeObserver(SystemNodeObserver* observer) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  system_node_observers_.RemoveObserver(observer);
}

void GraphImpl::RemoveWorkerNodeObserver(WorkerNodeObserver* observer) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  worker_node_observers_.RemoveObserver(observer);
}

void GraphImpl::PassToGraphImpl(std::unique_ptr<GraphOwned> graph_owned) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  graph_owned_.PassObject(std::move(graph_owned), this);
}

std::unique_ptr<GraphOwned> GraphImpl::TakeFromGraph(GraphOwned* graph_owned) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  return graph_owned_.TakeObject(graph_owned, this);
}

void GraphImpl::RegisterObject(GraphRegistered* object) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  registered_objects_.RegisterObject(object);
}

void GraphImpl::UnregisterObject(GraphRegistered* object) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  registered_objects_.UnregisterObject(object);
}

const SystemNode* GraphImpl::GetSystemNode() const {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  DCHECK(system_node_.get());
  return system_node_.get();
}

Graph::NodeSetView<const ProcessNode*> GraphImpl::GetAllProcessNodes() const {
  return NodeSetView<const ProcessNode*>(
      GetNodesOfType(NodeTypeEnum::kProcess));
}

Graph::NodeSetView<const FrameNode*> GraphImpl::GetAllFrameNodes() const {
  return NodeSetView<const FrameNode*>(GetNodesOfType(NodeTypeEnum::kFrame));
}

Graph::NodeSetView<const PageNode*> GraphImpl::GetAllPageNodes() const {
  return NodeSetView<const PageNode*>(GetNodesOfType(NodeTypeEnum::kPage));
}

Graph::NodeSetView<const WorkerNode*> GraphImpl::GetAllWorkerNodes() const {
  return NodeSetView<const WorkerNode*>(GetNodesOfType(NodeTypeEnum::kWorker));
}

bool GraphImpl::HasOnlySystemNode() const {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  if (!GetNodesOfType(NodeTypeEnum::kProcess).empty() ||
      !GetNodesOfType(NodeTypeEnum::kPage).empty() ||
      !GetNodesOfType(NodeTypeEnum::kFrame).empty() ||
      !GetNodesOfType(NodeTypeEnum::kWorker).empty()) {
    return false;
  }

  const NodeSet& system_nodes = GetNodesOfType(NodeTypeEnum::kSystem);
  return system_nodes.size() == 1 &&
         *system_nodes.begin() == GetSystemNodeImpl();
}

ukm::UkmRecorder* GraphImpl::GetUkmRecorder() const {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  return ukm_recorder();
}

NodeDataDescriberRegistry* GraphImpl::GetNodeDataDescriberRegistry() const {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  if (!describer_registry_) {
    describer_registry_ = std::make_unique<NodeDataDescriberRegistryImpl>();
  }

  return describer_registry_.get();
}

uintptr_t GraphImpl::GetImplType() const {
  return kGraphImplType;
}

const void* GraphImpl::GetImpl() const {
  return this;
}

#if DCHECK_IS_ON()
bool GraphImpl::IsOnGraphSequence() const {
  return sequence_checker_.CalledOnValidSequence();
}
#endif

GraphRegistered* GraphImpl::GetRegisteredObject(uintptr_t type_id) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  return registered_objects_.GetRegisteredObject(type_id);
}

// static
GraphImpl* GraphImpl::FromGraph(const Graph* graph) {
  CHECK_EQ(kGraphImplType, graph->GetImplType());
  return reinterpret_cast<GraphImpl*>(const_cast<void*>(graph->GetImpl()));
}

bool GraphImpl::NodeInGraph(const NodeBase* node) const {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  const NodeSet& nodes = GetNodesOfType(node->GetNodeType());
  return nodes.contains(node->ToNode());
}

bool GraphImpl::NodeEdgesArePublic(const NodeBase* node) const {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  switch (GetNodeState(node)) {
    case NodeState::kNotInGraph:
    case NodeState::kInitializingNotInGraph:
      // Hide node connections until edges are initialized.
      return false;
    case NodeState::kInitializingEdges:
    case NodeState::kUninitializingEdges:
      // InitializingFrameNodeObservers are called during this state, and must
      // see the edges.
      return true;
    case NodeState::kJoiningGraph:
    case NodeState::kActiveInGraph:
    case NodeState::kLeavingGraph:
      return true;
    case NodeState::kLeftGraph:
      // Hide node connections during teardown.
      return false;
  }
  NOTREACHED();
}

ProcessNodeImpl* GraphImpl::GetProcessNodeByPid(base::ProcessId pid) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  return base::FindPtrOrNull(processes_by_pid_, pid);
}

FrameNodeImpl* GraphImpl::GetFrameNodeById(
    RenderProcessHostId render_process_id,
    int render_frame_id) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  return base::FindPtrOrNull(
      frames_by_id_, ProcessAndFrameId(render_process_id, render_frame_id));
}

Graph::NodeSetView<ProcessNodeImpl*> GraphImpl::GetAllProcessNodeImpls() const {
  return NodeSetView<ProcessNodeImpl*>(GetNodesOfType(NodeTypeEnum::kProcess));
}

Graph::NodeSetView<FrameNodeImpl*> GraphImpl::GetAllFrameNodeImpls() const {
  return NodeSetView<FrameNodeImpl*>(GetNodesOfType(NodeTypeEnum::kFrame));
}

Graph::NodeSetView<PageNodeImpl*> GraphImpl::GetAllPageNodeImpls() const {
  return NodeSetView<PageNodeImpl*>(GetNodesOfType(NodeTypeEnum::kPage));
}

Graph::NodeSetView<WorkerNodeImpl*> GraphImpl::GetAllWorkerNodeImpls() const {
  return NodeSetView<WorkerNodeImpl*>(GetNodesOfType(NodeTypeEnum::kWorker));
}

void GraphImpl::AddNewNode(NodeBase* new_node) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  DCHECK(!node_in_transition_);

  // Add the node to the graph.
  NodeSet& nodes = GetNodesOfType(new_node->GetNodeType());
  auto it = nodes.insert(new_node->ToNode());
  DCHECK(it.second);  // Inserted successfully

  // Advance the node through its lifecycle until it is active in the graph. See
  // NodeBase and NodeState for full details of the lifecycle.
  node_in_transition_ = new_node;
  node_in_transition_state_ = NodeState::kNotInGraph;
  new_node->SetGraphPointer(this);
  node_in_transition_state_ = NodeState::kInitializingNotInGraph;
  new_node->OnInitializingProperties();
  DispatchBeforeNodeAddedNotifications(new_node);
  node_in_transition_state_ = NodeState::kInitializingEdges;
  new_node->OnInitializingEdges();
  node_in_transition_state_ = NodeState::kJoiningGraph;
  DispatchNodeAddedNotifications(new_node);
  node_in_transition_ = nullptr;
  node_in_transition_state_ = NodeState::kNotInGraph;
  new_node->OnAfterJoiningGraph();
}

void GraphImpl::RemoveNode(NodeBase* node) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  DCHECK(NodeInGraph(node));
  DCHECK_EQ(this, node->graph());
  DCHECK(!node_in_transition_);

  // Walk the node through the latter half of its lifecycle. See NodeBase and
  // NodeState for full details of the lifecycle.
  node->OnBeforeLeavingGraph();
  node_in_transition_ = node;
  node_in_transition_state_ = NodeState::kLeavingGraph;
  DispatchBeforeNodeRemovedNotifications(node);
  node_in_transition_state_ = NodeState::kUninitializingEdges;
  node->OnUninitializingEdges();
  node_in_transition_state_ = NodeState::kLeftGraph;
  DispatchNodeRemovedNotifications(node);
  node->CleanUpNodeState();
  node->ClearGraphPointer();
  node_in_transition_ = nullptr;
  node_in_transition_state_ = NodeState::kNotInGraph;

  // Remove the node itself.
  NodeSet& nodes = GetNodesOfType(node->GetNodeType());
  size_t erased = nodes.erase(node->ToNode());
  DCHECK_EQ(1u, erased);
}

size_t GraphImpl::NodeDataDescriberCountForTesting() const {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  if (!describer_registry_) {
    return 0;
  }
  auto* registry = static_cast<const NodeDataDescriberRegistryImpl*>(
      describer_registry_.get());
  return registry->size();
}

Graph::NodeSet& GraphImpl::GetNodesOfType(NodeTypeEnum node_type) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  return nodes_.at(base::strict_cast<size_t>(node_type));
}

const Graph::NodeSet& GraphImpl::GetNodesOfType(NodeTypeEnum node_type) const {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  return nodes_.at(base::strict_cast<size_t>(node_type));
}

NodeState GraphImpl::GetNodeState(const NodeBase* node) const {
  DCHECK_EQ(this, node->graph());
  // If this is a transitioning node (being added to or removed from the graph)
  // then return the appropriate state.
  if (node == node_in_transition_) {
    return node_in_transition_state_;
  }
  // Otherwise, this is a node at steady state.
  return NodeState::kActiveInGraph;
}

template <>
const GraphImpl::ObserverList<FrameNodeObserver>& GraphImpl::GetObservers()
    const {
  return frame_node_observers_;
}

template <>
const GraphImpl::ObserverList<PageNodeObserver>& GraphImpl::GetObservers()
    const {
  return page_node_observers_;
}

template <>
const GraphImpl::ObserverList<ProcessNodeObserver>& GraphImpl::GetObservers()
    const {
  return process_node_observers_;
}

template <>
const GraphImpl::ObserverList<SystemNodeObserver>& GraphImpl::GetObservers()
    const {
  return system_node_observers_;
}

template <>
const GraphImpl::ObserverList<WorkerNodeObserver>& GraphImpl::GetObservers()
    const {
  return worker_node_observers_;
}

GraphImpl::ProcessAndFrameId::ProcessAndFrameId(
    RenderProcessHostId render_process_id,
    int render_frame_id)
    : render_process_id(render_process_id), render_frame_id(render_frame_id) {}

bool GraphImpl::ProcessAndFrameId::operator<(
    const ProcessAndFrameId& other) const {
  return std::tie(render_process_id, render_frame_id) <
         std::tie(other.render_process_id, other.render_frame_id);
}

GraphImpl::ProcessAndFrameId::~ProcessAndFrameId() = default;

GraphImpl::ProcessAndFrameId::ProcessAndFrameId(
    const GraphImpl::ProcessAndFrameId& other) = default;

GraphImpl::ProcessAndFrameId& GraphImpl::ProcessAndFrameId::operator=(
    const GraphImpl::ProcessAndFrameId& other) = default;

void GraphImpl::DispatchBeforeNodeAddedNotifications(NodeBase* node) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  CHECK_EQ(node->GetNodeState(), NodeState::kInitializingNotInGraph);

  // This handles the strongly typed observer notifications.
  switch (node->GetNodeType()) {
    case NodeTypeEnum::kFrame: {
      auto* frame_node = FrameNodeImpl::FromNodeBase(node);
      for (auto& observer : frame_node_observers_) {
        observer.OnBeforeFrameNodeAdded(
            frame_node, frame_node->parent_frame_node(),
            frame_node->page_node(), frame_node->process_node(),
            frame_node->parent_or_outer_document_or_embedder());
      }
      return;
    }
    case NodeTypeEnum::kPage: {
      auto* page_node = PageNodeImpl::FromNodeBase(node);
      for (auto& observer : page_node_observers_) {
        observer.OnBeforePageNodeAdded(page_node);
      }
      return;
    }
    case NodeTypeEnum::kProcess: {
      auto* process_node = ProcessNodeImpl::FromNodeBase(node);
      for (auto& observer : process_node_observers_) {
        observer.OnBeforeProcessNodeAdded(process_node);
      }
      return;
    }
    case NodeTypeEnum::kSystem:
      // Do nothing.
      return;
    case NodeTypeEnum::kWorker: {
      auto* worker_node = WorkerNodeImpl::FromNodeBase(node);
      for (auto& observer : worker_node_observers_) {
        observer.OnBeforeWorkerNodeAdded(worker_node,
                                         worker_node->process_node());
      }
      return;
    }
  }
  NOTREACHED();
}

void GraphImpl::DispatchNodeAddedNotifications(NodeBase* node) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  CHECK_EQ(node->GetNodeState(), NodeState::kJoiningGraph);

  // This handles the strongly typed observer notifications.
  switch (node->GetNodeType()) {
    case NodeTypeEnum::kFrame: {
      auto* frame_node = FrameNodeImpl::FromNodeBase(node);
      for (auto& observer : frame_node_observers_) {
        observer.OnFrameNodeAdded(frame_node);
      }
      return;
    }
    case NodeTypeEnum::kPage: {
      auto* page_node = PageNodeImpl::FromNodeBase(node);
      for (auto& observer : page_node_observers_) {
        observer.OnPageNodeAdded(page_node);
      }
      return;
    }
    case NodeTypeEnum::kProcess: {
      auto* process_node = ProcessNodeImpl::FromNodeBase(node);
      for (auto& observer : process_node_observers_) {
        observer.OnProcessNodeAdded(process_node);
      }
      return;
    }
    case NodeTypeEnum::kSystem:
      // Do nothing.
      return;
    case NodeTypeEnum::kWorker: {
      auto* worker_node = WorkerNodeImpl::FromNodeBase(node);
      for (auto& observer : worker_node_observers_) {
        observer.OnWorkerNodeAdded(worker_node);
      }
      return;
    }
  }
  NOTREACHED();
}

void GraphImpl::DispatchBeforeNodeRemovedNotifications(NodeBase* node) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  CHECK_EQ(node->GetNodeState(), NodeState::kLeavingGraph);

  switch (node->GetNodeType()) {
    case NodeTypeEnum::kFrame: {
      auto* frame_node = FrameNodeImpl::FromNodeBase(node);
      for (auto& observer : frame_node_observers_) {
        observer.OnBeforeFrameNodeRemoved(frame_node);
      }
      return;
    }
    case NodeTypeEnum::kPage: {
      auto* page_node = PageNodeImpl::FromNodeBase(node);
      for (auto& observer : page_node_observers_) {
        observer.OnBeforePageNodeRemoved(page_node);
      }
      return;
    }
    case NodeTypeEnum::kProcess: {
      auto* process_node = ProcessNodeImpl::FromNodeBase(node);
      for (auto& observer : process_node_observers_) {
        observer.OnBeforeProcessNodeRemoved(process_node);
      }
      return;
    }
    case NodeTypeEnum::kSystem:
      // Do nothing.
      return;
    case NodeTypeEnum::kWorker: {
      auto* worker_node = WorkerNodeImpl::FromNodeBase(node);
      for (auto& observer : worker_node_observers_) {
        observer.OnBeforeWorkerNodeRemoved(worker_node);
      }
      return;
    }
  }
  NOTREACHED();
}

void GraphImpl::DispatchNodeRemovedNotifications(NodeBase* node) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  CHECK_EQ(node->GetNodeState(), NodeState::kLeftGraph);

  // This handles the strongly typed observer notifications.
  switch (node->GetNodeType()) {
    case NodeTypeEnum::kFrame: {
      auto* frame_node = FrameNodeImpl::FromNodeBase(node);
      for (auto& observer : frame_node_observers_) {
        observer.OnFrameNodeRemoved(
            frame_node, frame_node->parent_frame_node(),
            frame_node->page_node(), frame_node->process_node(),
            frame_node->parent_or_outer_document_or_embedder());
      }
      return;
    }
    case NodeTypeEnum::kPage: {
      auto* page_node = PageNodeImpl::FromNodeBase(node);
      for (auto& observer : page_node_observers_) {
        observer.OnPageNodeRemoved(page_node);
      }
      return;
    }
    case NodeTypeEnum::kProcess: {
      auto* process_node = ProcessNodeImpl::FromNodeBase(node);
      for (auto& observer : process_node_observers_) {
        observer.OnProcessNodeRemoved(process_node);
      }
      return;
    }
    case NodeTypeEnum::kSystem:
      // Do nothing.
      return;
    case NodeTypeEnum::kWorker: {
      auto* worker_node = WorkerNodeImpl::FromNodeBase(node);
      for (auto& observer : worker_node_observers_) {
        observer.OnWorkerNodeRemoved(worker_node, worker_node->process_node());
      }
      return;
    }
  }
  NOTREACHED();
}

int64_t GraphImpl::GetNextNodeSerializationId() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  return ++current_node_serialization_id_;
}

void GraphImpl::BeforeProcessPidChange(ProcessNodeImpl* process,
                                       base::ProcessId new_pid) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  // On Windows, PIDs are aggressively reused, and because not all process
  // creation/death notifications are synchronized, it's possible for more than
  // one process node to have the same PID. To handle this, the second and
  // subsequent registration override earlier registrations, while
  // unregistration will only unregister the current holder of the PID.
  if (process->GetProcessId() != base::kNullProcessId) {
    auto it = processes_by_pid_.find(process->GetProcessId());
    if (it != processes_by_pid_.end() && it->second == process) {
      processes_by_pid_.erase(it);
    }
  }
  if (new_pid != base::kNullProcessId) {
    processes_by_pid_[new_pid] = process;
  }
}

void GraphImpl::RegisterFrameNodeForId(RenderProcessHostId render_process_id,
                                       int render_frame_id,
                                       FrameNodeImpl* frame_node) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  auto insert_result = frames_by_id_.insert(
      {ProcessAndFrameId(render_process_id, render_frame_id), frame_node});
  DCHECK(insert_result.second);
}

void GraphImpl::UnregisterFrameNodeForId(RenderProcessHostId render_process_id,
                                         int render_frame_id,
                                         FrameNodeImpl* frame_node) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  const ProcessAndFrameId process_and_frame_id(render_process_id,
                                               render_frame_id);
  DCHECK_EQ(frames_by_id_.find(process_and_frame_id)->second, frame_node);
  frames_by_id_.erase(process_and_frame_id);
}

void GraphImpl::CreateSystemNode() {
  CHECK(!system_node_);
  // Create the singleton system node instance. Ownership is taken by the
  // graph.
  system_node_ = std::make_unique<SystemNodeImpl>();
  AddNewNode(system_node_.get());
}

void GraphImpl::ReleaseSystemNode() {
  CHECK(system_node_);
  RemoveNode(system_node_.get());
  system_node_.reset();
}

}  // namespace performance_manager
