// Copyright 2013 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "skia/ext/benchmarking_canvas.h"

#include <array>
#include <memory>
#include <sstream>
#include <utility>

#include "base/check_op.h"
#include "base/compiler_specific.h"
#include "base/memory/ptr_util.h"
#include "base/memory/raw_ptr.h"
#include "base/time/time.h"
#include "base/values.h"
#include "third_party/skia/include/core/SkBitmap.h"
#include "third_party/skia/include/core/SkColorFilter.h"
#include "third_party/skia/include/core/SkImage.h"
#include "third_party/skia/include/core/SkImageFilter.h"
#include "third_party/skia/include/core/SkPaint.h"
#include "third_party/skia/include/core/SkPath.h"
#include "third_party/skia/include/core/SkPicture.h"
#include "third_party/skia/include/core/SkRRect.h"
#include "third_party/skia/include/core/SkRegion.h"
#include "third_party/skia/include/core/SkString.h"
#include "third_party/skia/include/core/SkTextBlob.h"

namespace {

class FlagsBuilder {
public:
  FlagsBuilder(char separator)
      : separator_(separator) {}

  void addFlag(bool flag_val, const char flag_name[]) {
    if (!flag_val)
      return;
    if (!oss_.str().empty())
      oss_ << separator_;

    oss_ << flag_name;
  }

  std::string str() const {
    return oss_.str();
  }

private:
  char separator_;
  std::ostringstream oss_;
};

base::Value AsValue(bool b) {
  return base::Value(b);
}

base::Value AsValue(SkScalar scalar) {
  return base::Value(scalar);
}

base::Value AsValue(const SkSize& size) {
  base::DictValue val;
  val.Set("width", AsValue(size.width()));
  val.Set("height", AsValue(size.height()));

  return base::Value(std::move(val));
}

base::Value AsValue(const SkPoint& point) {
  base::DictValue val;
  val.Set("x", AsValue(point.x()));
  val.Set("y", AsValue(point.y()));

  return base::Value(std::move(val));
}

base::Value AsValue(const SkRect& rect) {
  base::DictValue val;
  val.Set("left", AsValue(rect.fLeft));
  val.Set("top", AsValue(rect.fTop));
  val.Set("right", AsValue(rect.fRight));
  val.Set("bottom", AsValue(rect.fBottom));

  return base::Value(std::move(val));
}

base::Value AsValue(const SkRRect& rrect) {
  base::DictValue radii_val;
  radii_val.Set("upper-left", AsValue(rrect.radii(SkRRect::kUpperLeft_Corner)));
  radii_val.Set("upper-right",
                AsValue(rrect.radii(SkRRect::kUpperRight_Corner)));
  radii_val.Set("lower-right",
                AsValue(rrect.radii(SkRRect::kLowerRight_Corner)));
  radii_val.Set("lower-left", AsValue(rrect.radii(SkRRect::kLowerLeft_Corner)));

  base::DictValue val;
  val.Set("rect", AsValue(rrect.rect()));
  val.Set("radii", std::move(radii_val));

  return base::Value(std::move(val));
}

base::Value AsValue(const SkMatrix& matrix) {
  base::ListValue val;
  for (int i = 0; i < 9; ++i)
    val.Append(AsValue(matrix[i]));

  return base::Value(std::move(val));
}

base::Value AsValue(SkColor color) {
  base::DictValue val;
  val.Set("a", static_cast<int>(SkColorGetA(color)));
  val.Set("r", static_cast<int>(SkColorGetR(color)));
  val.Set("g", static_cast<int>(SkColorGetG(color)));
  val.Set("b", static_cast<int>(SkColorGetB(color)));

  return base::Value(std::move(val));
}

base::Value AsValue(SkBlendMode mode) {
  return base::Value(SkBlendMode_Name(mode));
}

base::Value AsValue(SkCanvas::PointMode mode) {
  static const char* gModeStrings[] = { "Points", "Lines", "Polygon" };
  DCHECK_LT(static_cast<size_t>(mode), std::size(gModeStrings));

  return base::Value(UNSAFE_TODO(gModeStrings[mode]));
}

base::Value AsValue(const SkColorFilter& filter) {
  base::DictValue val;

  if (filter.isAlphaUnchanged()) {
    FlagsBuilder builder('|');
    builder.addFlag(true, "kAlphaUnchanged_Flag");

    val.Set("flags", builder.str());
  }

  SkScalar color_matrix[20];
  if (filter.asAColorMatrix(color_matrix)) {
    base::ListValue color_matrix_val;
    for (unsigned i = 0; i < 20; ++i) {
      color_matrix_val.Append(AsValue(UNSAFE_TODO(color_matrix[i])));
    }

    val.Set("color_matrix", std::move(color_matrix_val));
  }

  return base::Value(std::move(val));
}

base::Value AsValue(const SkImageFilter& filter) {
  base::DictValue val;
  val.Set("inputs", filter.countInputs());

  SkColorFilter* color_filter;
  if (filter.asColorFilter(&color_filter)) {
    val.Set("color_filter", AsValue(*color_filter));
    SkSafeUnref(color_filter); // ref'd in asColorFilter
  }

  return base::Value(std::move(val));
}

base::Value AsValue(const SkPaint& paint) {
  base::DictValue val;
  SkPaint default_paint;

  if (paint.getColor() != default_paint.getColor())
    val.Set("Color", AsValue(paint.getColor()));

  if (paint.getStyle() != default_paint.getStyle()) {
    static const char* gStyleStrings[] = { "Fill", "Stroke", "StrokeFill" };
    DCHECK_LT(static_cast<size_t>(paint.getStyle()),
              std::size(gStyleStrings));
    val.Set("Style", UNSAFE_TODO(gStyleStrings[paint.getStyle()]));
  }

  if (paint.asBlendMode() != default_paint.asBlendMode()) {
    val.Set("Xfermode", AsValue(paint.getBlendMode_or(SkBlendMode::kSrcOver)));
  }

  if (paint.isAntiAlias() || paint.isDither()) {
    FlagsBuilder builder('|');
    builder.addFlag(paint.isAntiAlias(), "AntiAlias");
    builder.addFlag(paint.isDither(), "Dither");

    val.Set("Flags", builder.str());
  }

  if (paint.getColorFilter())
    val.Set("ColorFilter", AsValue(*paint.getColorFilter()));

  if (paint.getImageFilter())
    val.Set("ImageFilter", AsValue(*paint.getImageFilter()));

  return base::Value(std::move(val));
}

base::Value SaveLayerFlagsAsValue(SkCanvas::SaveLayerFlags flags) {
  return base::Value(static_cast<int>(flags));
}

base::Value AsValue(SkClipOp op) {
  static const char* gOpStrings[] = { "Difference",
                                      "Intersect",
                                      "Union",
                                      "XOR",
                                      "ReverseDifference",
                                      "Replace"
                                    };
  size_t index = static_cast<size_t>(op);
  DCHECK_LT(index, std::size(gOpStrings));
  return base::Value(UNSAFE_TODO(gOpStrings[index]));
}

base::Value AsValue(const SkRegion& region) {
  base::DictValue val;
  val.Set("bounds", AsValue(SkRect::Make(region.getBounds())));

  return base::Value(std::move(val));
}

base::Value AsValue(const SkImage& image) {
  base::DictValue val;
  val.Set("size", AsValue(SkSize::Make(image.width(), image.height())));

  return base::Value(std::move(val));
}

base::Value AsValue(const SkTextBlob& blob) {
  base::DictValue val;
  val.Set("bounds", AsValue(blob.bounds()));

  return base::Value(std::move(val));
}

base::Value AsValue(const SkPath& path) {
  base::DictValue val;

  static const char* gFillStrings[] =
      { "winding", "even-odd", "inverse-winding", "inverse-even-odd" };
  size_t index = static_cast<size_t>(path.getFillType());
  DCHECK_LT(index, std::size(gFillStrings));
  val.Set("fill-type", UNSAFE_TODO(gFillStrings[index]));
  val.Set("convex", path.isConvex());
  val.Set("is-rect", path.isRect(nullptr));
  val.Set("bounds", AsValue(path.getBounds()));

  static const char* gVerbStrings[] =
      { "move", "line", "quad", "conic", "cubic", "close", "done" };
  static const int gPtsPerVerb[] = { 1, 1, 2, 2, 3, 0, 0 };
  static const int gPtOffsetPerVerb[] = { 0, 1, 1, 1, 1, 0, 0 };
  static_assert(
      std::size(gVerbStrings) == static_cast<size_t>(SkPath::kDone_Verb + 1),
      "gVerbStrings size mismatch");
  static_assert(
      std::size(gVerbStrings) == std::size(gPtsPerVerb),
      "gPtsPerVerb size mismatch");
  static_assert(
      std::size(gVerbStrings) == std::size(gPtOffsetPerVerb),
      "gPtOffsetPerVerb size mismatch");

  base::ListValue verbs_val;
  SkPath::RawIter iter(const_cast<SkPath&>(path));
  SkPoint points[4];

  for (SkPath::Verb verb = iter.next(points); verb != SkPath::kDone_Verb;
       verb = iter.next(points)) {
    DCHECK_LT(static_cast<size_t>(verb), std::size(gVerbStrings));

    base::DictValue verb_val;
    base::ListValue pts_val;

    for (int i = 0; i < UNSAFE_TODO(gPtsPerVerb[verb]); ++i) {
      pts_val.Append(AsValue(UNSAFE_TODO(points[i + gPtOffsetPerVerb[verb]])));
    }

    verb_val.Set(UNSAFE_TODO(gVerbStrings[verb]), std::move(pts_val));

    if (SkPath::kConic_Verb == verb)
      verb_val.Set("weight", AsValue(iter.conicWeight()));

    verbs_val.Append(std::move(verb_val));
  }
  val.Set("verbs", std::move(verbs_val));

  return base::Value(std::move(val));
}

template <typename T>
base::Value AsListValue(const T array[], size_t count) {
  base::ListValue val;

  for (size_t i = 0; i < count; ++i)
    val.Append(AsValue(UNSAFE_TODO(array[i])));

  return base::Value(std::move(val));
}

} // namespace

namespace skia {

class BenchmarkingCanvas::AutoOp {
public:
  // AutoOp objects are always scoped within draw call frames,
  // so the paint is guaranteed to be valid for their lifetime.
 AutoOp(BenchmarkingCanvas* canvas,
        const char op_name[],
        const SkPaint* paint = nullptr)
     : canvas_(canvas) {
   DCHECK(canvas);
   DCHECK(op_name);

   op_record_.Set("cmd_string", op_name);
   base::Value* op_params = op_record_.Set("info", base::ListValue());
   DCHECK(op_params);
   DCHECK(op_params->is_list());
   op_params_ = &op_params->GetList();

   if (paint) {
     this->addParam("paint", AsValue(*paint));
     filtered_paint_ = *paint;
   }

   start_ticks_ = base::TimeTicks::Now();
 }

  ~AutoOp() {
    base::TimeDelta ticks = base::TimeTicks::Now() - start_ticks_;
    op_record_.Set("cmd_time", ticks.InMillisecondsF());

    canvas_->op_records_.Append(std::move(op_record_));
  }

  void addParam(const char name[], base::Value value) {
    base::DictValue param;
    param.Set(name, std::move(value));

    op_params_->Append(std::move(param));
  }

  const SkPaint* paint() const { return &filtered_paint_; }

private:
 raw_ptr<BenchmarkingCanvas> canvas_;
 base::DictValue op_record_;
 raw_ptr<base::ListValue> op_params_;
 base::TimeTicks start_ticks_;

 SkPaint filtered_paint_;
};

BenchmarkingCanvas::BenchmarkingCanvas(SkCanvas* canvas)
    : INHERITED(canvas->imageInfo().width(),
                canvas->imageInfo().height()) {
  addCanvas(canvas);
}

BenchmarkingCanvas::~BenchmarkingCanvas() = default;

size_t BenchmarkingCanvas::CommandCount() const {
  return op_records_.size();
}

const base::ListValue& BenchmarkingCanvas::Commands() const {
  return op_records_;
}

double BenchmarkingCanvas::GetTime(size_t index) {
  const base::Value& op = op_records_[index];
  if (!op.is_dict())
    return 0;
  return op.GetDict().FindDouble("cmd_time").value_or(0);
}

void BenchmarkingCanvas::willSave() {
  AutoOp op(this, "Save");

  INHERITED::willSave();
}

SkCanvas::SaveLayerStrategy BenchmarkingCanvas::getSaveLayerStrategy(
    const SaveLayerRec& rec) {
  AutoOp op(this, "SaveLayer", rec.fPaint);
  if (rec.fBounds)
    op.addParam("bounds", AsValue(*rec.fBounds));
  if (rec.fSaveLayerFlags)
    op.addParam("flags", SaveLayerFlagsAsValue(rec.fSaveLayerFlags));

  return INHERITED::getSaveLayerStrategy(rec);
}

void BenchmarkingCanvas::willRestore() {
  AutoOp op(this, "Restore");

  INHERITED::willRestore();
}

void BenchmarkingCanvas::didConcat44(const SkM44& m) {
  SkScalar values[16];
  m.getColMajor(values);
  AutoOp op(this, "Concat");
  op.addParam("matrix", AsListValue(values, 16));

  INHERITED::didConcat44(m);
}

void BenchmarkingCanvas::didScale(SkScalar x, SkScalar y) {
  AutoOp op(this, "Scale");
  op.addParam("scale-x", AsValue(x));
  op.addParam("scale-y", AsValue(y));

  INHERITED::didScale(x, y);
}

void BenchmarkingCanvas::didTranslate(SkScalar x, SkScalar y) {
  AutoOp op(this, "Translate");
  op.addParam("translate-x", AsValue(x));
  op.addParam("translate-y", AsValue(y));

  INHERITED::didTranslate(x, y);
}

void BenchmarkingCanvas::didSetM44(const SkM44& m) {
  SkScalar values[16];
  m.getColMajor(values);
  AutoOp op(this, "SetMatrix");
  op.addParam("matrix", AsListValue(values, 16));

  INHERITED::didSetM44(m);
}

void BenchmarkingCanvas::onClipRect(const SkRect& rect,
                                    SkClipOp region_op,
                                    SkCanvas::ClipEdgeStyle style) {
  AutoOp op(this, "ClipRect");
  op.addParam("rect", AsValue(rect));
  op.addParam("op", AsValue(region_op));
  op.addParam("anti-alias", AsValue(style == kSoft_ClipEdgeStyle));

  INHERITED::onClipRect(rect, region_op, style);
}

void BenchmarkingCanvas::onClipRRect(const SkRRect& rrect,
                                     SkClipOp region_op,
                                     SkCanvas::ClipEdgeStyle style) {
  AutoOp op(this, "ClipRRect");
  op.addParam("rrect", AsValue(rrect));
  op.addParam("op", AsValue(region_op));
  op.addParam("anti-alias", AsValue(style == kSoft_ClipEdgeStyle));

  INHERITED::onClipRRect(rrect, region_op, style);
}

void BenchmarkingCanvas::onClipPath(const SkPath& path,
                                    SkClipOp region_op,
                                    SkCanvas::ClipEdgeStyle style) {
  AutoOp op(this, "ClipPath");
  op.addParam("path", AsValue(path));
  op.addParam("op", AsValue(region_op));
  op.addParam("anti-alias", AsValue(style == kSoft_ClipEdgeStyle));

  INHERITED::onClipPath(path, region_op, style);
}

void BenchmarkingCanvas::onClipRegion(const SkRegion& region,
                                      SkClipOp region_op) {
  AutoOp op(this, "ClipRegion");
  op.addParam("region", AsValue(region));
  op.addParam("op", AsValue(region_op));

  INHERITED::onClipRegion(region, region_op);
}

void BenchmarkingCanvas::onDrawPaint(const SkPaint& paint) {
  AutoOp op(this, "DrawPaint", &paint);

  INHERITED::onDrawPaint(*op.paint());
}

void BenchmarkingCanvas::onDrawPoints(PointMode mode, size_t count,
                                      const SkPoint pts[], const SkPaint& paint) {
  AutoOp op(this, "DrawPoints", &paint);
  op.addParam("mode", AsValue(mode));
  op.addParam("points", AsListValue(pts, count));

  INHERITED::onDrawPoints(mode, count, pts, *op.paint());
}

void BenchmarkingCanvas::onDrawRect(const SkRect& rect, const SkPaint& paint) {
  AutoOp op(this, "DrawRect", &paint);
  op.addParam("rect", AsValue(rect));

  INHERITED::onDrawRect(rect, *op.paint());
}

void BenchmarkingCanvas::onDrawOval(const SkRect& rect, const SkPaint& paint) {
  AutoOp op(this, "DrawOval", &paint);
  op.addParam("rect", AsValue(rect));

  INHERITED::onDrawOval(rect, *op.paint());
}

void BenchmarkingCanvas::onDrawRRect(const SkRRect& rrect, const SkPaint& paint) {
  AutoOp op(this, "DrawRRect", &paint);
  op.addParam("rrect", AsValue(rrect));

  INHERITED::onDrawRRect(rrect, *op.paint());
}

void BenchmarkingCanvas::onDrawDRRect(const SkRRect& outer, const SkRRect& inner,
                                      const SkPaint& paint) {
  AutoOp op(this, "DrawDRRect", &paint);
  op.addParam("outer", AsValue(outer));
  op.addParam("inner", AsValue(inner));

  INHERITED::onDrawDRRect(outer, inner, *op.paint());
}

void BenchmarkingCanvas::onDrawPath(const SkPath& path, const SkPaint& paint) {
  AutoOp op(this, "DrawPath", &paint);
  op.addParam("path", AsValue(path));

  INHERITED::onDrawPath(path, *op.paint());
}

void BenchmarkingCanvas::onDrawPicture(const SkPicture* picture,
                                       const SkMatrix* matrix,
                                       const SkPaint* paint) {
  DCHECK(picture);
  AutoOp op(this, "DrawPicture", paint);
  op.addParam("picture", AsValue(picture));
  if (matrix)
    op.addParam("matrix", AsValue(*matrix));

  INHERITED::onDrawPicture(picture, matrix, op.paint());
}

void BenchmarkingCanvas::onDrawImage2(const SkImage* image,
                                      SkScalar left,
                                      SkScalar top,
                                      const SkSamplingOptions& sampling,
                                      const SkPaint* paint) {
  DCHECK(image);
  AutoOp op(this, "DrawImage", paint);
  op.addParam("image", AsValue(*image));
  op.addParam("left", AsValue(left));
  op.addParam("top", AsValue(top));

  INHERITED::onDrawImage2(image, left, top, sampling, op.paint());
}

void BenchmarkingCanvas::onDrawImageRect2(const SkImage* image,
                                          const SkRect& src,
                                          const SkRect& dst,
                                          const SkSamplingOptions& sampling,
                                          const SkPaint* paint,
                                          SrcRectConstraint constraint) {
  DCHECK(image);
  AutoOp op(this, "DrawImageRect", paint);
  op.addParam("image", AsValue(*image));
  op.addParam("src", AsValue(src));
  op.addParam("dst", AsValue(dst));

  INHERITED::onDrawImageRect2(image, src, dst, sampling, op.paint(),
                              constraint);
}

void BenchmarkingCanvas::onDrawTextBlob(const SkTextBlob* blob, SkScalar x, SkScalar y,
                                        const SkPaint& paint) {
  DCHECK(blob);
  AutoOp op(this, "DrawTextBlob", &paint);
  op.addParam("blob", AsValue(*blob));
  op.addParam("x", AsValue(x));
  op.addParam("y", AsValue(y));

  INHERITED::onDrawTextBlob(blob, x, y, *op.paint());
}

} // namespace skia
