// Copyright 2013 The Flutter 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 "flutter/lib/ui/compositing/scene_builder.h"
#include <cstdint>

#include "dart_api.h"
#include "flutter/flow/layers/backdrop_filter_layer.h"
#include "flutter/flow/layers/clip_path_layer.h"
#include "flutter/flow/layers/clip_rect_layer.h"
#include "flutter/flow/layers/clip_rrect_layer.h"
#include "flutter/flow/layers/clip_rsuperellipse_layer.h"
#include "flutter/flow/layers/color_filter_layer.h"
#include "flutter/flow/layers/container_layer.h"
#include "flutter/flow/layers/display_list_layer.h"
#include "flutter/flow/layers/image_filter_layer.h"
#include "flutter/flow/layers/layer.h"
#include "flutter/flow/layers/opacity_layer.h"
#include "flutter/flow/layers/performance_overlay_layer.h"
#include "flutter/flow/layers/platform_view_layer.h"
#include "flutter/flow/layers/shader_mask_layer.h"
#include "flutter/flow/layers/texture_layer.h"
#include "flutter/flow/layers/transform_layer.h"
#include "flutter/fml/build_config.h"
#include "flutter/lib/ui/compositing/scene.h"
#include "flutter/lib/ui/floating_point.h"
#include "flutter/lib/ui/painting/matrix.h"
#include "flutter/lib/ui/painting/shader.h"

namespace flutter {

IMPLEMENT_WRAPPERTYPEINFO(ui, SceneBuilder);

SceneBuilder::SceneBuilder() {
  // Add a ContainerLayer as the root layer, so that AddLayer operations are
  // always valid.
  PushLayer(std::make_shared<flutter::ContainerLayer>());
}

SceneBuilder::~SceneBuilder() = default;

void SceneBuilder::pushTransform(Dart_Handle layer_handle,
                                 tonic::Float64List& matrix4,
                                 const fml::RefPtr<EngineLayer>& old_layer) {
  DlMatrix matrix = ToDlMatrix(matrix4);
  auto layer = std::make_shared<flutter::TransformLayer>(matrix);
  PushLayer(layer);
  // matrix4 has to be released before we can return another Dart object
  matrix4.Release();
  EngineLayer::MakeRetained(layer_handle, layer);

  if (old_layer && old_layer->Layer()) {
    layer->AssignOldLayer(old_layer->Layer().get());
  }
}

void SceneBuilder::pushOffset(Dart_Handle layer_handle,
                              double dx,
                              double dy,
                              const fml::RefPtr<EngineLayer>& old_layer) {
  DlMatrix matrix = DlMatrix::MakeTranslation({SafeNarrow(dx), SafeNarrow(dy)});
  auto layer = std::make_shared<flutter::TransformLayer>(matrix);
  PushLayer(layer);
  EngineLayer::MakeRetained(layer_handle, layer);

  if (old_layer && old_layer->Layer()) {
    layer->AssignOldLayer(old_layer->Layer().get());
  }
}

void SceneBuilder::pushClipRect(Dart_Handle layer_handle,
                                double left,
                                double right,
                                double top,
                                double bottom,
                                int clip_behavior,
                                const fml::RefPtr<EngineLayer>& old_layer) {
  DlRect clip_rect = DlRect::MakeLTRB(SafeNarrow(left), SafeNarrow(top),
                                      SafeNarrow(right), SafeNarrow(bottom));
  auto layer = std::make_shared<flutter::ClipRectLayer>(
      clip_rect, static_cast<flutter::Clip>(clip_behavior));
  PushLayer(layer);
  EngineLayer::MakeRetained(layer_handle, layer);

  if (old_layer && old_layer->Layer()) {
    layer->AssignOldLayer(old_layer->Layer().get());
  }
}

void SceneBuilder::pushClipRRect(Dart_Handle layer_handle,
                                 const RRect& rrect,
                                 int clip_behavior,
                                 const fml::RefPtr<EngineLayer>& old_layer) {
  auto layer = std::make_shared<flutter::ClipRRectLayer>(
      rrect.rrect, static_cast<flutter::Clip>(clip_behavior));
  PushLayer(layer);
  EngineLayer::MakeRetained(layer_handle, layer);

  if (old_layer && old_layer->Layer()) {
    layer->AssignOldLayer(old_layer->Layer().get());
  }
}

void SceneBuilder::pushClipRSuperellipse(
    Dart_Handle layer_handle,
    const RSuperellipse* rse,
    int clip_behavior,
    const fml::RefPtr<EngineLayer>& old_layer) {
  auto layer = std::make_shared<flutter::ClipRSuperellipseLayer>(
      rse->rsuperellipse(), static_cast<flutter::Clip>(clip_behavior));
  PushLayer(layer);
  EngineLayer::MakeRetained(layer_handle, layer);

  if (old_layer && old_layer->Layer()) {
    layer->AssignOldLayer(old_layer->Layer().get());
  }
}

void SceneBuilder::pushClipPath(Dart_Handle layer_handle,
                                const CanvasPath* path,
                                int clip_behavior,
                                const fml::RefPtr<EngineLayer>& old_layer) {
  flutter::Clip flutter_clip_behavior =
      static_cast<flutter::Clip>(clip_behavior);
  FML_DCHECK(flutter_clip_behavior != flutter::Clip::kNone);
  auto layer = std::make_shared<flutter::ClipPathLayer>(
      path->path(), static_cast<flutter::Clip>(flutter_clip_behavior));
  PushLayer(layer);
  EngineLayer::MakeRetained(layer_handle, layer);

  if (old_layer && old_layer->Layer()) {
    layer->AssignOldLayer(old_layer->Layer().get());
  }
}

void SceneBuilder::pushOpacity(Dart_Handle layer_handle,
                               int alpha,
                               double dx,
                               double dy,
                               const fml::RefPtr<EngineLayer>& old_layer) {
  auto layer = std::make_shared<flutter::OpacityLayer>(
      alpha, DlPoint(SafeNarrow(dx), SafeNarrow(dy)));
  PushLayer(layer);
  EngineLayer::MakeRetained(layer_handle, layer);

  if (old_layer && old_layer->Layer()) {
    layer->AssignOldLayer(old_layer->Layer().get());
  }
}

void SceneBuilder::pushColorFilter(Dart_Handle layer_handle,
                                   const ColorFilter* color_filter,
                                   const fml::RefPtr<EngineLayer>& old_layer) {
  auto layer =
      std::make_shared<flutter::ColorFilterLayer>(color_filter->filter());
  PushLayer(layer);
  EngineLayer::MakeRetained(layer_handle, layer);

  if (old_layer && old_layer->Layer()) {
    layer->AssignOldLayer(old_layer->Layer().get());
  }
}

void SceneBuilder::pushImageFilter(Dart_Handle layer_handle,
                                   const ImageFilter* image_filter,
                                   double dx,
                                   double dy,
                                   const fml::RefPtr<EngineLayer>& old_layer) {
  auto layer = std::make_shared<flutter::ImageFilterLayer>(
      image_filter->filter(DlTileMode::kDecal),
      DlPoint(SafeNarrow(dx), SafeNarrow(dy)));
  PushLayer(layer);
  EngineLayer::MakeRetained(layer_handle, layer);

  if (old_layer && old_layer->Layer()) {
    layer->AssignOldLayer(old_layer->Layer().get());
  }
}

void SceneBuilder::pushBackdropFilter(
    Dart_Handle layer_handle,
    ImageFilter* filter,
    int blend_mode,
    Dart_Handle backdrop_id,
    const fml::RefPtr<EngineLayer>& old_layer) {
  std::optional<int64_t> converted_backdrop_id;
  if (Dart_IsInteger(backdrop_id)) {
    int64_t out;
    Dart_IntegerToInt64(backdrop_id, &out);
    converted_backdrop_id = out;
  }

  auto layer = std::make_shared<flutter::BackdropFilterLayer>(
      filter->filter(DlTileMode::kMirror), static_cast<DlBlendMode>(blend_mode),
      converted_backdrop_id);
  PushLayer(layer);
  EngineLayer::MakeRetained(layer_handle, layer);

  if (old_layer && old_layer->Layer()) {
    layer->AssignOldLayer(old_layer->Layer().get());
  }
}

void SceneBuilder::pushShaderMask(Dart_Handle layer_handle,
                                  Shader* shader,
                                  double mask_rect_left,
                                  double mask_rect_right,
                                  double mask_rect_top,
                                  double mask_rect_bottom,
                                  int blend_mode,
                                  int filter_quality_index,
                                  const fml::RefPtr<EngineLayer>& old_layer) {
  DlRect rect = DlRect::MakeLTRB(
      SafeNarrow(mask_rect_left), SafeNarrow(mask_rect_top),
      SafeNarrow(mask_rect_right), SafeNarrow(mask_rect_bottom));
  auto sampling = ImageFilter::SamplingFromIndex(filter_quality_index);
  auto layer = std::make_shared<flutter::ShaderMaskLayer>(
      shader->shader(sampling), rect, static_cast<DlBlendMode>(blend_mode));
  PushLayer(layer);
  EngineLayer::MakeRetained(layer_handle, layer);

  if (old_layer && old_layer->Layer()) {
    layer->AssignOldLayer(old_layer->Layer().get());
  }
}

void SceneBuilder::addRetained(const fml::RefPtr<EngineLayer>& retained_layer) {
  AddLayer(retained_layer->Layer());
}

void SceneBuilder::pop() {
  PopLayer();
}

void SceneBuilder::addPicture(double dx,
                              double dy,
                              Picture* picture,
                              int hints) {
  if (!picture) {
    // Picture::dispose was called and it has been collected.
    return;
  }

  // Explicitly check for display_list, since the picture object might have
  // been disposed but not collected yet, but the display list is null.
  if (picture->display_list()) {
    auto layer = std::make_unique<flutter::DisplayListLayer>(
        DlPoint(SafeNarrow(dx), SafeNarrow(dy)), picture->display_list(),
        !!(hints & 1), !!(hints & 2));
    AddLayer(std::move(layer));
  }
}

void SceneBuilder::addTexture(double dx,
                              double dy,
                              double width,
                              double height,
                              int64_t texture_id,
                              bool freeze,
                              int filter_quality_index) {
  auto sampling = ImageFilter::SamplingFromIndex(filter_quality_index);
  auto layer = std::make_unique<flutter::TextureLayer>(
      DlPoint(SafeNarrow(dx), SafeNarrow(dy)),
      DlSize(SafeNarrow(width), SafeNarrow(height)), texture_id, freeze,
      sampling);
  AddLayer(std::move(layer));
}

void SceneBuilder::addPlatformView(double dx,
                                   double dy,
                                   double width,
                                   double height,
                                   int64_t view_id) {
  auto layer = std::make_unique<flutter::PlatformViewLayer>(
      DlPoint(SafeNarrow(dx), SafeNarrow(dy)),
      DlSize(SafeNarrow(width), SafeNarrow(height)), view_id);
  AddLayer(std::move(layer));
}

void SceneBuilder::addPerformanceOverlay(uint64_t enabled_options,
                                         double left,
                                         double right,
                                         double top,
                                         double bottom) {
  DlRect rect = DlRect::MakeLTRB(SafeNarrow(left), SafeNarrow(top),
                                 SafeNarrow(right), SafeNarrow(bottom));
  auto layer =
      std::make_unique<flutter::PerformanceOverlayLayer>(enabled_options);
  layer->set_paint_bounds(rect);
  AddLayer(std::move(layer));
}

void SceneBuilder::build(Dart_Handle scene_handle) {
  FML_DCHECK(layer_stack_.size() >= 1);

  Scene::create(scene_handle, std::move(layer_stack_[0]));
  layer_stack_.clear();
  ClearDartWrapper();  // may delete this object.
}

void SceneBuilder::AddLayer(std::shared_ptr<Layer> layer) {
  FML_DCHECK(layer);

  if (!layer_stack_.empty()) {
    layer_stack_.back()->Add(std::move(layer));
  }
}

void SceneBuilder::PushLayer(std::shared_ptr<ContainerLayer> layer) {
  AddLayer(layer);
  layer_stack_.push_back(std::move(layer));
}

void SceneBuilder::PopLayer() {
  // We never pop the root layer, so that AddLayer operations are always valid.
  if (layer_stack_.size() > 1) {
    layer_stack_.pop_back();
  }
}

}  // namespace flutter
