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

#ifndef CC_TEST_PAINT_OP_HELPER_H_
#define CC_TEST_PAINT_OP_HELPER_H_

#include <sstream>
#include <string>
#include <vector>

#include "base/strings/stringprintf.h"
#include "cc/paint/draw_looper.h"
#include "cc/paint/paint_filter.h"
#include "cc/paint/paint_op.h"
#include "cc/paint/paint_op_buffer_iterator.h"
#include "cc/paint/skottie_wrapper.h"

namespace cc {

// A helper class to help with debugging PaintOp/PaintOpBuffer.
// Note that this file is primarily used for debugging. As such, it isn't
// typically a part of BUILD.gn (except for self-testing), so all of the
// implementation should be limited ot the header.
class PaintOpHelper {
 public:
  // Print optional/nullable wrapper types are using underlying types.
  template <typename T>
  static std::string ToString(const T* ptr) {
    return ptr ? ToString(*ptr) : "(nil)";
  }

  template <typename T>
  static std::string ToString(const sk_sp<T>& ptr) {
    return ptr ? ToString(*ptr) : "(nil)";
  }

  template <typename T>
  static std::string ToString(scoped_refptr<T> ptr) {
    return ptr ? ToString(*ptr) : "(nil)";
  }

  template <typename T>
  static std::string ToString(const std::optional<T>& opt) {
    return opt.has_value() ? ToString(*opt) : "(nil)";
  }

  template <typename T>
  static std::string ToString(const std::vector<T>& vec) {
    std::ostringstream str;
    str << "{";
    bool is_first = true;
    for (const T& element : vec) {
      if (!is_first) {
        str << ", ";
      }
      str << ToString(element);
      is_first = false;
    }
    str << "}";
    return str.str();
  }

  static std::string ToString(const PaintOp& base_op) {
    std::ostringstream str;
    str << std::boolalpha << base_op.GetType() << "(";
    switch (base_op.GetType()) {
      case PaintOpType::kAnnotate: {
        const auto& op = static_cast<const AnnotateOp&>(base_op);
        str << "type=" << ToString(op.annotation_type)
            << ", rect=" << ToString(op.rect) << ", data=" << ToString(op.data);
        break;
      }
      case PaintOpType::kClipPath: {
        const auto& op = static_cast<const ClipPathOp&>(base_op);
        str << "path=" << ToString(op.path) << ", op=" << ToString(op.op)
            << ", antialias=" << op.antialias
            << ", use_cache=" << (op.use_cache == UsePaintCache::kEnabled);
        break;
      }
      case PaintOpType::kClipRect: {
        const auto& op = static_cast<const ClipRectOp&>(base_op);
        str << "rect=" << ToString(op.rect) << ", op=" << ToString(op.op)
            << ", antialias=" << op.antialias;
        break;
      }
      case PaintOpType::kClipRRect: {
        const auto& op = static_cast<const ClipRRectOp&>(base_op);
        str << "rrect=" << ToString(op.rrect) << ", op=" << ToString(op.op)
            << ", antialias=" << op.antialias;
        break;
      }
      case PaintOpType::kConcat: {
        const auto& op = static_cast<const ConcatOp&>(base_op);
        str << "matrix=" << ToString(op.matrix);
        break;
      }
      case PaintOpType::kCustomData: {
        const auto& op = static_cast<const CustomDataOp&>(base_op);
        str << "id=" << ToString(op.id);
        break;
      }
      case PaintOpType::kDrawColor: {
        const auto& op = static_cast<const DrawColorOp&>(base_op);
        str << "color=" << ToString(op.color) << ", mode=" << ToString(op.mode);
        break;
      }
      case PaintOpType::kDrawDRRect: {
        const auto& op = static_cast<const DrawDRRectOp&>(base_op);
        str << "outer=" << ToString(op.outer)
            << ", inner=" << ToString(op.inner)
            << ", flags=" << ToString(op.flags);
        break;
      }
      case PaintOpType::kDrawImage: {
        const auto& op = static_cast<const DrawImageOp&>(base_op);
        str << "image=" << ToString(op.image) << ", left=" << ToString(op.left)
            << ", top=" << ToString(op.top) << ", flags=" << ToString(op.flags);
        break;
      }
      case PaintOpType::kDrawImageRect: {
        const auto& op = static_cast<const DrawImageRectOp&>(base_op);
        str << "image=" << ToString(op.image) << ", src=" << ToString(op.src)
            << ", dst=" << ToString(op.dst)
            << ", constraint=" << ToString(op.constraint)
            << ", flags=" << ToString(op.flags);
        break;
      }
      case PaintOpType::kDrawIRect: {
        const auto& op = static_cast<const DrawIRectOp&>(base_op);
        str << "rect=" << ToString(op.rect) << ", flags=" << ToString(op.flags);
        break;
      }
      case PaintOpType::kDrawLine: {
        const auto& op = static_cast<const DrawLineOp&>(base_op);
        str << "x0=" << ToString(op.x0) << ", y0=" << ToString(op.y0)
            << ", x1=" << ToString(op.x1) << ", y1=" << ToString(op.y1)
            << ", flags=" << ToString(op.flags);
        break;
      }
      case PaintOpType::kDrawLineLite: {
        const auto& op = static_cast<const DrawLineLiteOp&>(base_op);
        str << "x0=" << ToString(op.x0) << ", y0=" << ToString(op.y0)
            << ", x1=" << ToString(op.x1) << ", y1=" << ToString(op.y1)
            << ", flags=" << ToString(op.core_paint_flags);
        break;
      }
      case PaintOpType::kDrawArc: {
        const auto& op = static_cast<const DrawArcOp&>(base_op);
        str << "oval=" << ToString(op.oval)
            << ", start_angle=" << ToString(op.start_angle_degrees)
            << ", sweep_angle=" << ToString(op.sweep_angle_degrees)
            << ", flags=" << ToString(op.flags);
        break;
      }
      case PaintOpType::kDrawArcLite: {
        const auto& op = static_cast<const DrawArcLiteOp&>(base_op);
        str << "oval=" << ToString(op.oval)
            << ", start_angle=" << ToString(op.start_angle_degrees)
            << ", sweep_angle=" << ToString(op.sweep_angle_degrees)
            << ", flags=" << ToString(op.core_paint_flags);
        break;
      }
      case PaintOpType::kDrawOval: {
        const auto& op = static_cast<const DrawOvalOp&>(base_op);
        str << "oval=" << ToString(op.oval) << ", flags=" << ToString(op.flags);
        break;
      }
      case PaintOpType::kDrawPath: {
        const auto& op = static_cast<const DrawPathOp&>(base_op);
        str << "path=" << ToString(op.path) << ", flags=" << ToString(op.flags)
            << ", use_cache=" << (op.use_cache == UsePaintCache::kEnabled);
        break;
      }
      case PaintOpType::kDrawRecord: {
        const auto& op = static_cast<const DrawRecordOp&>(base_op);
        str << "record=" << ToString(op.record);
        break;
      }
      case PaintOpType::kDrawRect: {
        const auto& op = static_cast<const DrawRectOp&>(base_op);
        str << "rect=" << ToString(op.rect) << ", flags=" << ToString(op.flags);
        break;
      }
      case PaintOpType::kDrawRRect: {
        const auto& op = static_cast<const DrawRRectOp&>(base_op);
        str << "rrect=" << ToString(op.rrect)
            << ", flags=" << ToString(op.flags);
        break;
      }
      case PaintOpType::kDrawScrollingContents: {
        str << "scroll_element_id="
            << static_cast<const DrawScrollingContentsOp&>(base_op)
                   .scroll_element_id;
        break;
      }
      case PaintOpType::kDrawSkottie: {
        const auto& op = static_cast<const DrawSkottieOp&>(base_op);
        str << "skottie=" << ToString(op.skottie)
            << ", dst=" << ToString(op.dst) << ", t=" << op.t;
        break;
      }
      case PaintOpType::kDrawTextBlob: {
        const auto& op = static_cast<const DrawTextBlobOp&>(base_op);
        str << "blob=" << ToString(op.blob) << ", x=" << ToString(op.x)
            << ", y=" << ToString(op.y) << ", flags=" << ToString(op.flags);
        break;
      }
      case PaintOpType::kDrawSlug: {
        const auto& op = static_cast<const DrawSlugOp&>(base_op);
        str << "flags=" << ToString(op.flags);
        break;
      }
      case PaintOpType::kDrawVertices: {
        const auto& op = static_cast<const DrawVerticesOp&>(base_op);
        str << "flags=" << ToString(op.flags);
        break;
      }
      case PaintOpType::kNoop:
        break;
      case PaintOpType::kRestore:
        break;
      case PaintOpType::kRotate: {
        const auto& op = static_cast<const RotateOp&>(base_op);
        str << "degrees=" << ToString(op.degrees);
        break;
      }
      case PaintOpType::kSave:
        break;
      case PaintOpType::kSaveLayer: {
        const auto& op = static_cast<const SaveLayerOp&>(base_op);
        str << "bounds=" << ToString(op.bounds)
            << ", flags=" << ToString(op.flags);
        break;
      }
      case PaintOpType::kSaveLayerAlpha: {
        const auto& op = static_cast<const SaveLayerAlphaOp&>(base_op);
        str << "bounds=" << ToString(op.bounds)
            << ", alpha=" << ToString(op.alpha);
        break;
      }
      case PaintOpType::kSaveLayerFilters: {
        const auto& op = static_cast<const SaveLayerFiltersOp&>(base_op);
        str << "flags=" << ToString(op.flags)
            << ", filters=" << ToString(op.filters)
            << ", backdrop_filter=" << ToString(op.backdrop_filter);
        break;
      }
      case PaintOpType::kScale: {
        const auto& op = static_cast<const ScaleOp&>(base_op);
        str << "sx=" << ToString(op.sx) << ", sy=" << ToString(op.sy);
        break;
      }
      case PaintOpType::kSetMatrix: {
        const auto& op = static_cast<const SetMatrixOp&>(base_op);
        str << "matrix=" << ToString(op.matrix);
        break;
      }
      case PaintOpType::kTranslate: {
        const auto& op = static_cast<const TranslateOp&>(base_op);
        str << "dx=" << ToString(op.dx) << ", dy=" << ToString(op.dy);
        break;
      }
      case PaintOpType::kSetNodeId: {
        const auto& op = static_cast<const SetNodeIdOp&>(base_op);
        str << "id=" << op.node_id;
        break;
      }
    }
    str << ")";
    return str.str();
  }

  static std::string ToString(uint8_t value) {
    return base::StringPrintf("%d", value);
  }

  static std::string ToString(const SkScalar& scalar) {
    return base::StringPrintf("%.3f", scalar);
  }

  static std::string ToString(const SkPoint& point) {
    return base::StringPrintf("[%.3f,%.3f]", point.fX, point.fY);
  }

  static std::string ToString(const SkRect& rect) {
    return base::StringPrintf("[%.3f,%.3f %.3fx%.3f]", rect.x(), rect.y(),
                              rect.width(), rect.height());
  }

  static std::string ToString(const SkIRect& rect) {
    return base::StringPrintf("[%d,%d %dx%d]", rect.x(), rect.y(), rect.width(),
                              rect.height());
  }

  static std::string ToString(const SkRRect& rect) {
    return base::StringPrintf("[bounded by %.3f,%.3f %.3fx%.3f]",
                              rect.rect().x(), rect.rect().y(),
                              rect.rect().width(), rect.rect().height());
  }

  static std::string ToString(const SkRegion& rect) {
    return ToString(rect.getBounds());
  }

  static std::string ToString(const SkM44& matrix) {
    return base::StringPrintf(
        "[%8.4f %8.4f %8.4f %8.4f][%8.4f %8.4f %8.4f %8.4f][%8.4f %8.4f %8.4f "
        "%8.4f][%8.4f %8.4f %8.4f %8.4f]]",
        matrix.rc(0, 0), matrix.rc(0, 1), matrix.rc(0, 2), matrix.rc(0, 3),
        matrix.rc(1, 0), matrix.rc(1, 1), matrix.rc(1, 2), matrix.rc(1, 3),
        matrix.rc(2, 0), matrix.rc(2, 1), matrix.rc(2, 2), matrix.rc(2, 3),
        matrix.rc(3, 0), matrix.rc(3, 1), matrix.rc(3, 2), matrix.rc(3, 3));
  }

  static std::string ToString(const SkMatrix& matrix) {
    return base::StringPrintf(
        "[%8.4f %8.4f %8.4f][%8.4f %8.4f %8.4f][%8.4f %8.4f %8.4f]]",
        matrix.rc(0, 0), matrix.rc(0, 1), matrix.rc(0, 2), matrix.rc(1, 0),
        matrix.rc(1, 1), matrix.rc(1, 2), matrix.rc(2, 0), matrix.rc(2, 1),
        matrix.rc(2, 2));
  }

  static std::string ToString(const SkColor& color) {
    return base::StringPrintf("rgba(%d, %d, %d, %d)", SkColorGetR(color),
                              SkColorGetG(color), SkColorGetB(color),
                              SkColorGetA(color));
  }

  static std::string ToString(const SkColor4f& color) {
    return base::StringPrintf("rgba(%f, %f, %f, %f)", color.fR, color.fG,
                              color.fB, color.fA);
  }

  static std::string ToString(const SkISize& size) {
    return base::StringPrintf("SkISize(%d, %d)", size.width(), size.height());
  }

  static std::string ToString(const SkIPoint& point) {
    return base::StringPrintf("SkIPoint(%d, %d)", point.x(), point.y());
  }

  static std::string ToString(const SkPoint3& point) {
    return base::StringPrintf(
        "SkPoint3(%s, %s, %s)", ToString(point.x()).c_str(),
        ToString(point.y()).c_str(), ToString(point.z()).c_str());
  }

  static std::string ToString(SkColorChannel channel) {
    switch (channel) {
      case SkColorChannel::kR:
        return "kR";
      case SkColorChannel::kG:
        return "kG";
      case SkColorChannel::kB:
        return "kB";
      case SkColorChannel::kA:
        return "kA";
    }
    NOTREACHED();
  }

  static std::string ToString(const SkBlendMode& mode) {
    switch (mode) {
      default:
        break;
      case SkBlendMode::kClear:
        return "kClear";
      case SkBlendMode::kSrc:
        return "kSrc";
      case SkBlendMode::kDst:
        return "kDst";
      case SkBlendMode::kSrcOver:
        return "kSrcOver";
      case SkBlendMode::kDstOver:
        return "kDstOver";
      case SkBlendMode::kSrcIn:
        return "kSrcIn";
      case SkBlendMode::kDstIn:
        return "kDstIn";
      case SkBlendMode::kSrcOut:
        return "kSrcOut";
      case SkBlendMode::kDstOut:
        return "kDstOut";
      case SkBlendMode::kSrcATop:
        return "kSrcATop";
      case SkBlendMode::kDstATop:
        return "kDstATop";
      case SkBlendMode::kXor:
        return "kXor";
      case SkBlendMode::kPlus:
        return "kPlus";
      case SkBlendMode::kModulate:
        return "kModulate";
      case SkBlendMode::kScreen:
        return "kScreen";
      case SkBlendMode::kOverlay:
        return "kOverlay";
      case SkBlendMode::kDarken:
        return "kDarken";
      case SkBlendMode::kLighten:
        return "kLighten";
      case SkBlendMode::kColorDodge:
        return "kColorDodge";
      case SkBlendMode::kColorBurn:
        return "kColorBurn";
      case SkBlendMode::kHardLight:
        return "kHardLight";
      case SkBlendMode::kSoftLight:
        return "kSoftLight";
      case SkBlendMode::kDifference:
        return "kDifference";
      case SkBlendMode::kExclusion:
        return "kExclusion";
      case SkBlendMode::kMultiply:
        return "kMultiply";
      case SkBlendMode::kHue:
        return "kHue";
      case SkBlendMode::kSaturation:
        return "kSaturation";
      case SkBlendMode::kColor:
        return "kColor";
      case SkBlendMode::kLuminosity:
        return "kLuminosity";
    }
    return "<unknown SkBlendMode>";
  }

  static std::string ToString(const SkClipOp& op) {
    switch (op) {
      default:
        break;
      case SkClipOp::kDifference:
        return "kDifference";
      case SkClipOp::kIntersect:
        return "kIntersect";
    }
    return "<unknown SkClipOp>";
  }

  static std::string ToString(const SkData& data) { return "<SkData>"; }

  static std::string ToString(const SkPath& path) {
    // TODO(vmpstr): SkPath has a dump function which we can use here?
    return "<SkPath>";
  }

  static std::string ToString(PaintFlags::FilterQuality quality) {
    switch (quality) {
      case PaintFlags::FilterQuality::kNone:
        return "kNone_SkFilterQuality";
      case PaintFlags::FilterQuality::kLow:
        return "kLow_SkFilterQuality";
      case PaintFlags::FilterQuality::kMedium:
        return "kMedium_SkFilterQuality";
      case PaintFlags::FilterQuality::kHigh:
        return "kHigh_SkFilterQuality";
    }
    return "<unknown FilterQuality>";
  }

  static std::string ToString(PaintFlags::Cap cap) {
    switch (cap) {
      case PaintFlags::kButt_Cap:
        return "kButt_Cap";
      case PaintFlags::kRound_Cap:
        return "kRound_Cap";
      case PaintFlags::kSquare_Cap:
        return "kSquare_Cap";
    }
    return "<unknown PaintFlags::Cap>";
  }

  static std::string ToString(PaintFlags::Join join) {
    switch (join) {
      case PaintFlags::kMiter_Join:
        return "kMiter_Join";
      case PaintFlags::kRound_Join:
        return "kRound_Join";
      case PaintFlags::kBevel_Join:
        return "kBevel_Join";
    }
    return "<unknown PaintFlags::Join>";
  }

  static std::string ToString(TurbulencePaintFilter::TurbulenceType type) {
    switch (type) {
      case TurbulencePaintFilter::TurbulenceType::kTurbulence:
        return "kTurbulence";
      case TurbulencePaintFilter::TurbulenceType::kFractalNoise:
        return "kFractalNoise";
    }
    return "<unknown TurbulencePaintFilter::TurbulenceType>";
  }

  static std::string ToString(MorphologyPaintFilter::MorphType type) {
    switch (type) {
      case MorphologyPaintFilter::MorphType::kDilate:
        return "kDilate";
      case MorphologyPaintFilter::MorphType::kErode:
        return "kErode";
    }
    return "<unknown MorphologyPaintFilter::MorphType>";
  }

  static std::string ToString(PaintFilter::LightingType type) {
    switch (type) {
      case PaintFilter::LightingType::kDiffuse:
        return "kDiffuse";
      case PaintFilter::LightingType::kSpecular:
        return "kSpecular";
    }
    return "<unknown PaintFilter::LightingType>";
  }

  static std::string ToString(DropShadowPaintFilter::ShadowMode shadow_mode) {
    switch (shadow_mode) {
      case DropShadowPaintFilter::ShadowMode::kDrawShadowAndForeground:
        return "kDrawShadowAndForeground";
      case DropShadowPaintFilter::ShadowMode::kDrawShadowOnly:
        return "kDrawShadowOnly";
    }
    return "<unknown DropShadowPaintFilter::ShadowMode>";
  }

  static std::string ToString(const SkImageFilters::Dither dither) {
    switch (dither) {
      case SkImageFilters::Dither::kNo:
        return "kNo";
      case SkImageFilters::Dither::kYes:
        return "kYes";
    }
    return "<unknown SkImageFilters::Dither>";
  }

  static std::string ToString(const ColorFilter& filter) {
    return "ColorFilter";
  }

  static std::string ToString(const PathEffect& effect) { return "PathEffect"; }

  static std::string ToString(const DrawLooper& looper) { return "DrawLooper"; }

  static std::string ToString(PaintCanvas::AnnotationType type) {
    switch (type) {
      default:
        break;
      case PaintCanvas::AnnotationType::kUrl:
        return "URL";
      case PaintCanvas::AnnotationType::kNameDestination:
        return "NAMED_DESTINATION";
      case PaintCanvas::AnnotationType::kLinkToDestination:
        return "LINK_TO_DESTINATION";
    }
    return "<unknown AnnotationType>";
  }

  static std::string ToString(const SkCanvas::SrcRectConstraint& constraint) {
    switch (constraint) {
      default:
        break;
      case SkCanvas::kStrict_SrcRectConstraint:
        return "kStrict_SrcRectConstraint";
      case SkCanvas::kFast_SrcRectConstraint:
        return "kFast_SrcRectConstraint";
    }
    return "<unknown SrcRectConstraint>";
  }

  static std::string ToString(PaintShader::ScalingBehavior behavior) {
    switch (behavior) {
      case PaintShader::ScalingBehavior::kRasterAtScale:
        return "kRasterAtScale";
      case PaintShader::ScalingBehavior::kFixedScale:
        return "kFixedScale";
    }
    return "<unknown ScalingBehavior>";
  }

  static std::string ToString(PaintShader::Type type) {
    switch (type) {
      case PaintShader::Type::kEmpty:
        return "kEmpty";
      case PaintShader::Type::kColor:
        return "kColor";
      case PaintShader::Type::kLinearGradient:
        return "kLinearGradient";
      case PaintShader::Type::kRadialGradient:
        return "kRadialGradient";
      case PaintShader::Type::kTwoPointConicalGradient:
        return "kTwoPointConicalGradient";
      case PaintShader::Type::kSweepGradient:
        return "kSweepGradient";
      case PaintShader::Type::kImage:
        return "kImage";
      case PaintShader::Type::kPaintRecord:
        return "kPaintRecord";
      case PaintShader::Type::kSkSLCommand:
        return "kSkSLCommand";
      case PaintShader::Type::kShaderCount:
        return "kShaderCount";
    }
    return "<unknown PaintShader::Type>";
  }

  static std::string ToString(const PaintImage& image) {
    return "<paint image>";
  }

  static std::string ToString(const SkottieWrapper& skottie) {
    std::ostringstream str;
    str << "<skottie [";
    str << "duration=" << skottie.duration() << " seconds";
    str << ", width=" << ToString(skottie.size().width());
    str << ", height=" << ToString(skottie.size().height());
    str << "]";
    return str.str();
  }

  static std::string ToString(const PaintRecord& record) {
    std::ostringstream str;
    str << "<PaintRecord>[";
    bool is_first = true;
    for (const PaintOp& op : record) {
      if (!is_first) {
        str << ", ";
      }
      str << ToString(op);
      is_first = false;
    }
    str << "]";
    return str.str();
  }

  static std::string ToString(const SkTextBlob& blob) {
    return "<sk text blob>";
  }

  static std::string ToString(SkTileMode tile_mode) {
    switch (tile_mode) {
      case SkTileMode::kClamp:
        return "kClamp";
      case SkTileMode::kRepeat:
        return "kRepeat";
      case SkTileMode::kMirror:
        return "kMirror";
      case SkTileMode::kDecal:
        return "kDecal";
    }
    return "<unknown SkTileMode>";
  }

  static std::string ToString(const gfx::SizeF& size) {
    return base::StringPrintf("[%s]", size.ToString().c_str());
  }

  static std::string ToString(const PaintShader& shader) {
    std::ostringstream str;
    str << "[type=" << ToString(shader.shader_type());
    str << ", flags=" << shader.flags_;
    str << ", end_radius=" << shader.end_radius_;
    str << ", start_radius=" << shader.start_radius_;
    str << ", tx=" << static_cast<unsigned>(shader.tx_);
    str << ", ty=" << static_cast<unsigned>(shader.ty_);
    str << ", fallback_color=" << ToString(shader.fallback_color_);
    str << ", scaling_behavior=" << ToString(shader.scaling_behavior_);
    str << ", local_matrix=" << ToString(shader.local_matrix_);
    str << ", center=" << ToString(shader.center_);
    str << ", tile=" << ToString(shader.tile_);
    str << ", start_point=" << ToString(shader.start_point_);
    str << ", end_point=" << ToString(shader.end_point_);
    str << ", start_degrees=" << shader.start_degrees_;
    str << ", end_degrees=" << shader.end_degrees_;
    if (shader.shader_type() == PaintShader::Type::kImage) {
      str << ", image=" << ToString(shader.image_);
    } else {
      str << ", image=(nil)";
    }
    str << ", record=" << ToString(shader.record_);
    str << ", id=" << shader.id_;
    str << ", tile_scale=" << ToString(shader.tile_scale_);
    if (shader.colors_.size() > 0) {
      str << ", colors=[" << ToString(shader.colors_[0]);
      for (size_t i = 1; i < shader.colors_.size(); ++i) {
        str << ", " << ToString(shader.colors_[i]);
      }
      str << "]";
    } else {
      str << ", colors=(nil)";
    }
    if (shader.positions_.size() > 0) {
      str << ", positions=[" << shader.positions_[0];
      for (size_t i = 1; i < shader.positions_.size(); ++i) {
        str << ", " << shader.positions_[i];
      }
      str << "]";
    } else {
      str << ", positions=(nil)";
    }
    str << "]";

    return str.str();
  }

  static std::string ToString(const PaintFilter& base_filter) {
    std::ostringstream str;
    str << std::boolalpha;
    switch (base_filter.type()) {
      case PaintFilter::Type::kNullFilter:
        str << "NullFilter()";
        break;
      case PaintFilter::Type::kColorFilter: {
        const auto& filter =
            static_cast<const ColorFilterPaintFilter&>(base_filter);
        str << "ColorFilterPaintFilter("
            << "color_filter=" << ToString(filter.color_filter())
            << ", input=" << ToString(filter.input())
            << ", crop_rect=" << ToString(filter.GetCropRect()) << ")";
        break;
      }
      case PaintFilter::Type::kBlur: {
        const auto& filter = static_cast<const BlurPaintFilter&>(base_filter);

        str << "BlurPaintFilter("
            << "sigma_x=" << ToString(filter.sigma_x())
            << ", sigma_y=" << ToString(filter.sigma_y())
            << ", tile_mode=" << ToString(filter.tile_mode())
            << ", input=" << ToString(filter.input())
            << ", crop_rect=" << ToString(filter.GetCropRect()) << ")";
        break;
      }
      case PaintFilter::Type::kDropShadow: {
        const auto& filter =
            static_cast<const DropShadowPaintFilter&>(base_filter);
        str << "DropShadowPaintFilter("
            << "dx=" << ToString(filter.dx())
            << ", dy=" << ToString(filter.dy())
            << ", sigma_x=" << ToString(filter.sigma_x())
            << ", sigma_y=" << ToString(filter.sigma_y())
            << ", color=" << ToString(filter.color())
            << ", shadow_mode=" << ToString(filter.shadow_mode())
            << ", input=" << ToString(filter.input())
            << ", crop_rect=" << ToString(filter.GetCropRect()) << ")";
        break;
      }
      case PaintFilter::Type::kMagnifier: {
        const auto& filter =
            static_cast<const MagnifierPaintFilter&>(base_filter);
        str << "MagnifierPaintFilter("
            << "lens_bounds=" << ToString(filter.lens_bounds())
            << ", zoom_amount=" << ToString(filter.zoom_amount())
            << ", inset=" << ToString(filter.inset())
            << ", input=" << ToString(filter.input())
            << ", crop_rect=" << ToString(filter.GetCropRect()) << ")";
        break;
      }
      case PaintFilter::Type::kCompose: {
        const auto& filter =
            static_cast<const ComposePaintFilter&>(base_filter);
        str << "ComposePaintFilter("
            << "outer=" << ToString(filter.outer())
            << ", inner=" << ToString(filter.inner())
            << ", crop_rect=" << ToString(filter.GetCropRect()) << ")";
        break;
      }
      case PaintFilter::Type::kAlphaThreshold: {
        const auto& filter =
            static_cast<const AlphaThresholdPaintFilter&>(base_filter);
        str << "AlphaThresholdPaintFilter("
            << "region=" << ToString(filter.region())
            << ", input=" << ToString(filter.input())
            << ", crop_rect=" << ToString(filter.GetCropRect()) << ")";
        break;
      }
      case PaintFilter::Type::kXfermode: {
        const auto& filter =
            static_cast<const XfermodePaintFilter&>(base_filter);
        str << "XfermodePaintFilter("
            << "blend_mode=" << ToString(filter.blend_mode())
            << ", background=" << ToString(filter.background())
            << ", foreground=" << ToString(filter.foreground())
            << ", crop_rect=" << ToString(filter.GetCropRect()) << ")";
        break;
      }
      case PaintFilter::Type::kArithmetic: {
        const auto& filter =
            static_cast<const ArithmeticPaintFilter&>(base_filter);
        str << "ArithmeticPaintFilter("
            << "k1=" << filter.k1() << ", k2=" << filter.k2()
            << ", k3=" << filter.k3() << ", k4=" << filter.k4()
            << ", enfore_pm_color=" << filter.enforce_pm_color()
            << ", background=" << ToString(filter.background())
            << ", foreground=" << ToString(filter.foreground())
            << ", crop_rect=" << ToString(filter.GetCropRect()) << ")";
        break;
      }
      case PaintFilter::Type::kMatrixConvolution: {
        const auto& filter =
            static_cast<const MatrixConvolutionPaintFilter&>(base_filter);
        const SkISize& kernel_size = filter.kernel_size();
        str << "MatrixConvolutionPaintFilter("
            << "kernel_size=" << ToString(kernel_size) << ", kernel=[";
        for (int32_t i = 0; i < kernel_size.width() * kernel_size.height();
             ++i) {
          str << (i != 0 ? ", " : "") << ToString(filter.kernel_at(i));
        }
        str << "], gain=" << ToString(filter.gain())
            << ", bias=" << ToString(filter.bias())
            << ", kernel_offset=" << ToString(filter.kernel_offset())
            << ", tile_mode=" << ToString(filter.tile_mode())
            << ", convolve_alpha=" << filter.convolve_alpha()
            << ", input=" << ToString(filter.input())
            << ", crop_rect=" << ToString(filter.GetCropRect()) << ")";
        break;
      }
      case PaintFilter::Type::kDisplacementMapEffect: {
        const auto& filter =
            static_cast<const DisplacementMapEffectPaintFilter&>(base_filter);
        str << "DisplacementMapEffectPaintFilter("
            << "channel_x=" << ToString(filter.channel_x())
            << ", channel_y=" << ToString(filter.channel_y())
            << ", scale=" << ToString(filter.scale())
            << ", displacement=" << ToString(filter.displacement())
            << ", color=" << ToString(filter.color())
            << ", crop_rect=" << ToString(filter.GetCropRect()) << ")";
        break;
      }
      case PaintFilter::Type::kImage: {
        const auto& filter = static_cast<const ImagePaintFilter&>(base_filter);
        str << "ImagePaintFilter("
            << "image=" << ToString(filter.image())
            << ", src_rect=" << ToString(filter.src_rect())
            << ", dst_rect=" << ToString(filter.dst_rect())
            << ", filter_quality=" << ToString(filter.filter_quality())
            << ", crop_rect=" << ToString(filter.GetCropRect()) << ")";
        break;
      }
      case PaintFilter::Type::kPaintRecord: {
        const auto& filter = static_cast<const RecordPaintFilter&>(base_filter);
        str << "RecordPaintFilter("
            << "record=" << ToString(filter.record())
            << ", record_bounds=" << ToString(filter.record_bounds())
            << ", raster_scale=" << ToString(filter.raster_scale())
            << ", scaling_behavior=" << ToString(filter.scaling_behavior())
            << ", crop_rect=" << ToString(filter.GetCropRect()) << ")";
        break;
      }
      case PaintFilter::Type::kMerge: {
        const auto& filter = static_cast<const MergePaintFilter&>(base_filter);
        str << "MergePaintFilter("
            << "input_count=" << filter.input_count() << ", input=[";
        for (size_t i = 0; i < filter.input_count(); ++i) {
          str << (i != 0 ? ", " : "") << ToString(filter.input_at(i));
        }
        str << "], crop_rect=" << ToString(filter.GetCropRect()) << ")";
        break;
      }
      case PaintFilter::Type::kMorphology: {
        const auto& filter =
            static_cast<const MorphologyPaintFilter&>(base_filter);
        str << "MorphologyPaintFilter("
            << "morph_type=" << ToString(filter.morph_type())
            << ", radius_x=" << filter.radius_x()
            << ", radius_y=" << filter.radius_y()
            << ", input=" << ToString(filter.input())
            << ", crop_rect=" << ToString(filter.GetCropRect()) << ")";
        break;
      }
      case PaintFilter::Type::kOffset: {
        const auto& filter = static_cast<const OffsetPaintFilter&>(base_filter);
        str << "OffsetPaintFilter("
            << "dx=" << filter.dx() << ", dy=" << filter.dy()
            << ", input=" << ToString(filter.input())
            << ", crop_rect=" << ToString(filter.GetCropRect()) << ")";
        break;
      }
      case PaintFilter::Type::kTile: {
        const auto& filter = static_cast<const TilePaintFilter&>(base_filter);
        str << "TilePaintFilter("
            << "src=" << ToString(filter.src())
            << ", dst=" << ToString(filter.dst())
            << ", input=" << ToString(filter.input())
            << ", crop_rect=" << ToString(filter.GetCropRect()) << ")";
        break;
      }
      case PaintFilter::Type::kTurbulence: {
        const auto& filter =
            static_cast<const TurbulencePaintFilter&>(base_filter);
        str << "TurbulencePaintFilter("
            << "turbulence_type=" << ToString(filter.turbulence_type())
            << ", base_frequency_x=" << ToString(filter.base_frequency_x())
            << ", base_frequency_y=" << ToString(filter.base_frequency_y())
            << ", num_octaves=" << filter.num_octaves()
            << ", seed=" << ToString(filter.seed())
            << ", tile_size=" << ToString(filter.tile_size())
            << ", crop_rect=" << ToString(filter.GetCropRect()) << ")";
        break;
      }
      case PaintFilter::Type::kShader: {
        const auto& filter = static_cast<const ShaderPaintFilter&>(base_filter);
        str << "ShaderPaintFilter("
            << "shader=" << ToString(filter.shader())
            << ", alpha=" << ToString(filter.alpha())
            << ", filter_quality=" << ToString(filter.filter_quality())
            << ", dither=" << ToString(filter.dither())
            << ", crop_rect=" << ToString(filter.GetCropRect()) << ")";
        break;
      }
      case PaintFilter::Type::kMatrix: {
        const auto& filter = static_cast<const MatrixPaintFilter&>(base_filter);
        str << "MatrixPaintFilter("
            << "matrix=" << ToString(filter.matrix())
            << ", filter_quality=" << ToString(filter.filter_quality())
            << ", input=" << ToString(filter.input())
            << ", crop_rect=" << ToString(filter.GetCropRect()) << ")";
        break;
      }
      case PaintFilter::Type::kLightingDistant: {
        const auto& filter =
            static_cast<const LightingDistantPaintFilter&>(base_filter);
        str << "LightingDistantPaintFilter("
            << "lighting_type=" << ToString(filter.lighting_type())
            << ", direction=" << ToString(filter.direction())
            << ", light_color=" << ToString(filter.light_color())
            << ", surface_scale=" << ToString(filter.surface_scale())
            << ", kconstant=" << ToString(filter.kconstant())
            << ", shininess=" << ToString(filter.shininess())
            << ", input=" << ToString(filter.input())
            << ", crop_rect=" << ToString(filter.GetCropRect()) << ")";
        break;
      }
      case PaintFilter::Type::kLightingPoint: {
        const auto& filter =
            static_cast<const LightingPointPaintFilter&>(base_filter);
        str << "LightingPointPaintFilter("
            << "lighting_type=" << ToString(filter.lighting_type())
            << ", location=" << ToString(filter.location())
            << ", light_color=" << ToString(filter.light_color())
            << ", surface_scale=" << ToString(filter.surface_scale())
            << ", kconstant=" << ToString(filter.kconstant())
            << ", shininess=" << ToString(filter.shininess())
            << ", input=" << ToString(filter.input())
            << ", crop_rect=" << ToString(filter.GetCropRect()) << ")";
        break;
      }
      case PaintFilter::Type::kLightingSpot: {
        const auto& filter =
            static_cast<const LightingSpotPaintFilter&>(base_filter);
        str << "LightingSpotPaintFilter("
            << "lighting_type=" << ToString(filter.lighting_type())
            << ", location=" << ToString(filter.location())
            << ", target=" << ToString(filter.target())
            << ", specular_exponent=" << ToString(filter.specular_exponent())
            << ", cutoff_angle=" << ToString(filter.cutoff_angle())
            << ", light_color=" << ToString(filter.light_color())
            << ", surface_scale=" << ToString(filter.surface_scale())
            << ", kconstant=" << ToString(filter.kconstant())
            << ", shininess=" << ToString(filter.shininess())
            << ", input=" << ToString(filter.input())
            << ", crop_rect=" << ToString(filter.GetCropRect()) << ")";
      }
    }
    return str.str();
  }

  static std::string ToString(const PaintFlags& flags) {
    std::ostringstream str;
    str << std::boolalpha;
    str << "[color=" << ToString(flags.getColor());
    str << ", blendMode=" << ToString(flags.getBlendMode());
    str << ", isAntiAlias=" << flags.isAntiAlias();
    str << ", isDither=" << flags.isDither();
    str << ", filterQuality=" << ToString(flags.getFilterQuality());
    str << ", strokeWidth=" << ToString(flags.getStrokeWidth());
    str << ", strokeMiter=" << ToString(flags.getStrokeMiter());
    str << ", strokeCap=" << ToString(flags.getStrokeCap());
    str << ", strokeJoin=" << ToString(flags.getStrokeJoin());
    str << ", colorFilter=" << ToString(flags.getColorFilter());
    str << ", shader=" << ToString(flags.getShader());
    str << ", hasShader=" << flags.HasShader();
    str << ", shaderIsOpaque=" << (flags.HasShader() && flags.ShaderIsOpaque());
    str << ", pathEffect=" << ToString(flags.getPathEffect());
    str << ", imageFilter=" << ToString(flags.getImageFilter());
    str << ", drawLooper=" << ToString(flags.getLooper());
    str << ", supportsFoldingAlpha=" << flags.SupportsFoldingAlpha();
    str << ", isValid=" << flags.IsValid();
    str << ", hasDiscardableImages=" << flags.HasDiscardableImages();
    str << "]";
    return str.str();
  }

  static std::string ToString(const CorePaintFlags& flags) {
    PaintFlags paint_flags(flags);
    return ToString(paint_flags);
  }
};

}  // namespace cc

#endif  // CC_TEST_PAINT_OP_HELPER_H_
