// 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 "components/performance_manager/graph/graph_impl_operations.h"

#include <set>

#include "base/memory/raw_ptr.h"
#include "components/performance_manager/graph/frame_node_impl.h"
#include "components/performance_manager/graph/page_node_impl.h"
#include "components/performance_manager/graph/process_node_impl.h"
#include "components/performance_manager/graph/worker_node_impl.h"

namespace performance_manager {

namespace {

bool VisitWorkerClientFrames(
    const WorkerNodeImpl* worker,
    GraphImplOperations::FrameNodeImplVisitor frame_visitor,
    std::set<const FrameNodeImpl*>& visited_frames) {
  for (FrameNodeImpl* f : worker->client_frames()) {
    // Mark this frame as visited.
    const auto [_, inserted] = visited_frames.insert(f);
    if (!inserted) {
      // Already visited.
      continue;
    }
    if (!frame_visitor(f)) {
      return false;
    }
  }
  return true;
}

bool VisitWorkerAndClients(
    WorkerNodeImpl* worker,
    GraphImplOperations::FrameNodeImplVisitor frame_visitor,
    GraphImplOperations::WorkerNodeImplVisitor worker_visitor,
    std::set<const FrameNodeImpl*>& visited_frames,
    std::set<const WorkerNodeImpl*>& visited_workers) {
  // Mark this worker as visited.
  const auto [_, inserted] = visited_workers.insert(worker);
  if (!inserted) {
    // Already visited.
    return true;
  }
  if (!worker_visitor(worker)) {
    return false;
  }
  if (!VisitWorkerClientFrames(worker, frame_visitor, visited_frames)) {
    return false;
  }
  for (WorkerNodeImpl* w : worker->client_workers()) {
    if (!VisitWorkerAndClients(w, frame_visitor, worker_visitor, visited_frames,
                               visited_workers)) {
      return false;
    }
  }
  return true;
}

}  // namespace

// static
base::flat_set<raw_ptr<PageNodeImpl, CtnExperimental>>
GraphImplOperations::GetAssociatedPageNodes(const ProcessNodeImpl* process) {
  std::vector<raw_ptr<PageNodeImpl, CtnExperimental>> page_nodes;
  page_nodes.reserve(process->frame_nodes().size());

  for (FrameNodeImpl* frame_node : process->frame_nodes()) {
    page_nodes.push_back(frame_node->page_node());
  }

  return base::flat_set<raw_ptr<PageNodeImpl, CtnExperimental>>(
      std::move(page_nodes));
}

// static
base::flat_set<raw_ptr<ProcessNodeImpl, CtnExperimental>>
GraphImplOperations::GetAssociatedProcessNodes(const PageNodeImpl* page) {
  std::vector<raw_ptr<ProcessNodeImpl, CtnExperimental>> process_nodes;
  process_nodes.reserve(10);  // Avoid resizing in most cases.

  VisitFrameTreePreOrder(page,
                         [&process_nodes](FrameNodeImpl* frame_node) -> bool {
                           process_nodes.push_back(frame_node->process_node());
                           return true;
                         });

  return base::flat_set<raw_ptr<ProcessNodeImpl, CtnExperimental>>(
      std::move(process_nodes));
}

// static
std::vector<FrameNodeImpl*> GraphImplOperations::GetFrameNodes(
    const PageNodeImpl* page) {
  std::vector<FrameNodeImpl*> frame_nodes;
  frame_nodes.reserve(20);  // This is in the 99.9th %ile of frame tree sizes.

  for (FrameNodeImpl* main_frame_node : page->main_frame_nodes()) {
    frame_nodes.push_back(main_frame_node);
  }

  for (size_t i = 0; i < frame_nodes.size(); ++i) {
    auto* parent_frame_node = frame_nodes[i];
    for (FrameNodeImpl* frame_node : parent_frame_node->child_frame_nodes()) {
      frame_nodes.push_back(frame_node);
    }
  }

  return frame_nodes;
}

// static
bool GraphImplOperations::VisitFrameAndChildrenPreOrder(
    FrameNodeImpl* frame,
    GraphImplOperations::FrameNodeImplVisitor visitor) {
  if (!visitor(frame)) {
    return false;
  }
  for (FrameNodeImpl* child : frame->child_frame_nodes()) {
    if (!VisitFrameAndChildrenPreOrder(child, visitor)) {
      return false;
    }
  }
  return true;
}

// static
bool GraphImplOperations::VisitFrameAndChildrenPostOrder(
    FrameNodeImpl* frame,
    GraphImplOperations::FrameNodeImplVisitor visitor) {
  for (FrameNodeImpl* child : frame->child_frame_nodes()) {
    if (!VisitFrameAndChildrenPostOrder(child, visitor)) {
      return false;
    }
  }
  if (!visitor(frame)) {
    return false;
  }
  return true;
}

// static
bool GraphImplOperations::VisitFrameTreePreOrder(const PageNodeImpl* page,
                                                 FrameNodeImplVisitor visitor) {
  for (FrameNodeImpl* main_frame_node : page->main_frame_nodes()) {
    if (!VisitFrameAndChildrenPreOrder(main_frame_node, visitor)) {
      return false;
    }
  }
  return true;
}

// static
bool GraphImplOperations::VisitFrameTreePostOrder(
    const PageNodeImpl* page,
    FrameNodeImplVisitor visitor) {
  for (FrameNodeImpl* main_frame_node : page->main_frame_nodes()) {
    if (!VisitFrameAndChildrenPostOrder(main_frame_node, visitor)) {
      return false;
    }
  }
  return true;
}

// static
bool GraphImplOperations::HasFrame(const PageNodeImpl* page,
                                   FrameNodeImpl* frame) {
  bool has_frame = false;
  VisitFrameTreePreOrder(page, [&has_frame, frame](FrameNodeImpl* f) -> bool {
    if (f == frame) {
      has_frame = true;
      return false;
    }
    return true;
  });
  return has_frame;
}

// static
bool GraphImplOperations::VisitAllWorkerClients(
    const WorkerNodeImpl* worker,
    FrameNodeImplVisitor frame_visitor,
    WorkerNodeImplVisitor worker_visitor) {
  std::set<const FrameNodeImpl*> visited_frames;
  std::set<const WorkerNodeImpl*> visited_workers;
  // Don't visit `worker` itself.
  if (!VisitWorkerClientFrames(worker, frame_visitor, visited_frames)) {
    return false;
  }
  for (WorkerNodeImpl* w : worker->client_workers()) {
    if (!VisitWorkerAndClients(w, frame_visitor, worker_visitor, visited_frames,
                               visited_workers)) {
      return false;
    }
  }
  return true;
}

}  // namespace performance_manager
