// Copyright 2024 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/layers/tile_display_layer_impl.h"

#include <algorithm>
#include <memory>
#include <utility>
#include <variant>

#include "base/check.h"
#include "base/logging.h"
#include "base/notreached.h"
#include "cc/base/math_util.h"
#include "cc/layers/append_quads_data.h"
#include "cc/trees/layer_tree_impl.h"
#include "components/viz/client/client_resource_provider.h"
#include "components/viz/common/quads/solid_color_draw_quad.h"
#include "components/viz/common/quads/tile_draw_quad.h"

namespace cc {

namespace {

class TilingOrder {
 public:
  bool operator()(const std::unique_ptr<TileDisplayLayerTiling>& left,
                  const std::unique_ptr<TileDisplayLayerTiling>& right) {
    return left->contents_scale_key() > right->contents_scale_key();
  }
};

}  // namespace

TileDisplayLayerTile::TileDisplayLayerTile(
    TileDisplayLayerImpl& layer,
    const TileDisplayLayerTileContents& contents)
    : layer_(layer), contents_(contents) {}

TileDisplayLayerTile::TileDisplayLayerTile(TileDisplayLayerTile&&) = default;

TileDisplayLayerTile::~TileDisplayLayerTile() {
  if (auto* resource = std::get_if<TileDisplayLayerTileResource>(&contents_)) {
    layer_->DiscardResource(resource->resource_id);
  }
}

TileDisplayLayerTiling::TileDisplayLayerTiling(TileDisplayLayerImpl& layer,
                                               float scale_key)
    : layer_(layer), scale_key_(scale_key) {}

TileDisplayLayerTiling::~TileDisplayLayerTiling() = default;

TileDisplayLayerTile* TileDisplayLayerTiling::TileAt(
    const TileIndex& index) const {
  auto it = tiles_.find(index);
  if (it == tiles_.end()) {
    return nullptr;
  }
  return it->second.get();
}

void TileDisplayLayerTiling::SetRasterTransform(
    const gfx::AxisTransform2d& transform) {
  DCHECK_EQ(std::max(transform.scale().x(), transform.scale().y()), scale_key_);
  raster_transform_ = transform;
}

void TileDisplayLayerTiling::SetTileSize(const gfx::Size& size) {
  if (size == tiling_data_.max_texture_size()) {
    return;
  }

  tiling_data_.SetMaxTextureSize(size);
}

void TileDisplayLayerTiling::SetTilingRect(const gfx::Rect& rect) {
  if (rect == tiling_data_.tiling_rect()) {
    return;
  }

  tiling_data_.SetTilingRect(rect);
}

void TileDisplayLayerTiling::SetTileContents(
    const TileIndex& key,
    const TileDisplayLayerTileContents& contents,
    bool update_damage) {
  if (update_damage) {
    // Full tree updates receive damage as part of the LayerImpl::update_rect.
    // For incremental tile updates on an Active tree, we need to record the
    // damage caused by each tile change.
    gfx::Rect tile_rect = tiling_data_.TileBoundsWithBorder(key.i, key.j);
    tile_rect.set_size(tiling_data_.max_texture_size());
    gfx::Rect enclosing_layer_rect = ToEnclosingRect(
        raster_transform_.InverseMapRect(gfx::RectF(tile_rect)));
    layer_->RecordDamage(enclosing_layer_rect);
  }

  std::unique_ptr<Tile> old_tile;
  if (std::holds_alternative<TileDisplayLayerNoContents>(contents)) {
    const auto& no_contents = std::get<TileDisplayLayerNoContents>(contents);
    if (no_contents.reason == mojom::MissingTileReason::kTileDeleted) {
      tiles_.erase(key);
    } else {
      old_tile =
          std::exchange(tiles_[key], std::make_unique<Tile>(*layer_, contents));
    }
  } else {
    old_tile =
        std::exchange(tiles_[key], std::make_unique<Tile>(*layer_, contents));
  }
}

DisplayTilingCoverageIterator TileDisplayLayerTiling::Cover(
    const gfx::Rect& coverage_rect,
    float coverage_scale) const {
  return DisplayTilingCoverageIterator(this, coverage_scale, coverage_rect);
}

gfx::Size TileDisplayLayerTiling::raster_size() const {
  return layer_->bounds();
}

TileDisplayLayerImpl::TileDisplayLayerImpl(LayerTreeImpl& tree, int id)
    : TileBasedLayerImpl(&tree, id) {}

TileDisplayLayerImpl::~TileDisplayLayerImpl() = default;

TileDisplayLayerTiling& TileDisplayLayerImpl::GetOrCreateTilingFromScaleKey(
    float scale_key) {
  auto it = std::find_if(tilings_.begin(), tilings_.end(),
                         [scale_key](const auto& tiling) {
                           return tiling->contents_scale_key() == scale_key;
                         });
  if (it != tilings_.end()) {
    return **it;
  }

  tilings_.push_back(
      std::make_unique<TileDisplayLayerTiling>(*this, scale_key));
  TileDisplayLayerTiling& tiling = *tilings_.back();
  std::sort(tilings_.begin(), tilings_.end(), TilingOrder());
  return tiling;
}

void TileDisplayLayerImpl::RemoveTiling(float scale_key) {
  auto it = std::find_if(tilings_.begin(), tilings_.end(),
                         [scale_key](const auto& tiling) {
                           return tiling->contents_scale_key() == scale_key;
                         });
  if (it != tilings_.end()) {
    tilings_.erase(it);
  }
}

const TileDisplayLayerTiling* TileDisplayLayerImpl::GetTilingForTesting(
    float scale_key) const {
  auto it = std::find_if(tilings_.begin(), tilings_.end(),
                         [scale_key](const auto& tiling) {
                           return tiling->contents_scale_key() == scale_key;
                         });
  return it != tilings_.end() ? it->get() : nullptr;
}

mojom::LayerType TileDisplayLayerImpl::GetLayerType() const {
  return mojom::LayerType::kTileDisplay;
}

std::unique_ptr<LayerImpl> TileDisplayLayerImpl::CreateLayerImpl(
    LayerTreeImpl* tree_impl) const {
  NOTREACHED();
}

void TileDisplayLayerImpl::CopyPropertiesTo(LayerImpl* layer) const {
  NOTREACHED();
}

bool TileDisplayLayerImpl::ComputeCheckerboardedNeedsRecord() {
  // NOTE: Currently it is not necessary to compute
  // checkerboarded_needs_recorded on the Viz side, as it is consumed only on
  // the client side. However, it will become necessary when we introduce
  // frames driven entirely by Viz. At that point, we should dedupe the
  // relevant code into TileBasedLayerImpl.
  // See crbug.com/482862751.
  return false;
}

float TileDisplayLayerImpl::GetMaximumContentsScaleForUseInAppendQuads() const {
  return tilings_.empty() ? 1.0 : tilings_.front()->contents_scale_key();
}

bool TileDisplayLayerImpl::IsDirectlyCompositedImage() const {
  return is_directly_composited_image_;
}

gfx::Rect TileDisplayLayerImpl::RecordedBounds() const {
  return recorded_bounds_;
}

gfx::ContentColorUsage TileDisplayLayerImpl::GetContentColorUsage() const {
  return content_color_usage_;
}

DamageReasonSet TileDisplayLayerImpl::GetDamageReasons() const {
  DamageReasonSet reasons =
      LayerImpl::GetDamageReasonsFromLayerPropertyChange();
  if (has_animated_image_update_rect_) {
    reasons.Put(DamageReason::kAnimatedImage);
  }
  if (has_non_animated_image_update_rect_ || !GetDamageRect().IsEmpty()) {
    reasons.Put(DamageReason::kUntracked);
  }
  return reasons;
}

void TileDisplayLayerImpl::ResetChangeTracking() {
  TileBasedLayerImpl<TileDisplayLayerTiling>::ResetChangeTracking();
  has_animated_image_update_rect_ = false;
  has_non_animated_image_update_rect_ = false;
}

void TileDisplayLayerImpl::RecordDamage(const gfx::Rect& damage_rect) {
  UnionWithExistingDamage(damage_rect);
}

void TileDisplayLayerImpl::DiscardResource(viz::ResourceId resource) {
  layer_tree_impl()->host_impl()->resource_provider()->RemoveImportedResource(
      std::move(resource));
}

std::vector<float> TileDisplayLayerImpl::GetSafeToDeleteTilings() {
  std::vector<float> safe_to_delete_scales;
  for (float scale : proposed_tiling_scales_for_deletion_) {
    // Check if a tiling corresponding to the candidate scale is present in
    // |last_append_quads_scales_|.
    if (!LastAppendQuadsScalesContains(scale)) {
      // If a tiling corresponding to the candidate scale is not present in
      // |last_append_quads_scales_|, then its safe to delete.
      safe_to_delete_scales.push_back(scale);
    }
  }
  proposed_tiling_scales_for_deletion_.clear();
  return safe_to_delete_scales;
}

float TileDisplayLayerImpl::GetIdealContentsScaleKey() const {
  const auto ideal_scale = GetIdealContentsScale();
  float ideal_scale_key = std::max(ideal_scale.x(), ideal_scale.y());

  // external_page_scale_factor (e.g. an OOPIF scaled by its embedder) affects
  // raster/ideal scale but not geometry, so it is not part of
  // GetIdealContentsScale(). Apply it here to mirror
  // PictureLayerImpl::UpdateIdealScales(); otherwise the coverage iterator
  // would select a lower-resolution tiling, producing blurry content.
  ideal_scale_key *= layer_tree_impl()->external_page_scale_factor();
  return ideal_scale_key;
}

bool TileDisplayLayerImpl::ValidateTilingSetForContentsResourceId() const {
  // Masks are only supported if they fit on exactly one tile.
  return tilings_.size() == 1u;
}

void TileDisplayLayerImpl::AppendQuadsForResourcelessSoftwareDraw(
    const AppendQuadsContext& context,
    viz::CompositorRenderPass* render_pass,
    AppendQuadsData* append_quads_data,
    viz::SharedQuadState* shared_quad_state,
    const Occlusion& scaled_occlusion) {
  // `DRAW_MODE_RESOURCELESS_SOFTWARE` is a renderer-only software draw mode,
  // and its handling is thus specific to the renderer-side PictureLayerImpl. It
  // should never be propagated to the Viz side.
  NOTREACHED();
}

TilingSetCoverageIterator<TileDisplayLayerTiling> TileDisplayLayerImpl::Cover(
    const gfx::Rect& coverage_rect,
    float coverage_scale,
    float ideal_contents_scale) const {
  return TilingSetCoverageIterator<TileDisplayLayerTiling>(
      tilings_, coverage_rect, coverage_scale, ideal_contents_scale);
}

TileBasedLayerImpl<TileDisplayLayerTiling>::TilingResolution
TileDisplayLayerImpl::GetTilingResolutionForDebugBorders(
    const TileDisplayLayerTiling* tiling) const {
  const float ideal_scale_key = GetIdealContentsScaleKey();
  if (MathUtil::IsFloatNearlyTheSame(tiling->contents_scale_key(),
                                     ideal_scale_key)) {
    // NOTE: The above check is not exactly the same computation as is
    // used by PictureLayerImpl, as high resolution tiles within
    // PictureLayerImpl use `raster_contents_scale_`, which is not
    // necessarily the ideal scale. However, we don't have the former
    // field here, so use the ideal scale as an approximation.
    // TODO(crbug.com/450651370): Determine whether we want to fix this.
    return TilingResolution::kHigh;
  }
  if (tiling->contents_scale_key() > ideal_scale_key) {
    return TilingResolution::kAboveHigh;
  }
  return TilingResolution::kBelowHigh;
}

}  // namespace cc
