// 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.

#ifndef COMPONENTS_PERFORMANCE_MANAGER_GRAPH_GRAPH_IMPL_H_
#define COMPONENTS_PERFORMANCE_MANAGER_GRAPH_GRAPH_IMPL_H_

#include <stdint.h>

#include <array>
#include <map>
#include <memory>

#include "base/memory/raw_ptr.h"
#include "base/observer_list.h"
#include "base/process/process_handle.h"
#include "base/sequence_checker.h"
#include "components/performance_manager/execution_context/execution_context_registry_impl.h"
#include "components/performance_manager/owned_objects.h"
#include "components/performance_manager/public/graph/graph.h"
#include "components/performance_manager/public/graph/graph_registered.h"
#include "components/performance_manager/public/graph/node_attached_data.h"
#include "components/performance_manager/public/graph/node_state.h"
#include "components/performance_manager/public/graph/node_type.h"
#include "components/performance_manager/public/render_process_host_id.h"
#include "components/performance_manager/registered_objects.h"
#include "services/metrics/public/cpp/ukm_recorder.h"

namespace performance_manager {

class FrameNodeImpl;
class Node;
class NodeBase;
class PageNodeImpl;
class ProcessNodeImpl;
class SystemNodeImpl;
class WorkerNodeImpl;

// Represents a graph of the nodes representing a single browser. Maintains a
// set of nodes that can be retrieved in different ways, some indexed. Keeps
// a list of observers that are notified of node addition and removal.
class GraphImpl : public Graph {
 public:
  // An ObserverList that DCHECK's if any observers are still in it when the
  // graph is deleted. `allow_reentrancy` is true because some observers update
  // node properties that also trigger observers. (For example
  // FrameVisibilityVoter::OnFrameVisibilityChanged() can call
  // FrameNodeImpl::SetPriorityAndReason().)
  template <typename Observer>
  using ObserverList =
      base::ReentrantObserverList<Observer, /*check_empty=*/true>;

  GraphImpl();
  ~GraphImpl() override;

  // Disallow copy and assign.
  GraphImpl(const GraphImpl&) = delete;
  GraphImpl& operator=(const GraphImpl&) = delete;

  // Set up the graph.
  void SetUp();

  // Tear down the graph to prepare for deletion.
  void TearDown();

  // Graph implementation:
  void AddFrameNodeObserver(FrameNodeObserver* observer) override;
  void AddPageNodeObserver(PageNodeObserver* observer) override;
  void AddProcessNodeObserver(ProcessNodeObserver* observer) override;
  void AddSystemNodeObserver(SystemNodeObserver* observer) override;
  void AddWorkerNodeObserver(WorkerNodeObserver* observer) override;
  void RemoveFrameNodeObserver(FrameNodeObserver* observer) override;
  void RemovePageNodeObserver(PageNodeObserver* observer) override;
  void RemoveProcessNodeObserver(ProcessNodeObserver* observer) override;
  void RemoveSystemNodeObserver(SystemNodeObserver* observer) override;
  void RemoveWorkerNodeObserver(WorkerNodeObserver* observer) override;
  void PassToGraphImpl(std::unique_ptr<GraphOwned> graph_owned) override;
  std::unique_ptr<GraphOwned> TakeFromGraph(GraphOwned* graph_owned) override;
  void RegisterObject(GraphRegistered* object) override;
  void UnregisterObject(GraphRegistered* object) override;
  const SystemNode* GetSystemNode() const override;
  NodeSetView<const ProcessNode*> GetAllProcessNodes() const override;
  NodeSetView<const FrameNode*> GetAllFrameNodes() const override;
  NodeSetView<const PageNode*> GetAllPageNodes() const override;
  NodeSetView<const WorkerNode*> GetAllWorkerNodes() const override;

  bool HasOnlySystemNode() const override;
  ukm::UkmRecorder* GetUkmRecorder() const override;
  NodeDataDescriberRegistry* GetNodeDataDescriberRegistry() const override;
  uintptr_t GetImplType() const override;
  const void* GetImpl() const override;
#if DCHECK_IS_ON()
  bool IsOnGraphSequence() const override;
#endif
  GraphRegistered* GetRegisteredObject(uintptr_t type_id) override;

  // Helper function for safely downcasting to the implementation. This also
  // casts away constness. This will CHECK on an invalid cast.
  static GraphImpl* FromGraph(const Graph* graph);

  void set_ukm_recorder(ukm::UkmRecorder* ukm_recorder) {
    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
    ukm_recorder_ = ukm_recorder;
  }
  ukm::UkmRecorder* ukm_recorder() const {
    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
    return ukm_recorder_;
  }

  SystemNodeImpl* GetSystemNodeImpl() const {
    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
    return system_node_.get();
  }
  NodeSetView<ProcessNodeImpl*> GetAllProcessNodeImpls() const;
  NodeSetView<FrameNodeImpl*> GetAllFrameNodeImpls() const;
  NodeSetView<PageNodeImpl*> GetAllPageNodeImpls() const;
  NodeSetView<WorkerNodeImpl*> GetAllWorkerNodeImpls() const;

  // Retrieves the process node with PID |pid|, if any.
  ProcessNodeImpl* GetProcessNodeByPid(base::ProcessId pid);

  // Retrieves the frame node with the routing ids of the process and the frame.
  FrameNodeImpl* GetFrameNodeById(RenderProcessHostId render_process_id,
                                  int render_frame_id);

  // Returns true if |node| is in this graph.
  bool NodeInGraph(const NodeBase* node) const;

  // Returns true iff `node` is in a state where its edges are publicly visible.
  bool NodeEdgesArePublic(const NodeBase* node) const;

  // Management functions for node owners, any node added to the graph must be
  // removed from the graph before it's deleted.
  void AddNewNode(NodeBase* new_node);
  void RemoveNode(NodeBase* node);

  // Allows explicitly invoking SystemNode destruction for testing.
  void ReleaseSystemNodeForTesting() {
    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
    ReleaseSystemNode();
  }

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

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

  // Returns the number of registered NodeDataDescribers, for testing.
  size_t NodeDataDescriberCountForTesting() const;

 protected:
  friend class NodeBase;

  // Returns the underlying set holding all nodes of type `node_type`.
  NodeSet& GetNodesOfType(NodeTypeEnum node_type);
  const NodeSet& GetNodesOfType(NodeTypeEnum node_type) const;

  // Used to implement `NodeBase::GetNodeState()` and `Node::GetNodeState()`.
  NodeState GetNodeState(const NodeBase* node) const;

  // Provides access to per-node-class typed observers. Exposed to nodes via
  // TypedNodeBase.
  template <typename Observer>
  const ObserverList<Observer>& GetObservers() const;

 private:
  struct ProcessAndFrameId {
    ProcessAndFrameId(RenderProcessHostId render_process_id,
                      int render_frame_id);
    ~ProcessAndFrameId();

    ProcessAndFrameId(const ProcessAndFrameId& other);
    ProcessAndFrameId& operator=(const ProcessAndFrameId& other);

    bool operator<(const ProcessAndFrameId& other) const;
    RenderProcessHostId render_process_id;
    int render_frame_id;
  };

  using ProcessByPidMap = std::map<base::ProcessId, ProcessNodeImpl*>;
  using FrameById =
      std::map<ProcessAndFrameId, raw_ptr<FrameNodeImpl, CtnExperimental>>;

  void DispatchBeforeNodeAddedNotifications(NodeBase* node);
  void DispatchNodeAddedNotifications(NodeBase* node);
  void DispatchBeforeNodeRemovedNotifications(NodeBase* node);
  void DispatchNodeRemovedNotifications(NodeBase* node);

  // Returns a new serialization ID.
  friend class NodeBase;
  int64_t GetNextNodeSerializationId();

  // Process PID map for use by ProcessNodeImpl.
  friend class ProcessNodeImpl;
  void BeforeProcessPidChange(ProcessNodeImpl* process,
                              base::ProcessId new_pid);

  // Frame id map for use by FrameNodeImpl.
  friend class FrameNodeImpl;
  void RegisterFrameNodeForId(RenderProcessHostId render_process_id,
                              int render_frame_id,
                              FrameNodeImpl* frame_node);
  void UnregisterFrameNodeForId(RenderProcessHostId render_process_id,
                                int render_frame_id,
                                FrameNodeImpl* frame_node);

  void CreateSystemNode() VALID_CONTEXT_REQUIRED(sequence_checker_);
  void ReleaseSystemNode() VALID_CONTEXT_REQUIRED(sequence_checker_);

  enum class LifecycleState {
    kBeforeSetUp,
    kSetUpCalled,
    kTearDownCalled,
  };

  // Tracks the lifecycle state of this instance to enforce calls to `SetUp()`
  // and `TearDown()`.
  LifecycleState lifecycle_state_ = LifecycleState::kBeforeSetUp;

  std::unique_ptr<SystemNodeImpl> system_node_
      GUARDED_BY_CONTEXT(sequence_checker_);
  std::array<NodeSet, kValidNodeTypeCount> nodes_
      GUARDED_BY_CONTEXT(sequence_checker_);
  ProcessByPidMap processes_by_pid_ GUARDED_BY_CONTEXT(sequence_checker_);
  FrameById frames_by_id_ GUARDED_BY_CONTEXT(sequence_checker_);
  raw_ptr<ukm::UkmRecorder> ukm_recorder_
      GUARDED_BY_CONTEXT(sequence_checker_) = nullptr;

  // Typed observers.
  ObserverList<FrameNodeObserver> frame_node_observers_
      GUARDED_BY_CONTEXT(sequence_checker_);
  ObserverList<PageNodeObserver> page_node_observers_
      GUARDED_BY_CONTEXT(sequence_checker_);
  ObserverList<ProcessNodeObserver> process_node_observers_
      GUARDED_BY_CONTEXT(sequence_checker_);
  ObserverList<SystemNodeObserver> system_node_observers_
      GUARDED_BY_CONTEXT(sequence_checker_);
  ObserverList<WorkerNodeObserver> worker_node_observers_
      GUARDED_BY_CONTEXT(sequence_checker_);

  // Graph-owned objects. For now we only expect O(10) clients, hence the
  // flat_map.
  OwnedObjects<GraphOwned,
               /* CallbackArgType = */ Graph*,
               &GraphOwned::PassToGraphImpl,
               &GraphOwned::TakeFromGraphImpl>
      graph_owned_ GUARDED_BY_CONTEXT(sequence_checker_);

  // Allocated on first use.
  mutable std::unique_ptr<NodeDataDescriberRegistry> describer_registry_
      GUARDED_BY_CONTEXT(sequence_checker_);

  // Storage for GraphRegistered objects.
  RegisteredObjects<GraphRegistered> registered_objects_
      GUARDED_BY_CONTEXT(sequence_checker_);

  execution_context::ExecutionContextRegistryImpl
      execution_context_registry_impl_;

  // The most recently assigned serialization ID.
  int64_t current_node_serialization_id_ GUARDED_BY_CONTEXT(sequence_checker_) =
      0u;

  // The identity of the node currently being added to or removed from the
  // graph, if any. This is used to prevent re-entrant notifications.
  raw_ptr<const NodeBase> node_in_transition_ = nullptr;

  // The state of the node being added or removed. Any node in the graph not
  // explicitly in transition is automatically in the kActiveInGraph state.
  NodeState node_in_transition_state_ = NodeState::kNotInGraph;

  SEQUENCE_CHECKER(sequence_checker_);
};

}  // namespace performance_manager

#endif  // COMPONENTS_PERFORMANCE_MANAGER_GRAPH_GRAPH_IMPL_H_
