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

#include "flutter/display_list/skia/dl_sk_dispatcher.h"

#include "flutter/display_list/dl_blend_mode.h"
#include "flutter/display_list/dl_canvas.h"
#include "flutter/display_list/effects/image_filters/dl_blur_image_filter.h"
#include "flutter/display_list/geometry/dl_geometry_conversions.h"
#include "flutter/display_list/skia/dl_sk_conversions.h"
#include "flutter/display_list/skia/dl_sk_types.h"
#include "flutter/fml/trace_event.h"

#include "third_party/skia/include/effects/SkDashPathEffect.h"
#include "third_party/skia/include/utils/SkShadowUtils.h"

namespace flutter {

const SkPaint* DlSkCanvasDispatcher::safe_paint(bool use_attributes) {
  if (use_attributes) {
    // The accumulated SkPaint object will already have incorporated
    // any attribute overrides.
    // Any rendering operation that uses an optional paint will ignore
    // the shader in the paint so we inform that |paint()| method so
    // that it can set the dither flag appropriately.
    return &paint(false);
  } else if (has_opacity()) {
    temp_paint_.setAlphaf(opacity());
    return &temp_paint_;
  } else {
    return nullptr;
  }
}

void DlSkCanvasDispatcher::save() {
  canvas_->save();
  // save has no impact on attributes, but it needs to register a record
  // on the restore stack so that the eventual call to restore() will
  // know what to do at that time. We could annotate the restore record
  // with a flag that the record came from a save call, but it is simpler
  // to just pass in the current opacity value as the value to be used by
  // the children and let the utility calls notice that it didn't change.
  save_opacity(opacity());
}
void DlSkCanvasDispatcher::restore() {
  canvas_->restore();
  restore_opacity();
}
void DlSkCanvasDispatcher::saveLayer(const DlRect& bounds,
                                     const SaveLayerOptions options,
                                     const DlImageFilter* backdrop,
                                     std::optional<int64_t> backdrop_id) {
  if (!options.content_is_clipped() && options.can_distribute_opacity() &&
      backdrop == nullptr) {
    // We know that:
    // - no bounds is needed for clipping here
    // - no backdrop filter is used to initialize the layer
    // - the current attributes only have an alpha
    // - the children are compatible with individually rendering with
    //   an inherited opacity
    // Therefore we can just use a save instead of a saveLayer and pass the
    // intended opacity to the children.
    canvas_->save();
    // If the saveLayer does not use attributes, the children should continue
    // to render with the inherited opacity unmodified. If attributes are to
    // be applied, the children should render with the combination of the
    // inherited opacity combined with the alpha from the current color.
    save_opacity(options.renders_with_attributes() ? combined_opacity()
                                                   : opacity());
  } else {
    TRACE_EVENT0("flutter", "Canvas::saveLayer");
    const SkPaint* paint = safe_paint(options.renders_with_attributes());
    const sk_sp<SkImageFilter> sk_backdrop = ToSk(backdrop);
    const SkRect* sl_bounds =
        options.bounds_from_caller() ? &ToSkRect(bounds) : nullptr;
    SkCanvas::SaveLayerRec params(sl_bounds, paint, sk_backdrop.get(), 0);
    if (sk_backdrop && backdrop->asBlur()) {
      params.fBackdropTileMode = ToSk(backdrop->asBlur()->tile_mode());
    }
    canvas_->saveLayer(params);
    // saveLayer will apply the current opacity on behalf of the children
    // so they will inherit an opaque opacity.
    save_opacity(SK_Scalar1);
  }
}

void DlSkCanvasDispatcher::translate(DlScalar tx, DlScalar ty) {
  canvas_->translate(tx, ty);
}
void DlSkCanvasDispatcher::scale(DlScalar sx, DlScalar sy) {
  canvas_->scale(sx, sy);
}
void DlSkCanvasDispatcher::rotate(DlScalar degrees) {
  canvas_->rotate(degrees);
}
void DlSkCanvasDispatcher::skew(DlScalar sx, DlScalar sy) {
  canvas_->skew(sx, sy);
}
// clang-format off
// 2x3 2D affine subset of a 4x4 transform in row major order
void DlSkCanvasDispatcher::transform2DAffine(
    DlScalar mxx, DlScalar mxy, DlScalar mxt,
    DlScalar myx, DlScalar myy, DlScalar myt) {
  // Internally concat(SkMatrix) gets redirected to concat(SkM44)
  // so we just jump directly to the SkM44 version
  canvas_->concat(SkM44(mxx, mxy, 0, mxt,
                        myx, myy, 0, myt,
                         0,   0,  1,  0,
                         0,   0,  0,  1));
}
// full 4x4 transform in row major order
void DlSkCanvasDispatcher::transformFullPerspective(
    DlScalar mxx, DlScalar mxy, DlScalar mxz, DlScalar mxt,
    DlScalar myx, DlScalar myy, DlScalar myz, DlScalar myt,
    DlScalar mzx, DlScalar mzy, DlScalar mzz, DlScalar mzt,
    DlScalar mwx, DlScalar mwy, DlScalar mwz, DlScalar mwt) {
  canvas_->concat(SkM44(mxx, mxy, mxz, mxt,
                        myx, myy, myz, myt,
                        mzx, mzy, mzz, mzt,
                        mwx, mwy, mwz, mwt));
}
// clang-format on
void DlSkCanvasDispatcher::transformReset() {
  canvas_->setMatrix(original_transform_);
}

void DlSkCanvasDispatcher::clipRect(const DlRect& rect,
                                    DlClipOp clip_op,
                                    bool is_aa) {
  canvas_->clipRect(ToSkRect(rect), ToSk(clip_op), is_aa);
}
void DlSkCanvasDispatcher::clipOval(const DlRect& bounds,
                                    DlClipOp clip_op,
                                    bool is_aa) {
  canvas_->clipRRect(SkRRect::MakeOval(ToSkRect(bounds)), ToSk(clip_op), is_aa);
}
void DlSkCanvasDispatcher::clipRoundRect(const DlRoundRect& rrect,
                                         DlClipOp clip_op,
                                         bool is_aa) {
  canvas_->clipRRect(ToSkRRect(rrect), ToSk(clip_op), is_aa);
}
void DlSkCanvasDispatcher::clipRoundSuperellipse(const DlRoundSuperellipse& rse,
                                                 DlClipOp clip_op,
                                                 bool is_aa) {
  // Skia doesn't support round superellipse, thus fall back to round rectangle.
  canvas_->clipRRect(ToApproximateSkRRect(rse), ToSk(clip_op), is_aa);
}
void DlSkCanvasDispatcher::clipPath(const DlPath& path,
                                    DlClipOp clip_op,
                                    bool is_aa) {
  path.WillRenderSkPath();
  canvas_->clipPath(path.GetSkPath(), ToSk(clip_op), is_aa);
}

void DlSkCanvasDispatcher::drawPaint() {
  const SkPaint& sk_paint = paint();
  SkImageFilter* filter = sk_paint.getImageFilter();
  if (filter && !filter->asColorFilter(nullptr)) {
    // drawPaint does an implicit saveLayer if an SkImageFilter is
    // present that cannot be replaced by an SkColorFilter.
    TRACE_EVENT0("flutter", "Canvas::saveLayer");
  }
  canvas_->drawPaint(sk_paint);
}
void DlSkCanvasDispatcher::drawColor(DlColor color, DlBlendMode mode) {
  // SkCanvas::drawColor(SkColor) does the following conversion anyway
  // We do it here manually to increase precision on applying opacity
  SkColor4f color4f = ToSkColor4f(color);
  color4f.fA *= opacity();
  canvas_->drawColor(color4f, ToSk(mode));
}
void DlSkCanvasDispatcher::drawLine(const DlPoint& p0, const DlPoint& p1) {
  canvas_->drawLine(ToSkPoint(p0), ToSkPoint(p1), paint());
}
void DlSkCanvasDispatcher::drawDashedLine(const DlPoint& p0,
                                          const DlPoint& p1,
                                          DlScalar on_length,
                                          DlScalar off_length) {
  SkPaint dash_paint = paint();
  SkScalar intervals[] = {on_length, off_length};
  dash_paint.setPathEffect(SkDashPathEffect::Make(intervals, 2, 0.0f));
  canvas_->drawLine(ToSkPoint(p0), ToSkPoint(p1), dash_paint);
}
void DlSkCanvasDispatcher::drawRect(const DlRect& rect) {
  canvas_->drawRect(ToSkRect(rect), paint());
}
void DlSkCanvasDispatcher::drawOval(const DlRect& bounds) {
  canvas_->drawOval(ToSkRect(bounds), paint());
}
void DlSkCanvasDispatcher::drawCircle(const DlPoint& center, DlScalar radius) {
  canvas_->drawCircle(ToSkPoint(center), radius, paint());
}
void DlSkCanvasDispatcher::drawRoundRect(const DlRoundRect& rrect) {
  canvas_->drawRRect(ToSkRRect(rrect), paint());
}
void DlSkCanvasDispatcher::drawDiffRoundRect(const DlRoundRect& outer,
                                             const DlRoundRect& inner) {
  canvas_->drawDRRect(ToSkRRect(outer), ToSkRRect(inner), paint());
}
void DlSkCanvasDispatcher::drawRoundSuperellipse(
    const DlRoundSuperellipse& rse) {
  // Skia doesn't support round superellipse, thus fall back to round rectangle.
  canvas_->drawRRect(ToApproximateSkRRect(rse), paint());
}
void DlSkCanvasDispatcher::drawPath(const DlPath& path) {
  path.WillRenderSkPath();
  canvas_->drawPath(path.GetSkPath(), paint());
}
void DlSkCanvasDispatcher::drawArc(const DlRect& bounds,
                                   DlScalar start,
                                   DlScalar sweep,
                                   bool useCenter) {
  canvas_->drawArc(ToSkRect(bounds), start, sweep, useCenter, paint());
}
void DlSkCanvasDispatcher::drawPoints(DlPointMode mode,
                                      uint32_t count,
                                      const DlPoint pts[]) {
  canvas_->drawPoints(ToSk(mode), count, ToSkPoints(pts), paint());
}
void DlSkCanvasDispatcher::drawVertices(
    const std::shared_ptr<DlVertices>& vertices,
    DlBlendMode mode) {
  canvas_->drawVertices(ToSk(vertices), ToSk(mode), paint());
}
void DlSkCanvasDispatcher::drawImage(const sk_sp<DlImage> image,
                                     const DlPoint& point,
                                     DlImageSampling sampling,
                                     bool render_with_attributes) {
  canvas_->drawImage(image ? image->skia_image() : nullptr, point.x, point.y,
                     ToSk(sampling), safe_paint(render_with_attributes));
}
void DlSkCanvasDispatcher::drawImageRect(const sk_sp<DlImage> image,
                                         const DlRect& src,
                                         const DlRect& dst,
                                         DlImageSampling sampling,
                                         bool render_with_attributes,
                                         DlSrcRectConstraint constraint) {
  canvas_->drawImageRect(image ? image->skia_image() : nullptr, ToSkRect(src),
                         ToSkRect(dst), ToSk(sampling),
                         safe_paint(render_with_attributes), ToSk(constraint));
}
void DlSkCanvasDispatcher::drawImageNine(const sk_sp<DlImage> image,
                                         const DlIRect& center,
                                         const DlRect& dst,
                                         DlFilterMode filter,
                                         bool render_with_attributes) {
  if (!image) {
    return;
  }
  auto skia_image = image->skia_image();
  if (!skia_image) {
    return;
  }
  canvas_->drawImageNine(skia_image.get(), ToSkIRect(center), ToSkRect(dst),
                         ToSk(filter), safe_paint(render_with_attributes));
}
void DlSkCanvasDispatcher::drawAtlas(const sk_sp<DlImage> atlas,
                                     const DlRSTransform xform[],
                                     const DlRect tex[],
                                     const DlColor colors[],
                                     int count,
                                     DlBlendMode mode,
                                     DlImageSampling sampling,
                                     const DlRect* cullRect,
                                     bool render_with_attributes) {
  if (!atlas) {
    return;
  }
  auto skia_atlas = atlas->skia_image();
  if (!skia_atlas) {
    return;
  }
  std::vector<SkColor> sk_colors;
  if (colors != nullptr) {
    sk_colors.reserve(count);
    for (int i = 0; i < count; ++i) {
      sk_colors.push_back(colors[i].argb());
    }
  }
  canvas_->drawAtlas(skia_atlas.get(), ToSk(xform), ToSkRects(tex),
                     sk_colors.empty() ? nullptr : sk_colors.data(), count,
                     ToSk(mode), ToSk(sampling), ToSkRect(cullRect),
                     safe_paint(render_with_attributes));
}
void DlSkCanvasDispatcher::drawDisplayList(
    const sk_sp<DisplayList> display_list,
    DlScalar opacity) {
  const int restore_count = canvas_->getSaveCount();

  // Compute combined opacity and figure out whether we can apply it
  // during dispatch or if we need a saveLayer.
  SkScalar combined_opacity = opacity * this->opacity();
  if (combined_opacity < SK_Scalar1 &&
      !display_list->can_apply_group_opacity()) {
    TRACE_EVENT0("flutter", "Canvas::saveLayer");
    canvas_->saveLayerAlphaf(ToSkRect(&display_list->GetBounds()),
                             combined_opacity);
    combined_opacity = SK_Scalar1;
  } else {
    canvas_->save();
  }

  // Create a new CanvasDispatcher to isolate the actions of the
  // display_list from the current environment.
  DlSkCanvasDispatcher dispatcher(canvas_, combined_opacity);
  if (display_list->rtree()) {
    display_list->Dispatch(dispatcher, ToDlRect(canvas_->getLocalClipBounds()));
  } else {
    display_list->Dispatch(dispatcher);
  }

  // Restore canvas state to what it was before dispatching.
  canvas_->restoreToCount(restore_count);
}
void DlSkCanvasDispatcher::drawTextBlob(const sk_sp<SkTextBlob> blob,
                                        DlScalar x,
                                        DlScalar y) {
  canvas_->drawTextBlob(blob, x, y, paint());
}

void DlSkCanvasDispatcher::drawTextFrame(
    const std::shared_ptr<impeller::TextFrame>& text_frame,
    DlScalar x,
    DlScalar y) {
  FML_CHECK(false);
}

void DlSkCanvasDispatcher::DrawShadow(SkCanvas* canvas,
                                      const SkPath& path,
                                      DlColor color,
                                      float elevation,
                                      bool transparentOccluder,
                                      DlScalar dpr) {
  const SkScalar kAmbientAlpha = 0.039f;
  const SkScalar kSpotAlpha = 0.25f;

  uint32_t flags = transparentOccluder
                       ? SkShadowFlags::kTransparentOccluder_ShadowFlag
                       : SkShadowFlags::kNone_ShadowFlag;
  flags |= SkShadowFlags::kDirectionalLight_ShadowFlag;
  SkColor in_ambient =
      SkColorSetA(ToSkColor(color), kAmbientAlpha * color.getAlpha());
  SkColor in_spot =
      SkColorSetA(ToSkColor(color), kSpotAlpha * color.getAlpha());
  SkColor ambient_color, spot_color;
  SkShadowUtils::ComputeTonalColors(in_ambient, in_spot, &ambient_color,
                                    &spot_color);
  SkShadowUtils::DrawShadow(
      canvas, path, SkPoint3::Make(0, 0, dpr * elevation),
      SkPoint3::Make(0, -1, 1),
      DlCanvas::kShadowLightRadius / DlCanvas::kShadowLightHeight,
      ambient_color, spot_color, flags);
}

void DlSkCanvasDispatcher::drawShadow(const DlPath& path,
                                      const DlColor color,
                                      const DlScalar elevation,
                                      bool transparent_occluder,
                                      DlScalar dpr) {
  path.WillRenderSkPath();
  DrawShadow(canvas_, path.GetSkPath(), color, elevation, transparent_occluder,
             dpr);
}

}  // namespace flutter
