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

#include "services/ui/ws/frame_generator.h"

#include <utility>
#include <vector>

#include "components/viz/common/quads/compositor_frame.h"
#include "components/viz/common/quads/render_pass.h"
#include "components/viz/common/quads/render_pass_draw_quad.h"
#include "components/viz/common/quads/shared_quad_state.h"
#include "components/viz/common/quads/surface_draw_quad.h"
#include "ui/gfx/geometry/size_f.h"

namespace ui {
namespace ws {

FrameGenerator::FrameGenerator() = default;

FrameGenerator::~FrameGenerator() = default;

void FrameGenerator::SetDeviceScaleFactor(float device_scale_factor) {
  if (device_scale_factor_ == device_scale_factor)
    return;
  device_scale_factor_ = device_scale_factor;
  SetNeedsBeginFrame(true);
}

void FrameGenerator::SetHighContrastMode(bool enabled) {
  if (high_contrast_mode_enabled_ == enabled)
    return;

  high_contrast_mode_enabled_ = enabled;
  SetNeedsBeginFrame(true);
}

void FrameGenerator::SetEmbeddedSurface(const viz::SurfaceInfo& surface_info) {
  DCHECK(surface_info.is_valid());

  // Only handle embedded surfaces changing here. The display root surface
  // changing is handled immediately after the CompositorFrame is submitted.
  if (surface_info != window_manager_surface_info_) {
    window_manager_surface_info_ = surface_info;
    SetNeedsBeginFrame(true);
  }
}

void FrameGenerator::SwapSurfaceWith(FrameGenerator* other) {
  viz::SurfaceInfo window_manager_surface_info = window_manager_surface_info_;
  window_manager_surface_info_ = other->window_manager_surface_info_;
  other->window_manager_surface_info_ = window_manager_surface_info;
  if (window_manager_surface_info_.is_valid())
    SetNeedsBeginFrame(true);
  if (other->window_manager_surface_info_.is_valid())
    other->SetNeedsBeginFrame(true);
}

void FrameGenerator::OnWindowDamaged() {
  SetNeedsBeginFrame(true);
}

void FrameGenerator::OnWindowSizeChanged(const gfx::Size& pixel_size) {
  if (pixel_size_ == pixel_size)
    return;

  pixel_size_ = pixel_size;
  SetNeedsBeginFrame(true);
  display_private_->Resize(pixel_size);
}

void FrameGenerator::Bind(
    std::unique_ptr<viz::mojom::CompositorFrameSink> compositor_frame_sink,
    viz::mojom::DisplayPrivateAssociatedPtr display_private) {
  DCHECK(!compositor_frame_sink_);
  compositor_frame_sink_ = std::move(compositor_frame_sink);
  display_private_ = std::move(display_private);
}

void FrameGenerator::ReclaimResources(
    const std::vector<viz::ReturnedResource>& resources) {
  // Nothing to do here because FrameGenerator CompositorFrames don't reference
  // any resources.
  DCHECK(resources.empty());
}

void FrameGenerator::DidReceiveCompositorFrameAck(
    const std::vector<viz::ReturnedResource>& resources) {}

void FrameGenerator::DidPresentCompositorFrame(
    uint32_t presentation_token,
    const gfx::PresentationFeedback& feedback) {
  NOTIMPLEMENTED();
}

void FrameGenerator::OnBeginFrame(const viz::BeginFrameArgs& begin_frame_args) {
  DCHECK(compositor_frame_sink_);
  current_begin_frame_ack_ = viz::BeginFrameAck(begin_frame_args, false);
  if (begin_frame_args.type == viz::BeginFrameArgs::MISSED) {
    compositor_frame_sink_->DidNotProduceFrame(current_begin_frame_ack_);
    return;
  }

  current_begin_frame_ack_.has_damage = true;
  last_begin_frame_args_ = begin_frame_args;

  // TODO(fsamuel): We should add a trace for generating a top level frame.
  viz::CompositorFrame frame(GenerateCompositorFrame());
  if (!id_allocator_.GetCurrentLocalSurfaceId().is_valid() ||
      frame.size_in_pixels() != last_submitted_frame_size_ ||
      frame.device_scale_factor() != last_device_scale_factor_) {
    last_device_scale_factor_ = frame.device_scale_factor();
    last_submitted_frame_size_ = frame.size_in_pixels();
    id_allocator_.GenerateId();
  }

  compositor_frame_sink_->SubmitCompositorFrame(
      id_allocator_.GetCurrentLocalSurfaceId(), std::move(frame),
      GenerateHitTestRegionList(), 0);

  SetNeedsBeginFrame(false);
}

viz::CompositorFrame FrameGenerator::GenerateCompositorFrame() {
  const int render_pass_id = 1;
  const gfx::Rect bounds(pixel_size_);
  std::unique_ptr<viz::RenderPass> render_pass = viz::RenderPass::Create();
  render_pass->SetNew(render_pass_id, bounds, bounds, gfx::Transform());

  DrawWindow(render_pass.get());

  viz::CompositorFrame frame;
  frame.render_pass_list.push_back(std::move(render_pass));
  if (high_contrast_mode_enabled_) {
    std::unique_ptr<viz::RenderPass> invert_pass = viz::RenderPass::Create();
    invert_pass->SetNew(2, bounds, bounds, gfx::Transform());
    viz::SharedQuadState* shared_state =
        invert_pass->CreateAndAppendSharedQuadState();
    gfx::Size scaled_bounds = gfx::ScaleToCeiledSize(
        pixel_size_, window_manager_surface_info_.device_scale_factor(),
        window_manager_surface_info_.device_scale_factor());
    bool is_clipped = false;
    bool are_contents_opaque = false;
    shared_state->SetAll(gfx::Transform(), gfx::Rect(scaled_bounds), bounds,
                         bounds, is_clipped, are_contents_opaque, 1.f,
                         SkBlendMode::kSrcOver, 0);
    auto* quad =
        invert_pass->CreateAndAppendDrawQuad<viz::RenderPassDrawQuad>();
    frame.render_pass_list.back()->filters.Append(
        cc::FilterOperation::CreateInvertFilter(1.f));
    quad->SetNew(
        shared_state, bounds, bounds, render_pass_id, 0 /* mask_resource_id */,
        gfx::RectF() /* mask_uv_rect */, gfx::Size() /* mask_texture_size */,
        gfx::Vector2dF() /* filters_scale */,
        gfx::PointF() /* filters_origin */, gfx::RectF() /* tex_coord_rect */,
        false /* force_anti_aliasing_off */);
    frame.render_pass_list.push_back(std::move(invert_pass));
  }
  frame.metadata.device_scale_factor = device_scale_factor_;
  frame.metadata.begin_frame_ack = current_begin_frame_ack_;

  if (window_manager_surface_info_.is_valid()) {
    frame.metadata.referenced_surfaces.push_back(
        viz::SurfaceRange(window_manager_surface_info_.id()));
  }

  return frame;
}

viz::HitTestRegionList FrameGenerator::GenerateHitTestRegionList() const {
  viz::HitTestRegionList hit_test_region_list;
  hit_test_region_list.flags = viz::HitTestRegionFlags::kHitTestMine;
  hit_test_region_list.bounds.set_size(pixel_size_);

  viz::HitTestRegion hit_test_region;
  hit_test_region.frame_sink_id =
      window_manager_surface_info_.id().frame_sink_id();
  hit_test_region.flags = viz::HitTestRegionFlags::kHitTestChildSurface;
  hit_test_region.rect = gfx::Rect(pixel_size_);

  hit_test_region_list.regions.push_back(std::move(hit_test_region));

  return hit_test_region_list;
}

void FrameGenerator::DrawWindow(viz::RenderPass* pass) {
  DCHECK(window_manager_surface_info_.is_valid());

  const gfx::Rect bounds_at_origin(
      window_manager_surface_info_.size_in_pixels());

  gfx::Transform quad_to_target_transform;

  if (scale_and_center_) {
    // Determine the scaling to fit the source within the target.
    gfx::SizeF source(window_manager_surface_info_.size_in_pixels());
    const gfx::SizeF target(pixel_size_);
    const float scale = std::min(target.width() / source.width(),
                                 target.height() / source.height());

    // Apply the transform to center the source within the output.
    source.Scale(scale);
    DCHECK(source.width() <= target.width() ||
           source.height() <= target.height());
    if (source.width() < target.width()) {
      quad_to_target_transform.Translate(
          (target.width() - source.width()) / 2.0f, 0);
    } else if (source.height() < target.height()) {
      quad_to_target_transform.Translate(
          0, (target.height() - source.height()) / 2.0f);
    }

    // Apply the scaling after the transform.
    quad_to_target_transform.Scale(scale, scale);
  }

  viz::SharedQuadState* sqs = pass->CreateAndAppendSharedQuadState();

  gfx::Size scaled_bounds = gfx::ScaleToCeiledSize(
      bounds_at_origin.size(),
      window_manager_surface_info_.device_scale_factor(),
      window_manager_surface_info_.device_scale_factor());

  // TODO(fsamuel): These clipping and visible rects are incorrect. They need
  // to be populated from CompositorFrame structs.
  sqs->SetAll(quad_to_target_transform,
              gfx::Rect(scaled_bounds) /* layer_rect */,
              bounds_at_origin /* visible_layer_bounds */,
              bounds_at_origin /* clip_rect */, false /* is_clipped */,
              false /* are_contents_opaque */, 1.0f /* opacity */,
              SkBlendMode::kSrcOver, 0 /* sorting-context_id */);
  auto* quad = pass->CreateAndAppendDrawQuad<viz::SurfaceDrawQuad>();
  quad->SetAll(sqs, bounds_at_origin /* rect */,
               bounds_at_origin /* visible_rect */, true /* needs_blending*/,
               window_manager_surface_info_.id(),
               window_manager_surface_info_.id(),
               SK_ColorWHITE /* default_background_color */,
               false /* stretch_content_to_fill_bounds */);
}

void FrameGenerator::SetNeedsBeginFrame(bool needs_begin_frame) {
  needs_begin_frame &= window_manager_surface_info_.is_valid();
  compositor_frame_sink_->SetNeedsBeginFrame(needs_begin_frame);
}

}  // namespace ws
}  // namespace ui
