// 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"

// ACE PC preivew.
#ifndef WINDOWS_PLATFORM
// It cannot be passed to the sub-include of math.h, so define it in gn.
#define _USE_MATH_DEFINES
#endif

#include <math.h>

#include "flutter/flow/layers/physical_shape_layer.h"
#include "flutter/lib/ui/painting/image.h"
#include "flutter/lib/ui/painting/matrix.h"
#include "flutter/lib/ui/ui_dart_state.h"
#include "flutter/lib/ui/window/window.h"
#include "include/core/SkBitmap.h"
#include "include/core/SkCanvas.h"
#include "include/core/SkRSXform.h"

using tonic::ToDart;

namespace flutter {

IMPLEMENT_WRAPPERTYPEINFO(ui, Canvas);

#define FOR_EACH_BINDING(V)         \
  V(Canvas, save)                   \
  V(Canvas, saveLayerWithoutBounds) \
  V(Canvas, saveLayer)              \
  V(Canvas, restore)                \
  V(Canvas, getSaveCount)           \
  V(Canvas, translate)              \
  V(Canvas, scale)                  \
  V(Canvas, rotate)                 \
  V(Canvas, skew)                   \
  V(Canvas, transform)              \
  V(Canvas, clipRect)               \
  V(Canvas, clipRRect)              \
  V(Canvas, clipPath)               \
  V(Canvas, drawColor)              \
  V(Canvas, drawLine)               \
  V(Canvas, drawPaint)              \
  V(Canvas, drawRect)               \
  V(Canvas, drawRRect)              \
  V(Canvas, drawDRRect)             \
  V(Canvas, drawOval)               \
  V(Canvas, drawCircle)             \
  V(Canvas, drawArc)                \
  V(Canvas, drawPath)               \
  V(Canvas, drawImage)              \
  V(Canvas, drawImageRect)          \
  V(Canvas, drawImageNine)          \
  V(Canvas, drawPicture)            \
  V(Canvas, drawPoints)             \
  V(Canvas, drawVertices)           \
  V(Canvas, drawAtlas)              \
  V(Canvas, drawShadow)

FOR_EACH_BINDING(DART_NATIVE_CALLBACK)

void Canvas::RegisterNatives(tonic::DartLibraryNatives* natives) {}

fml::RefPtr<Canvas> Canvas::Create(PictureRecorder* recorder,
                                   double left,
                                   double top,
                                   double right,
                                   double bottom) {
  if (!recorder) {
    FML_LOG(ERROR)
        << "Canvas constructor called with non-genuine PictureRecorder.";
    return nullptr;
  }

  FML_DCHECK(!recorder->isRecording());  // verified by Dart code
  fml::RefPtr<Canvas> canvas = fml::MakeRefCounted<Canvas>(
      recorder->BeginRecording(SkRect::MakeLTRB(left, top, right, bottom)));
  recorder->set_canvas(canvas);
  return canvas;
}

Canvas::Canvas(SkCanvas* canvas) : canvas_(canvas) {}

Canvas::~Canvas() {}

void Canvas::save() {
  if (!canvas_)
    return;
  canvas_->save();
}

void Canvas::saveLayerWithoutBounds(const Paint& paint,
                                    const PaintData& paint_data) {
  if (!canvas_)
    return;
  canvas_->saveLayer(nullptr, paint.paint());
}

void Canvas::saveLayer(double left,
                       double top,
                       double right,
                       double bottom,
                       const Paint& paint,
                       const PaintData& paint_data) {
  if (!canvas_)
    return;
  SkRect bounds = SkRect::MakeLTRB(left, top, right, bottom);
  canvas_->saveLayer(&bounds, paint.paint());
}

void Canvas::restore() {
  if (!canvas_)
    return;
  canvas_->restore();
}

int Canvas::getSaveCount() {
  if (!canvas_)
    return 0;
  return canvas_->getSaveCount();
}

void Canvas::translate(double dx, double dy) {
  if (!canvas_)
    return;
  canvas_->translate(dx, dy);
}

void Canvas::scale(double sx, double sy) {
  if (!canvas_)
    return;
  canvas_->scale(sx, sy);
}

void Canvas::rotate(double radians) {
  if (!canvas_)
    return;
  canvas_->rotate(radians * 180.0 / M_PI);
}

void Canvas::skew(double sx, double sy) {
  if (!canvas_)
    return;
  canvas_->skew(sx, sy);
}

void Canvas::transform(const tonic::Float64List& matrix4) {
  if (!canvas_)
    return;
  canvas_->concat(ToSkMatrix(matrix4));
}

void Canvas::clipRect(double left,
                      double top,
                      double right,
                      double bottom,
                      SkClipOp clipOp,
                      bool doAntiAlias) {
  if (!canvas_)
    return;
  canvas_->clipRect(SkRect::MakeLTRB(left, top, right, bottom), clipOp,
                    doAntiAlias);
}

void Canvas::clipRRect(const RRect& rrect, bool doAntiAlias) {
  if (!canvas_)
    return;
  canvas_->clipRRect(rrect.sk_rrect, doAntiAlias);
}

void Canvas::clipPath(const CanvasPath* path, bool doAntiAlias) {
  if (!canvas_)
    return;
  if (!path) {
    FML_LOG(ERROR) << "Canvas.clipPath called with non-genuine Path.";
    return;
  }

  canvas_->clipPath(path->path(), doAntiAlias);
}

void Canvas::drawColor(SkColor color, SkBlendMode blend_mode) {
  if (!canvas_)
    return;
  canvas_->drawColor(color, blend_mode);
}

void Canvas::drawLine(double x1,
                      double y1,
                      double x2,
                      double y2,
                      const Paint& paint,
                      const PaintData& paint_data) {
  if (!canvas_)
    return;
  canvas_->drawLine(x1, y1, x2, y2, *paint.paint());
}

void Canvas::drawPaint(const Paint& paint, const PaintData& paint_data) {
  if (!canvas_)
    return;
  canvas_->drawPaint(*paint.paint());
}

void Canvas::drawRect(double left,
                      double top,
                      double right,
                      double bottom,
                      const Paint& paint,
                      const PaintData& paint_data) {
  if (!canvas_)
    return;
  canvas_->drawRect(SkRect::MakeLTRB(left, top, right, bottom), *paint.paint());
}

void Canvas::drawRRect(const RRect& rrect,
                       const Paint& paint,
                       const PaintData& paint_data) {
  if (!canvas_)
    return;
  canvas_->drawRRect(rrect.sk_rrect, *paint.paint());
}

void Canvas::drawDRRect(const RRect& outer,
                        const RRect& inner,
                        const Paint& paint,
                        const PaintData& paint_data) {
  if (!canvas_)
    return;
  canvas_->drawDRRect(outer.sk_rrect, inner.sk_rrect, *paint.paint());
}

void Canvas::drawOval(double left,
                      double top,
                      double right,
                      double bottom,
                      const Paint& paint,
                      const PaintData& paint_data) {
  if (!canvas_)
    return;
  canvas_->drawOval(SkRect::MakeLTRB(left, top, right, bottom), *paint.paint());
}

void Canvas::drawCircle(double x,
                        double y,
                        double radius,
                        const Paint& paint,
                        const PaintData& paint_data) {
  if (!canvas_)
    return;
  canvas_->drawCircle(x, y, radius, *paint.paint());
}

void Canvas::drawArc(double left,
                     double top,
                     double right,
                     double bottom,
                     double startAngle,
                     double sweepAngle,
                     bool useCenter,
                     const Paint& paint,
                     const PaintData& paint_data) {
  if (!canvas_)
    return;
  canvas_->drawArc(SkRect::MakeLTRB(left, top, right, bottom),
                   startAngle * 180.0 / M_PI, sweepAngle * 180.0 / M_PI,
                   useCenter, *paint.paint());
}

void Canvas::drawPath(const CanvasPath* path,
                      const Paint& paint,
                      const PaintData& paint_data) {
  if (!canvas_)
    return;
  if (!path) {
    FML_LOG(ERROR) << "Canvas.drawPath called with non-genuine Path.";
    return;
  }

  canvas_->drawPath(path->path(), *paint.paint());
}

void Canvas::drawImage(const CanvasImage* image,
                       double x,
                       double y,
                       const Paint& paint,
                       const PaintData& paint_data) {
  if (!canvas_)
    return;
  if (!image) {
    FML_LOG(ERROR) << "Canvas.drawImage called with non-genuine Image.";
    return;
  }
  canvas_->drawImage(image->image(), x, y, paint.paint());
}

void 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,
                           const Paint& paint,
                           const PaintData& paint_data) {
  if (!canvas_)
    return;
  if (!image) {
    FML_LOG(ERROR) << "Canvas.drawImageRect called with non-genuine Image.";
    return;
  }

  SkRect src = SkRect::MakeLTRB(src_left, src_top, src_right, src_bottom);
  SkRect dst = SkRect::MakeLTRB(dst_left, dst_top, dst_right, dst_bottom);
  canvas_->drawImageRect(image->image(), src, dst, paint.paint(),
                         SkCanvas::kFast_SrcRectConstraint);
}

void 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,
                           const Paint& paint,
                           const PaintData& paint_data) {
  if (!canvas_)
    return;
  if (!image) {
    FML_LOG(ERROR) << "Canvas.drawImageNine called with non-genuine Image.";
    return;
  }
  SkRect center =
      SkRect::MakeLTRB(center_left, center_top, center_right, center_bottom);
  SkIRect icenter;
  center.round(&icenter);
  SkRect dst = SkRect::MakeLTRB(dst_left, dst_top, dst_right, dst_bottom);
  canvas_->drawImageNine(image->image(), icenter, dst, paint.paint());
}

void Canvas::drawPicture(Picture* picture) {
  if (!canvas_)
    return;
  if (!picture) {
    FML_LOG(ERROR) << "Canvas.drawPicture called with non-genuine Picture.";
    return;
  }
  canvas_->drawPicture(picture->picture().get());
}

void Canvas::drawPoints(const Paint& paint,
                        const PaintData& paint_data,
                        SkCanvas::PointMode point_mode,
                        const tonic::Float32List& points) {
  if (!canvas_)
    return;

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

  canvas_->drawPoints(point_mode,
                      points.size() / 2,  // SkPoints have two floats.
                      reinterpret_cast<const SkPoint*>(points.data()),
                      *paint.paint());
}

void Canvas::drawVertices(const Vertices* vertices,
                          SkBlendMode blend_mode,
                          const Paint& paint,
                          const PaintData& paint_data) {
  if (!canvas_)
    return;
  if (!vertices) {
    FML_LOG(ERROR) << "Canvas.drawVertices called with non-genuine Vertices.";
    return;
  }
  canvas_->drawVertices(vertices->vertices(), blend_mode, *paint.paint());
}

void Canvas::drawAtlas(const Paint& paint,
                       const PaintData& paint_data,
                       CanvasImage* atlas,
                       const tonic::Float32List& transforms,
                       const tonic::Float32List& rects,
                       const tonic::Int32List& colors,
                       SkBlendMode blend_mode,
                       const tonic::Float32List& cull_rect) {
  if (!canvas_)
    return;
  if (!atlas) {
    FML_LOG(ERROR) << "Canvas.drawAtlas or Canvas.drawRawAtlas called with "
                      "non-genuine Image.";
    return;
  }
  sk_sp<SkImage> skImage = atlas->image();

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

  canvas_->drawAtlas(
      skImage.get(), reinterpret_cast<const SkRSXform*>(transforms.data()),
      reinterpret_cast<const SkRect*>(rects.data()),
      reinterpret_cast<const SkColor*>(colors.data()),
      rects.size() / 4,  // SkRect have four floats.
      blend_mode, reinterpret_cast<const SkRect*>(cull_rect.data()),
      paint.paint());
}

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

  SkScalar dpr =
      UIDartState::Current()->window()->viewport_metrics().device_pixel_ratio;
  flutter::PhysicalShapeLayer::DrawShadow(canvas_, path->path(), color,
                                          elevation, transparentOccluder, dpr);
}

void Canvas::Clear() {
  canvas_ = nullptr;
}

bool Canvas::IsRecording() const {
  return !!canvas_;
}

}  // namespace flutter