// 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/painting/canvas.h"

#include <cmath>

#include "flutter/display_list/dl_builder.h"
#include "flutter/lib/ui/floating_point.h"
#include "flutter/lib/ui/painting/image.h"
#include "flutter/lib/ui/painting/image_filter.h"
#include "flutter/lib/ui/painting/paint.h"
#include "flutter/lib/ui/ui_dart_state.h"
#include "flutter/lib/ui/window/platform_configuration.h"

using tonic::ToDart;

namespace flutter {

IMPLEMENT_WRAPPERTYPEINFO(ui, Canvas);

void Canvas::Create(Dart_Handle wrapper,
                    PictureRecorder* recorder,
                    double left,
                    double top,
                    double right,
                    double bottom) {
  UIDartState::ThrowIfUIOperationsProhibited();

  if (!recorder) {
    Dart_ThrowException(
        ToDart("Canvas constructor called with non-genuine PictureRecorder."));
    return;
  }

  fml::RefPtr<Canvas> canvas =
      fml::MakeRefCounted<Canvas>(recorder->BeginRecording(
          DlRect::MakeLTRB(SafeNarrow(left), SafeNarrow(top), SafeNarrow(right),
                           SafeNarrow(bottom))));
  recorder->set_canvas(canvas);
  canvas->AssociateWithDartWrapper(wrapper);
}

Canvas::Canvas(sk_sp<DisplayListBuilder> builder)
    : display_list_builder_(std::move(builder)) {}

Canvas::~Canvas() {}

void Canvas::save() {
  if (display_list_builder_) {
    builder()->Save();
  }
}

void Canvas::saveLayerWithoutBounds(Dart_Handle paint_objects,
                                    Dart_Handle paint_data) {
  Paint paint(paint_objects, paint_data);

  FML_DCHECK(paint.isNotNull());
  if (display_list_builder_) {
    DlPaint dl_paint;
    const DlPaint* save_paint =
        paint.paint(dl_paint, kSaveLayerWithPaintFlags, DlTileMode::kDecal);
    FML_DCHECK(save_paint);
    TRACE_EVENT0("flutter", "ui.Canvas::saveLayer (Recorded)");
    builder()->SaveLayer(std::nullopt, save_paint);
  }
}

void Canvas::saveLayer(double left,
                       double top,
                       double right,
                       double bottom,
                       Dart_Handle paint_objects,
                       Dart_Handle paint_data) {
  Paint paint(paint_objects, paint_data);

  FML_DCHECK(paint.isNotNull());
  DlRect bounds = DlRect::MakeLTRB(SafeNarrow(left), SafeNarrow(top),
                                   SafeNarrow(right), SafeNarrow(bottom));
  if (display_list_builder_) {
    DlPaint dl_paint;
    const DlPaint* save_paint =
        paint.paint(dl_paint, kSaveLayerWithPaintFlags, DlTileMode::kDecal);
    FML_DCHECK(save_paint);
    TRACE_EVENT0("flutter", "ui.Canvas::saveLayer (Recorded)");
    builder()->SaveLayer(bounds, save_paint);
  }
}

void Canvas::restore() {
  if (display_list_builder_) {
    builder()->Restore();
  }
}

int Canvas::getSaveCount() {
  if (display_list_builder_) {
    return builder()->GetSaveCount();
  } else {
    return 0;
  }
}

void Canvas::restoreToCount(int count) {
  if (display_list_builder_ && count < getSaveCount()) {
    builder()->RestoreToCount(count);
  }
}

void Canvas::translate(double dx, double dy) {
  if (display_list_builder_) {
    builder()->Translate(SafeNarrow(dx), SafeNarrow(dy));
  }
}

void Canvas::scale(double sx, double sy) {
  if (display_list_builder_) {
    builder()->Scale(SafeNarrow(sx), SafeNarrow(sy));
  }
}

void Canvas::rotate(double radians) {
  if (display_list_builder_) {
    builder()->Rotate(SafeNarrow(radians) * 180.0f / static_cast<float>(M_PI));
  }
}

void Canvas::skew(double sx, double sy) {
  if (display_list_builder_) {
    builder()->Skew(SafeNarrow(sx), SafeNarrow(sy));
  }
}

void Canvas::transform(const tonic::Float64List& matrix4) {
  // The Float array stored by Dart Matrix4 is in column-major order
  // DisplayList TransformFullPerspective takes row-major matrix order
  if (display_list_builder_) {
    // clang-format off
    builder()->TransformFullPerspective(
        SafeNarrow(matrix4[ 0]), SafeNarrow(matrix4[ 4]), SafeNarrow(matrix4[ 8]), SafeNarrow(matrix4[12]),
        SafeNarrow(matrix4[ 1]), SafeNarrow(matrix4[ 5]), SafeNarrow(matrix4[ 9]), SafeNarrow(matrix4[13]),
        SafeNarrow(matrix4[ 2]), SafeNarrow(matrix4[ 6]), SafeNarrow(matrix4[10]), SafeNarrow(matrix4[14]),
        SafeNarrow(matrix4[ 3]), SafeNarrow(matrix4[ 7]), SafeNarrow(matrix4[11]), SafeNarrow(matrix4[15]));
    // clang-format on
  }
}

void Canvas::getTransform(Dart_Handle matrix4_handle) {
  if (display_list_builder_) {
    // The Float array stored by DlMatrix is in column-major order
    DlMatrix matrix = builder()->GetMatrix();
    auto matrix4 = tonic::Float64List(matrix4_handle);
    for (int i = 0; i < 16; i++) {
      matrix4[i] = matrix.m[i];
    }
  }
}

void Canvas::clipRect(double left,
                      double top,
                      double right,
                      double bottom,
                      DlClipOp clipOp,
                      bool doAntiAlias) {
  if (display_list_builder_) {
    builder()->ClipRect(DlRect::MakeLTRB(SafeNarrow(left), SafeNarrow(top),
                                         SafeNarrow(right), SafeNarrow(bottom)),
                        clipOp, doAntiAlias);
  }
}

void Canvas::clipRRect(const RRect& rrect, bool doAntiAlias) {
  if (display_list_builder_) {
    builder()->ClipRoundRect(rrect.rrect, DlClipOp::kIntersect, doAntiAlias);
  }
}

void Canvas::clipRSuperellipse(const RSuperellipse* rse, bool doAntiAlias) {
  if (display_list_builder_) {
    builder()->ClipRoundSuperellipse(rse->rsuperellipse(), DlClipOp::kIntersect,
                                     doAntiAlias);
  }
}

void Canvas::clipPath(const CanvasPath* path, bool doAntiAlias) {
  if (!path) {
    Dart_ThrowException(
        ToDart("Canvas.clipPath called with non-genuine Path."));
    return;
  }
  if (display_list_builder_) {
    builder()->ClipPath(path->path(), DlClipOp::kIntersect, doAntiAlias);
  }
}

void Canvas::getDestinationClipBounds(Dart_Handle rect_handle) {
  if (display_list_builder_) {
    auto rect = tonic::Float64List(rect_handle);
    DlRect bounds = builder()->GetDestinationClipCoverage();
    rect[0] = bounds.GetLeft();
    rect[1] = bounds.GetTop();
    rect[2] = bounds.GetRight();
    rect[3] = bounds.GetBottom();
  }
}

void Canvas::getLocalClipBounds(Dart_Handle rect_handle) {
  if (display_list_builder_) {
    auto rect = tonic::Float64List(rect_handle);
    DlRect bounds = builder()->GetLocalClipCoverage();
    rect[0] = bounds.GetLeft();
    rect[1] = bounds.GetTop();
    rect[2] = bounds.GetRight();
    rect[3] = bounds.GetBottom();
  }
}

void Canvas::drawColor(uint32_t color, DlBlendMode blend_mode) {
  if (display_list_builder_) {
    builder()->DrawColor(DlColor(color), blend_mode);
  }
}

void Canvas::drawLine(double x1,
                      double y1,
                      double x2,
                      double y2,
                      Dart_Handle paint_objects,
                      Dart_Handle paint_data) {
  Paint paint(paint_objects, paint_data);

  FML_DCHECK(paint.isNotNull());
  if (display_list_builder_) {
    DlPaint dl_paint;
    paint.paint(dl_paint, kDrawLineFlags, DlTileMode::kDecal);
    builder()->DrawLine(DlPoint(SafeNarrow(x1), SafeNarrow(y1)),
                        DlPoint(SafeNarrow(x2), SafeNarrow(y2)), dl_paint);
  }
}

void Canvas::drawPaint(Dart_Handle paint_objects, Dart_Handle paint_data) {
  Paint paint(paint_objects, paint_data);

  FML_DCHECK(paint.isNotNull());
  if (display_list_builder_) {
    DlPaint dl_paint;
    paint.paint(dl_paint, kDrawPaintFlags, DlTileMode::kClamp);
    std::shared_ptr<DlImageFilter> filter = dl_paint.getImageFilter();
    if (filter && !filter->asColorFilter()) {
      // drawPaint does an implicit saveLayer if an DlImageFilter is
      // present that cannot be replaced by an DlColorFilter.
      TRACE_EVENT0("flutter", "ui.Canvas::saveLayer (Recorded)");
    }
    builder()->DrawPaint(dl_paint);
  }
}

void Canvas::drawRect(double left,
                      double top,
                      double right,
                      double bottom,
                      Dart_Handle paint_objects,
                      Dart_Handle paint_data) {
  Paint paint(paint_objects, paint_data);

  FML_DCHECK(paint.isNotNull());
  if (display_list_builder_) {
    DlPaint dl_paint;
    paint.paint(dl_paint, kDrawRectFlags, DlTileMode::kDecal);
    builder()->DrawRect(DlRect::MakeLTRB(SafeNarrow(left), SafeNarrow(top),
                                         SafeNarrow(right), SafeNarrow(bottom)),
                        dl_paint);
  }
}

void Canvas::drawRRect(const RRect& rrect,
                       Dart_Handle paint_objects,
                       Dart_Handle paint_data) {
  Paint paint(paint_objects, paint_data);

  FML_DCHECK(paint.isNotNull());
  if (display_list_builder_) {
    DlPaint dl_paint;
    paint.paint(dl_paint, kDrawRRectFlags, DlTileMode::kDecal);
    builder()->DrawRoundRect(rrect.rrect, dl_paint);
  }
}

void Canvas::drawDRRect(const RRect& outer,
                        const RRect& inner,
                        Dart_Handle paint_objects,
                        Dart_Handle paint_data) {
  Paint paint(paint_objects, paint_data);

  FML_DCHECK(paint.isNotNull());
  if (display_list_builder_) {
    DlPaint dl_paint;
    paint.paint(dl_paint, kDrawDRRectFlags, DlTileMode::kDecal);
    builder()->DrawDiffRoundRect(outer.rrect, inner.rrect, dl_paint);
  }
}

void Canvas::drawRSuperellipse(const RSuperellipse* rse,
                               Dart_Handle paint_objects,
                               Dart_Handle paint_data) {
  Paint paint(paint_objects, paint_data);

  FML_DCHECK(paint.isNotNull());
  if (display_list_builder_) {
    DlPaint dl_paint;
    paint.paint(dl_paint, kDrawDRRectFlags, DlTileMode::kDecal);
    builder()->DrawRoundSuperellipse(rse->rsuperellipse(), dl_paint);
  }
}

void Canvas::drawOval(double left,
                      double top,
                      double right,
                      double bottom,
                      Dart_Handle paint_objects,
                      Dart_Handle paint_data) {
  Paint paint(paint_objects, paint_data);

  FML_DCHECK(paint.isNotNull());
  if (display_list_builder_) {
    DlPaint dl_paint;
    paint.paint(dl_paint, kDrawOvalFlags, DlTileMode::kDecal);
    builder()->DrawOval(DlRect::MakeLTRB(SafeNarrow(left), SafeNarrow(top),
                                         SafeNarrow(right), SafeNarrow(bottom)),
                        dl_paint);
  }
}

void Canvas::drawCircle(double x,
                        double y,
                        double radius,
                        Dart_Handle paint_objects,
                        Dart_Handle paint_data) {
  Paint paint(paint_objects, paint_data);

  FML_DCHECK(paint.isNotNull());
  if (display_list_builder_) {
    DlPaint dl_paint;
    paint.paint(dl_paint, kDrawCircleFlags, DlTileMode::kDecal);
    builder()->DrawCircle(DlPoint(SafeNarrow(x), SafeNarrow(y)),
                          SafeNarrow(radius), dl_paint);
  }
}

void Canvas::drawArc(double left,
                     double top,
                     double right,
                     double bottom,
                     double startAngle,
                     double sweepAngle,
                     bool useCenter,
                     Dart_Handle paint_objects,
                     Dart_Handle paint_data) {
  Paint paint(paint_objects, paint_data);

  FML_DCHECK(paint.isNotNull());
  if (display_list_builder_) {
    DlPaint dl_paint;
    paint.paint(dl_paint,
                useCenter ? kDrawArcWithCenterFlags : kDrawArcNoCenterFlags,
                DlTileMode::kDecal);
    builder()->DrawArc(
        DlRect::MakeLTRB(SafeNarrow(left), SafeNarrow(top), SafeNarrow(right),
                         SafeNarrow(bottom)),
        SafeNarrow(startAngle) * 180.0f / static_cast<float>(M_PI),
        SafeNarrow(sweepAngle) * 180.0f / static_cast<float>(M_PI), useCenter,
        dl_paint);
  }
}

void Canvas::drawPath(const CanvasPath* path,
                      Dart_Handle paint_objects,
                      Dart_Handle paint_data) {
  Paint paint(paint_objects, paint_data);

  FML_DCHECK(paint.isNotNull());
  if (!path) {
    Dart_ThrowException(
        ToDart("Canvas.drawPath called with non-genuine Path."));
    return;
  }
  if (display_list_builder_) {
    DlPaint dl_paint;
    paint.paint(dl_paint, kDrawPathFlags, DlTileMode::kDecal);
    builder()->DrawPath(path->path(), dl_paint);
  }
}

Dart_Handle Canvas::drawImage(const CanvasImage* image,
                              double x,
                              double y,
                              Dart_Handle paint_objects,
                              Dart_Handle paint_data,
                              int filterQualityIndex) {
  Paint paint(paint_objects, paint_data);

  FML_DCHECK(paint.isNotNull());
  if (!image) {
    return ToDart("Canvas.drawImage called with non-genuine Image.");
  }

  auto dl_image = image->image();
  if (!dl_image) {
    return Dart_Null();
  }
  auto error = dl_image->get_error();
  if (error) {
    return ToDart(error.value());
  }

  auto sampling = ImageFilter::SamplingFromIndex(filterQualityIndex);
  if (display_list_builder_) {
    DlPaint dl_paint;
    const DlPaint* opt_paint =
        paint.paint(dl_paint, kDrawImageWithPaintFlags, DlTileMode::kClamp);
    builder()->DrawImage(dl_image, DlPoint(SafeNarrow(x), SafeNarrow(y)),
                         sampling, opt_paint);
  }
  return Dart_Null();
}

Dart_Handle Canvas::drawImageRect(const CanvasImage* image,
                                  double src_left,
                                  double src_top,
                                  double src_right,
                                  double src_bottom,
                                  double dst_left,
                                  double dst_top,
                                  double dst_right,
                                  double dst_bottom,
                                  Dart_Handle paint_objects,
                                  Dart_Handle paint_data,
                                  int filterQualityIndex) {
  Paint paint(paint_objects, paint_data);

  FML_DCHECK(paint.isNotNull());
  if (!image) {
    return ToDart("Canvas.drawImageRect called with non-genuine Image.");
  }

  auto dl_image = image->image();
  if (!dl_image) {
    return Dart_Null();
  }
  auto error = dl_image->get_error();
  if (error) {
    return ToDart(error.value());
  }

  DlRect src = DlRect::MakeLTRB(SafeNarrow(src_left), SafeNarrow(src_top),
                                SafeNarrow(src_right), SafeNarrow(src_bottom));
  DlRect dst = DlRect::MakeLTRB(SafeNarrow(dst_left), SafeNarrow(dst_top),
                                SafeNarrow(dst_right), SafeNarrow(dst_bottom));
  auto sampling = ImageFilter::SamplingFromIndex(filterQualityIndex);
  if (display_list_builder_) {
    DlPaint dl_paint;
    const DlPaint* opt_paint =
        paint.paint(dl_paint, kDrawImageRectWithPaintFlags, DlTileMode::kClamp);
    builder()->DrawImageRect(dl_image, src, dst, sampling, opt_paint,
                             DlSrcRectConstraint::kFast);
  }
  return Dart_Null();
}

Dart_Handle Canvas::drawImageNine(const CanvasImage* image,
                                  double center_left,
                                  double center_top,
                                  double center_right,
                                  double center_bottom,
                                  double dst_left,
                                  double dst_top,
                                  double dst_right,
                                  double dst_bottom,
                                  Dart_Handle paint_objects,
                                  Dart_Handle paint_data,
                                  int bitmapSamplingIndex) {
  Paint paint(paint_objects, paint_data);

  FML_DCHECK(paint.isNotNull());
  if (!image) {
    return ToDart("Canvas.drawImageNine called with non-genuine Image.");
  }
  auto dl_image = image->image();
  if (!dl_image) {
    return Dart_Null();
  }
  auto error = dl_image->get_error();
  if (error) {
    return ToDart(error.value());
  }

  DlRect center =
      DlRect::MakeLTRB(SafeNarrow(center_left), SafeNarrow(center_top),
                       SafeNarrow(center_right), SafeNarrow(center_bottom));
  DlIRect icenter = DlIRect::Round(center);
  DlRect dst = DlRect::MakeLTRB(SafeNarrow(dst_left), SafeNarrow(dst_top),
                                SafeNarrow(dst_right), SafeNarrow(dst_bottom));
  auto filter = ImageFilter::FilterModeFromIndex(bitmapSamplingIndex);
  if (display_list_builder_) {
    DlPaint dl_paint;
    const DlPaint* opt_paint =
        paint.paint(dl_paint, kDrawImageNineWithPaintFlags, DlTileMode::kClamp);
    builder()->DrawImageNine(dl_image, icenter, dst, filter, opt_paint);
  }
  return Dart_Null();
}

void Canvas::drawPicture(Picture* picture) {
  if (!picture) {
    Dart_ThrowException(
        ToDart("Canvas.drawPicture called with non-genuine Picture."));
    return;
  }
  if (picture->display_list()) {
    if (display_list_builder_) {
      builder()->DrawDisplayList(picture->display_list());
    }
  } else {
    FML_DCHECK(false);
  }
}

void Canvas::drawPoints(Dart_Handle paint_objects,
                        Dart_Handle paint_data,
                        DlPointMode point_mode,
                        const tonic::Float32List& points) {
  Paint paint(paint_objects, paint_data);

  static_assert(sizeof(DlPoint) == sizeof(float) * 2,
                "DlPoint doesn't use floats.");

  FML_DCHECK(paint.isNotNull());
  if (display_list_builder_) {
    DlPaint dl_paint;
    switch (point_mode) {
      case DlPointMode::kPoints:
        paint.paint(dl_paint, kDrawPointsAsPointsFlags, DlTileMode::kDecal);
        break;
      case DlPointMode::kLines:
        paint.paint(dl_paint, kDrawPointsAsLinesFlags, DlTileMode::kDecal);
        break;
      case DlPointMode::kPolygon:
        paint.paint(dl_paint, kDrawPointsAsPolygonFlags, DlTileMode::kDecal);
        break;
    }
    builder()->DrawPoints(point_mode,
                          points.num_elements() / 2,  // DlPoints have 2 floats
                          reinterpret_cast<const DlPoint*>(points.data()),
                          dl_paint);
  }
}

void Canvas::drawVertices(const Vertices* vertices,
                          DlBlendMode blend_mode,
                          Dart_Handle paint_objects,
                          Dart_Handle paint_data) {
  Paint paint(paint_objects, paint_data);

  if (!vertices) {
    Dart_ThrowException(
        ToDart("Canvas.drawVertices called with non-genuine Vertices."));
    return;
  }
  FML_DCHECK(paint.isNotNull());
  if (display_list_builder_) {
    DlPaint dl_paint;
    paint.paint(dl_paint, kDrawVerticesFlags, DlTileMode::kDecal);
    builder()->DrawVertices(vertices->vertices(), blend_mode, dl_paint);
  }
}

Dart_Handle Canvas::drawAtlas(Dart_Handle paint_objects,
                              Dart_Handle paint_data,
                              int filterQualityIndex,
                              CanvasImage* atlas,
                              Dart_Handle transforms_handle,
                              Dart_Handle rects_handle,
                              Dart_Handle colors_handle,
                              DlBlendMode blend_mode,
                              Dart_Handle cull_rect_handle) {
  Paint paint(paint_objects, paint_data);

  if (!atlas) {
    return ToDart(
        "Canvas.drawAtlas or Canvas.drawRawAtlas called with "
        "non-genuine Image.");
  }

  auto dl_image = atlas->image();
  auto error = dl_image->get_error();
  if (error) {
    return ToDart(error.value());
  }

  static_assert(sizeof(DlRSTransform) == sizeof(float) * 4,
                "DlRSTransform doesn't use floats.");
  static_assert(sizeof(DlRect) == sizeof(float) * 4,
                "DlRect doesn't use floats.");

  auto sampling = ImageFilter::SamplingFromIndex(filterQualityIndex);

  FML_DCHECK(paint.isNotNull());
  if (display_list_builder_) {
    tonic::Float32List transforms(transforms_handle);
    tonic::Float32List rects(rects_handle);
    tonic::Int32List colors(colors_handle);
    tonic::Float32List cull_rect(cull_rect_handle);

    std::vector<DlColor> dl_color(colors.num_elements());
    size_t count = colors.num_elements();
    for (size_t i = 0; i < count; i++) {
      dl_color[i] = DlColor(colors[i]);
    }

    DlPaint dl_paint;
    const DlPaint* opt_paint =
        paint.paint(dl_paint, kDrawAtlasWithPaintFlags, DlTileMode::kClamp);
    builder()->DrawAtlas(
        dl_image, reinterpret_cast<const DlRSTransform*>(transforms.data()),
        reinterpret_cast<const DlRect*>(rects.data()), dl_color.data(),
        rects.num_elements() / 4,  // DlRect have four floats.
        blend_mode, sampling, reinterpret_cast<const DlRect*>(cull_rect.data()),
        opt_paint);
  }
  return Dart_Null();
}

void Canvas::drawShadow(const CanvasPath* path,
                        uint32_t color,
                        double elevation,
                        bool transparentOccluder) {
  if (!path) {
    Dart_ThrowException(
        ToDart("Canvas.drawShader called with non-genuine Path."));
    return;
  }

  // Not using SafeNarrow because DPR will always be a relatively small number.
  const ViewportMetrics* metrics =
      UIDartState::Current()->platform_configuration()->GetMetrics(0);
  DlScalar dpr;
  // TODO(dkwingsmt): We should support rendering shadows on non-implicit views.
  // However, currently this method has no way to get the target view ID.
  if (metrics == nullptr) {
    dpr = 1.0f;
  } else {
    dpr = static_cast<float>(metrics->device_pixel_ratio);
  }
  if (display_list_builder_) {
    builder()->DrawShadow(path->path(), DlColor(color), SafeNarrow(elevation),
                          transparentOccluder, dpr);
  }
}

void Canvas::Invalidate() {
  display_list_builder_ = nullptr;
  if (dart_wrapper()) {
    ClearDartWrapper();
  }
}

}  // namespace flutter
