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

#include "cc/trees/debug_rect_history.h"

#include <stddef.h>

#include "base/memory/ptr_util.h"
#include "cc/base/math_util.h"
#include "cc/layers/heads_up_display_layer_impl.h"
#include "cc/layers/layer_impl.h"
#include "cc/layers/layer_list_iterator.h"
#include "cc/layers/render_surface_impl.h"
#include "cc/paint/display_item_list.h"
#include "cc/trees/damage_tracker.h"
#include "cc/trees/layer_tree_host.h"
#include "cc/trees/layer_tree_impl.h"
#include "cc/trees/scroll_node.h"
#include "ui/gfx/geometry/rect_conversions.h"

namespace cc {

// static
std::unique_ptr<DebugRectHistory> DebugRectHistory::Create() {
  return base::WrapUnique(new DebugRectHistory());
}

DebugRectHistory::DebugRectHistory() = default;

DebugRectHistory::~DebugRectHistory() = default;

void DebugRectHistory::SaveDebugRectsForCurrentFrame(
    LayerTreeImpl* tree_impl,
    HeadsUpDisplayLayerImpl* hud_layer,
    const RenderSurfaceList& render_surface_list,
    const LayerTreeDebugState& debug_state) {
  std::erase_if(debug_rects_,
                [](const DebugRect& rect) { return rect.fade_step <= 0; });

  if (debug_state.show_touch_event_handler_rects) {
    SaveTouchEventHandlerRects(tree_impl);
  }
  if (debug_state.show_wheel_event_handler_rects) {
    SaveWheelEventHandlerRects(tree_impl);
  }
  if (debug_state.show_scroll_event_handler_rects) {
    SaveScrollEventHandlerRects(tree_impl);
  }
  if (debug_state.show_main_thread_scroll_hit_test_rects) {
    SaveMainThreadScrollHitTestRects(tree_impl);
  }
  if (debug_state.show_main_thread_scroll_repaint_rects) {
    SaveMainThreadScrollRepaintOrRasterInducingScrollRects(
        tree_impl, DebugRectType::kMainThreadScrollRepaint);
  }
  if (debug_state.show_raster_inducing_scroll_rects) {
    SaveMainThreadScrollRepaintOrRasterInducingScrollRects(
        tree_impl, DebugRectType::kRasterInducingScroll);
  }
  if (debug_state.show_contentful_paint_rects ||
      debug_state.show_layout_shift_regions) {
    SaveWebVitalsDebugRects(hud_layer);
  }
  if (debug_state.show_paint_rects) {
    SavePaintRects(tree_impl);
  }
  if (debug_state.show_property_changed_rects) {
    SavePropertyChangedRects(tree_impl, hud_layer);
  }
  if (debug_state.show_surface_damage_rects) {
    SaveSurfaceDamageRects(tree_impl, render_surface_list);
  }
  if (debug_state.show_screen_space_rects) {
    SaveScreenSpaceRects(tree_impl, render_surface_list);
  }
}

void DebugRectHistory::SaveWebVitalsDebugRects(HeadsUpDisplayLayerImpl* hud) {
  if (!hud) {
    return;
  }

  for (const auto& web_vital_rect : hud->WebVitalsDebugRects()) {
    DebugRectType type;
    switch (web_vital_rect.type) {
      case WebVitalMetricType::kLayoutShift:
        type = DebugRectType::kLayoutShift;
        break;
      case WebVitalMetricType::kInteractionContentfulPaint:
        type = DebugRectType::kInteractionContentfulPaint;
        break;
      case WebVitalMetricType::kNavigationContentfulPaint:
        type = DebugRectType::kNavigationContentfulPaint;
        break;
    }

    debug_rects_.emplace_back(
        type,
        MathUtil::MapEnclosingClippedRect(hud->ScreenSpaceTransform(),
                                          web_vital_rect.rect),
        TouchAction::kNone, MainThreadRepaintReasons{},
        DebugColors::kFadeSteps);
  }
  hud->ClearWebVitalsDebugRects();
}

void DebugRectHistory::SavePaintRects(LayerTreeImpl* tree_impl) {
  // We would like to visualize where any layer's paint rect (update rect) has
  // changed, regardless of whether this layer is skipped for actual drawing or
  // not. Therefore we traverse over all layers, not just the render surface
  // list.
  for (auto* layer : *tree_impl) {
    Region invalidation_region = layer->GetInvalidationRegionForDebugging();
    if (invalidation_region.IsEmpty() || !layer->draws_content())
      continue;

    for (gfx::Rect rect : invalidation_region) {
      debug_rects_.emplace_back(DebugRectType::kPaint,
                                MathUtil::MapEnclosingClippedRect(
                                    layer->ScreenSpaceTransform(), rect),
                                TouchAction::kNone, MainThreadRepaintReasons{},
                                DebugColors::kFadeSteps);
    }
  }
}

void DebugRectHistory::SavePropertyChangedRects(LayerTreeImpl* tree_impl,
                                                LayerImpl* hud_layer) {
  for (LayerImpl* layer : *tree_impl) {
    if (layer == hud_layer)
      continue;

    if (!layer->LayerPropertyChanged())
      continue;

    debug_rects_.emplace_back(
        DebugRectType::kPropertyChanged,
        MathUtil::MapEnclosingClippedRect(layer->ScreenSpaceTransform(),
                                          gfx::Rect(layer->bounds())));
  }
}

void DebugRectHistory::SaveSurfaceDamageRects(
    LayerTreeImpl* tree_impl,
    const RenderSurfaceList& render_surface_list) {
  for (size_t i = 0; i < render_surface_list.size(); ++i) {
    size_t surface_index = render_surface_list.size() - 1 - i;
    int effect_id = render_surface_list[surface_index];
    RenderSurfaceImpl* render_surface = tree_impl->GetRenderSurface(effect_id);
    DCHECK(render_surface);

    debug_rects_.emplace_back(DebugRectType::kSurfaceDamage,
                              MathUtil::MapEnclosingClippedRect(
                                  render_surface->screen_space_transform(),
                                  render_surface->GetDamageRect()));
  }
}

void DebugRectHistory::SaveScreenSpaceRects(
    LayerTreeImpl* tree_impl,
    const RenderSurfaceList& render_surface_list) {
  for (size_t i = 0; i < render_surface_list.size(); ++i) {
    size_t surface_index = render_surface_list.size() - 1 - i;
    int effect_id = render_surface_list[surface_index];
    RenderSurfaceImpl* render_surface = tree_impl->GetRenderSurface(effect_id);
    DCHECK(render_surface);

    debug_rects_.emplace_back(DebugRectType::kScreenSpace,
                              MathUtil::MapEnclosingClippedRect(
                                  render_surface->screen_space_transform(),
                                  render_surface->content_rect()));
  }
}

void DebugRectHistory::SaveTouchEventHandlerRects(LayerTreeImpl* tree_impl) {
  for (auto* layer : *tree_impl) {
    for (const auto& pair : layer->touch_action_region()) {
      TouchAction touch_action = pair.first;
      for (gfx::Rect rect : pair.second) {
        debug_rects_.emplace_back(DebugRectType::kTouchEventHandler,
                                  MathUtil::MapEnclosingClippedRect(
                                      layer->ScreenSpaceTransform(), rect),
                                  touch_action);
      }
    }
  }
}

void DebugRectHistory::SaveWheelEventHandlerRects(LayerTreeImpl* tree_impl) {
  // TODO(crbug.com/40724301): Need behavior confirmation.
  // TODO(crbug.com/40724301): Need to check results in dev tools layer
  // view.
  for (auto* layer : *tree_impl) {
    const Region& region = layer->wheel_event_handler_region();
    for (gfx::Rect rect : region) {
      debug_rects_.emplace_back(DebugRectType::kWheelEventHandler,
                                MathUtil::MapEnclosingClippedRect(
                                    layer->ScreenSpaceTransform(), rect));
    }
  }
}

void DebugRectHistory::SaveScrollEventHandlerRects(LayerTreeImpl* tree_impl) {
  for (auto* layer : *tree_impl) {
    if (!layer->layer_tree_impl()->have_scroll_event_handlers()) {
      continue;
    }
    debug_rects_.emplace_back(
        DebugRectType::kScrollEventHandler,
        MathUtil::MapEnclosingClippedRect(layer->ScreenSpaceTransform(),
                                          gfx::Rect(layer->bounds())));
  }
}

void DebugRectHistory::SaveMainThreadScrollHitTestRects(
    LayerTreeImpl* tree_impl) {
  for (auto* layer : *tree_impl) {
    for (gfx::Rect rect : layer->main_thread_scroll_hit_test_region()) {
      debug_rects_.emplace_back(DebugRectType::kMainThreadScrollHitTest,
                                MathUtil::MapEnclosingClippedRect(
                                    layer->ScreenSpaceTransform(), rect));
    }
  }
}

void DebugRectHistory::SaveMainThreadScrollRepaintOrRasterInducingScrollRects(
    LayerTreeImpl* tree_impl,
    DebugRectType type) {
  const auto& scroll_tree = tree_impl->property_trees()->scroll_tree();
  const auto& transform_tree = tree_impl->property_trees()->transform_tree();
  for (auto& node : scroll_tree.nodes()) {
    if (type == DebugRectType::kMainThreadScrollRepaint
            ? scroll_tree.ShouldRealizeScrollsOnMain(node)
            : scroll_tree.CanRealizeScrollsOnPendingTree(node)) {
      if (node.transform_id != kInvalidPropertyNodeId) {
        const TransformNode& transform_node =
            transform_tree.Node(node.transform_id);
        if (transform_node.parent_id != kInvalidPropertyNodeId) {
          debug_rects_.emplace_back(
              type, MathUtil::MapEnclosingClippedRect(
                        // Skip the scroll translation node.
                        transform_tree.ToScreen(transform_node.parent_id),
                        gfx::Rect(node.container_origin,
                                  scroll_tree.container_bounds(node.id))));
        }
      }
    }
  }
}

}  // namespace cc
