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

#include <cmath>

#include "flutter/lib/ui/floating_point.h"
#include "flutter/lib/ui/painting/matrix.h"
#include "flutter/lib/ui/ui_dart_state.h"
#include "third_party/tonic/converter/dart_converter.h"
#include "third_party/tonic/dart_args.h"
#include "third_party/tonic/dart_binding_macros.h"
#include "third_party/tonic/dart_library_natives.h"

using tonic::ToDart;

namespace flutter {

typedef CanvasPath Path;

IMPLEMENT_WRAPPERTYPEINFO(ui, Path);

CanvasPath::CanvasPath() {
  sk_path_.setIsVolatile(
      !UIDartState::Current()->IsDeterministicRenderingEnabled());
  resetVolatility();
}

CanvasPath::~CanvasPath() = default;

void CanvasPath::resetVolatility() {
  dl_path_.reset();
}

int CanvasPath::getFillType() {
  return static_cast<int>(sk_path_.getFillType());
}

void CanvasPath::setFillType(int fill_type) {
  sk_path_.setFillType(static_cast<SkPathFillType>(fill_type));
  resetVolatility();
}

void CanvasPath::moveTo(double x, double y) {
  sk_path_.moveTo(SafeNarrow(x), SafeNarrow(y));
  resetVolatility();
}

void CanvasPath::relativeMoveTo(double x, double y) {
  sk_path_.rMoveTo(SafeNarrow(x), SafeNarrow(y));
  resetVolatility();
}

void CanvasPath::lineTo(double x, double y) {
  sk_path_.lineTo(SafeNarrow(x), SafeNarrow(y));
  resetVolatility();
}

void CanvasPath::relativeLineTo(double x, double y) {
  sk_path_.rLineTo(SafeNarrow(x), SafeNarrow(y));
  resetVolatility();
}

void CanvasPath::quadraticBezierTo(double x1, double y1, double x2, double y2) {
  sk_path_.quadTo(SafeNarrow(x1), SafeNarrow(y1), SafeNarrow(x2),
                  SafeNarrow(y2));
  resetVolatility();
}

void CanvasPath::relativeQuadraticBezierTo(double x1,
                                           double y1,
                                           double x2,
                                           double y2) {
  sk_path_.rQuadTo(SafeNarrow(x1), SafeNarrow(y1), SafeNarrow(x2),
                   SafeNarrow(y2));
  resetVolatility();
}

void CanvasPath::cubicTo(double x1,
                         double y1,
                         double x2,
                         double y2,
                         double x3,
                         double y3) {
  sk_path_.cubicTo(SafeNarrow(x1), SafeNarrow(y1), SafeNarrow(x2),
                   SafeNarrow(y2), SafeNarrow(x3), SafeNarrow(y3));
  resetVolatility();
}

void CanvasPath::relativeCubicTo(double x1,
                                 double y1,
                                 double x2,
                                 double y2,
                                 double x3,
                                 double y3) {
  sk_path_.rCubicTo(SafeNarrow(x1), SafeNarrow(y1), SafeNarrow(x2),
                    SafeNarrow(y2), SafeNarrow(x3), SafeNarrow(y3));
  resetVolatility();
}

void CanvasPath::conicTo(double x1, double y1, double x2, double y2, double w) {
  sk_path_.conicTo(SafeNarrow(x1), SafeNarrow(y1), SafeNarrow(x2),
                   SafeNarrow(y2), SafeNarrow(w));
  resetVolatility();
}

void CanvasPath::relativeConicTo(double x1,
                                 double y1,
                                 double x2,
                                 double y2,
                                 double w) {
  sk_path_.rConicTo(SafeNarrow(x1), SafeNarrow(y1), SafeNarrow(x2),
                    SafeNarrow(y2), SafeNarrow(w));
  resetVolatility();
}

void CanvasPath::arcTo(double left,
                       double top,
                       double right,
                       double bottom,
                       double startAngle,
                       double sweepAngle,
                       bool forceMoveTo) {
  sk_path_.arcTo(SkRect::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),
                 forceMoveTo);
  resetVolatility();
}

void CanvasPath::arcToPoint(double arcEndX,
                            double arcEndY,
                            double radiusX,
                            double radiusY,
                            double xAxisRotation,
                            bool isLargeArc,
                            bool isClockwiseDirection) {
  const auto arcSize = isLargeArc ? SkPath::ArcSize::kLarge_ArcSize
                                  : SkPath::ArcSize::kSmall_ArcSize;
  const auto direction =
      isClockwiseDirection ? SkPathDirection::kCW : SkPathDirection::kCCW;

  sk_path_.arcTo(SafeNarrow(radiusX), SafeNarrow(radiusY),
                 SafeNarrow(xAxisRotation), arcSize, direction,
                 SafeNarrow(arcEndX), SafeNarrow(arcEndY));
  resetVolatility();
}

void CanvasPath::relativeArcToPoint(double arcEndDeltaX,
                                    double arcEndDeltaY,
                                    double radiusX,
                                    double radiusY,
                                    double xAxisRotation,
                                    bool isLargeArc,
                                    bool isClockwiseDirection) {
  const auto arcSize = isLargeArc ? SkPath::ArcSize::kLarge_ArcSize
                                  : SkPath::ArcSize::kSmall_ArcSize;
  const auto direction =
      isClockwiseDirection ? SkPathDirection::kCW : SkPathDirection::kCCW;
  sk_path_.rArcTo(SafeNarrow(radiusX), SafeNarrow(radiusY),
                  SafeNarrow(xAxisRotation), arcSize, direction,
                  SafeNarrow(arcEndDeltaX), SafeNarrow(arcEndDeltaY));
  resetVolatility();
}

void CanvasPath::addRect(double left, double top, double right, double bottom) {
  sk_path_.addRect(SkRect::MakeLTRB(SafeNarrow(left), SafeNarrow(top),
                                    SafeNarrow(right), SafeNarrow(bottom)));
  resetVolatility();
}

void CanvasPath::addOval(double left, double top, double right, double bottom) {
  sk_path_.addOval(SkRect::MakeLTRB(SafeNarrow(left), SafeNarrow(top),
                                    SafeNarrow(right), SafeNarrow(bottom)));
  resetVolatility();
}

void CanvasPath::addArc(double left,
                        double top,
                        double right,
                        double bottom,
                        double startAngle,
                        double sweepAngle) {
  sk_path_.addArc(SkRect::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));
  resetVolatility();
}

void CanvasPath::addPolygon(const tonic::Float32List& points, bool close) {
  sk_path_.addPoly(reinterpret_cast<const SkPoint*>(points.data()),
                   points.num_elements() / 2, close);
  resetVolatility();
}

void CanvasPath::addRRect(const RRect& rrect) {
  sk_path_.addRRect(ToSkRRect(rrect.rrect));
  resetVolatility();
}

void CanvasPath::addRSuperellipse(const RSuperellipse* rsuperellipse) {
  DlPathBuilder builder;
  builder.SetConvexity(impeller::Convexity::kConvex);
  builder.SetBounds(rsuperellipse->bounds());
  builder.AddRoundSuperellipse(DlRoundSuperellipse::MakeRectRadii(
      rsuperellipse->bounds(), rsuperellipse->radii()));
  sk_path_.addPath(DlPath(builder.TakePath()).GetSkPath(),
                   SkPath::kAppend_AddPathMode);

  resetVolatility();
}

void CanvasPath::addPath(CanvasPath* path, double dx, double dy) {
  if (!path) {
    Dart_ThrowException(ToDart("Path.addPath called with non-genuine Path."));
    return;
  }
  sk_path_.addPath(path->sk_path_, SafeNarrow(dx), SafeNarrow(dy),
                   SkPath::kAppend_AddPathMode);
  resetVolatility();
}

void CanvasPath::addPathWithMatrix(CanvasPath* path,
                                   double dx,
                                   double dy,
                                   Dart_Handle matrix4_handle) {
  tonic::Float64List matrix4(matrix4_handle);

  if (!path) {
    matrix4.Release();
    Dart_ThrowException(
        ToDart("Path.addPathWithMatrix called with non-genuine Path."));
    return;
  }

  SkMatrix matrix = ToSkMatrix(matrix4);
  matrix4.Release();
  matrix.setTranslateX(matrix.getTranslateX() + SafeNarrow(dx));
  matrix.setTranslateY(matrix.getTranslateY() + SafeNarrow(dy));
  sk_path_.addPath(path->sk_path_, matrix, SkPath::kAppend_AddPathMode);
  resetVolatility();
}

void CanvasPath::extendWithPath(CanvasPath* path, double dx, double dy) {
  if (!path) {
    Dart_ThrowException(
        ToDart("Path.extendWithPath called with non-genuine Path."));
    return;
  }
  sk_path_.addPath(path->sk_path_, SafeNarrow(dx), SafeNarrow(dy),
                   SkPath::kExtend_AddPathMode);
  resetVolatility();
}

void CanvasPath::extendWithPathAndMatrix(CanvasPath* path,
                                         double dx,
                                         double dy,
                                         Dart_Handle matrix4_handle) {
  tonic::Float64List matrix4(matrix4_handle);

  if (!path) {
    matrix4.Release();
    Dart_ThrowException(
        ToDart("Path.addPathWithMatrix called with non-genuine Path."));
    return;
  }

  SkMatrix matrix = ToSkMatrix(matrix4);
  matrix4.Release();
  matrix.setTranslateX(matrix.getTranslateX() + SafeNarrow(dx));
  matrix.setTranslateY(matrix.getTranslateY() + SafeNarrow(dy));
  sk_path_.addPath(path->sk_path_, matrix, SkPath::kExtend_AddPathMode);
  resetVolatility();
}

void CanvasPath::close() {
  sk_path_.close();
  resetVolatility();
}

void CanvasPath::reset() {
  sk_path_.reset();
  resetVolatility();
}

bool CanvasPath::contains(double x, double y) {
  return sk_path_.contains(SafeNarrow(x), SafeNarrow(y));
}

void CanvasPath::shift(Dart_Handle path_handle, double dx, double dy) {
  fml::RefPtr<CanvasPath> path = Create(path_handle);
  auto& other_mutable_path = path->sk_path_;
  sk_path_.offset(SafeNarrow(dx), SafeNarrow(dy), &other_mutable_path);
  resetVolatility();
}

void CanvasPath::transform(Dart_Handle path_handle,
                           Dart_Handle matrix4_handle) {
  tonic::Float64List matrix4(matrix4_handle);
  auto sk_matrix = ToSkMatrix(matrix4);
  matrix4.Release();
  fml::RefPtr<CanvasPath> path = Create(path_handle);
  auto& other_mutable_path = path->sk_path_;
  sk_path_.transform(sk_matrix, &other_mutable_path);
}

tonic::Float32List CanvasPath::getBounds() {
  tonic::Float32List rect(Dart_NewTypedData(Dart_TypedData_kFloat32, 4));
  const SkRect& bounds = sk_path_.getBounds();
  rect[0] = bounds.left();
  rect[1] = bounds.top();
  rect[2] = bounds.right();
  rect[3] = bounds.bottom();
  return rect;
}

bool CanvasPath::op(CanvasPath* path1, CanvasPath* path2, int operation) {
  bool result = Op(path1->sk_path_, path2->sk_path_,
                   static_cast<SkPathOp>(operation), &sk_path_);
  resetVolatility();
  return result;
}

void CanvasPath::clone(Dart_Handle path_handle) {
  fml::RefPtr<CanvasPath> path = Create(path_handle);
  // per Skia docs, this will create a fast copy
  // data is shared until the source path or dest path are mutated
  path->sk_path_ = this->sk_path_;
}

const DlPath& CanvasPath::path() const {
  if (!dl_path_.has_value()) {
    dl_path_.emplace(sk_path_);
  }
  return dl_path_.value();
}

}  // namespace flutter
