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

#include "components/viz/common/quads/render_pass_io.h"

#include <optional>
#include <string>
#include <string_view>
#include <utility>
#include <vector>

#include "base/base64.h"
#include "base/bit_cast.h"
#include "base/compiler_specific.h"
#include "base/containers/span.h"
#include "base/json/values_util.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/raw_ptr_exclusion.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_tokenizer.h"
#include "base/values.h"
#include "cc/paint/paint_op_reader.h"
#include "cc/paint/paint_op_writer.h"
#include "components/viz/common/quads/compositor_frame.h"
#include "components/viz/common/quads/compositor_render_pass.h"
#include "components/viz/common/quads/compositor_render_pass_draw_quad.h"
#include "components/viz/common/quads/picture_draw_quad.h"
#include "components/viz/common/quads/solid_color_draw_quad.h"
#include "components/viz/common/quads/surface_draw_quad.h"
#include "components/viz/common/quads/texture_draw_quad.h"
#include "components/viz/common/quads/tile_draw_quad.h"
#include "components/viz/common/quads/video_hole_draw_quad.h"
#include "third_party/skia/modules/skcms/skcms.h"
#include "ui/gfx/geometry/rect_conversions.h"

namespace gfx {
struct HDRMetadata;
}

namespace viz {

namespace {

enum RenderPassField {
  kRenderPassID = 1 << 0,
  kRenderPassOutputRect = 1 << 1,
  kRenderPassDamageRect = 1 << 2,
  kRenderPassTransformToRootTarget = 1 << 3,
  kRenderPassFilters = 1 << 4,
  kRenderPassBackdropFilters = 1 << 5,
  kRenderPassBackdropFilterBounds = 1 << 6,
  kRenderPassColorSpace = 1 << 7,
  kRenderPassHasTransparentBackground = 1 << 8,
  kRenderPassCacheRenderPass = 1 << 9,
  kRenderPassHasDamageFromContributingContent = 1 << 10,
  kRenderPassGenerateMipmap = 1 << 11,
  kRenderPassCopyRequests = 1 << 12,
  kRenderPassQuadList = 1 << 13,
  kRenderPassSharedQuadStateList = 1 << 14,
  kRenderPassHasPreQuadDamage = 1 << 15,
  kRenderPassAllFields = 0xFFFFFFFF,
};

// This controls which fields are processed in CompositorRenderPassToDict() and
// CompositorRenderPassFromDict().
// Values other than kAllFields should never be checked in. This is only for
// local debugging convenience.
RenderPassField g_render_pass_fields = kRenderPassAllFields;

bool ProcessRenderPassField(RenderPassField field) {
  return (g_render_pass_fields & field) == field;
}

base::DictValue RectToDict(const gfx::Rect& rect) {
  return base::DictValue()
      .Set("x", rect.x())
      .Set("y", rect.y())
      .Set("width", rect.width())
      .Set("height", rect.height());
}

bool RectFromDict(const base::DictValue& dict, gfx::Rect* rect) {
  DCHECK(rect);
  std::optional<int> x = dict.FindInt("x");
  std::optional<int> y = dict.FindInt("y");
  std::optional<int> width = dict.FindInt("width");
  std::optional<int> height = dict.FindInt("height");
  if (!x || !y || !width || !height) {
    return false;
  }
  rect->SetRect(x.value(), y.value(), width.value(), height.value());
  return true;
}

base::DictValue RectFToDict(const gfx::RectF& rect) {
  return base::DictValue()
      .Set("x", rect.x())
      .Set("y", rect.y())
      .Set("width", rect.width())
      .Set("height", rect.height());
}

bool RectFFromDict(const base::DictValue& dict, gfx::RectF* rect) {
  DCHECK(rect);
  std::optional<double> x = dict.FindDouble("x");
  std::optional<double> y = dict.FindDouble("y");
  std::optional<double> width = dict.FindDouble("width");
  std::optional<double> height = dict.FindDouble("height");
  if (!x || !y || !width || !height) {
    return false;
  }
  rect->SetRect(static_cast<float>(x.value()), static_cast<float>(y.value()),
                static_cast<float>(width.value()),
                static_cast<float>(height.value()));
  return true;
}

base::DictValue SizeToDict(const gfx::Size& size) {
  return base::DictValue()
      .Set("width", size.width())
      .Set("height", size.height());
}

bool SizeFromDict(const base::DictValue& dict, gfx::Size* size) {
  DCHECK(size);
  std::optional<int> width = dict.FindInt("width");
  std::optional<int> height = dict.FindInt("height");
  if (!width || !height) {
    return false;
  }
  size->set_width(width.value());
  size->set_height(height.value());
  return true;
}

base::DictValue PointToDict(const gfx::Point& point) {
  return base::DictValue().Set("x", point.x()).Set("y", point.y());
}

bool PointFromDict(const base::DictValue& dict, gfx::Point* point) {
  DCHECK(point);
  std::optional<int> x = dict.FindInt("x");
  std::optional<int> y = dict.FindInt("y");
  if (!x || !y) {
    return false;
  }
  point->set_x(x.value());
  point->set_y(y.value());
  return true;
}

base::DictValue SkColor4fToDict(const SkColor4f color) {
  return base::DictValue()
      .Set("red", color.fR)
      .Set("green", color.fG)
      .Set("blue", color.fB)
      .Set("alpha", color.fA);
}

bool SkColor4fFromDict(const base::DictValue& dict, SkColor4f* color) {
  DCHECK(color);
  std::optional<double> red = dict.FindDouble("red");
  std::optional<double> green = dict.FindDouble("green");
  std::optional<double> blue = dict.FindDouble("blue");
  std::optional<double> alpha = dict.FindDouble("alpha");
  if (!red || !green || !blue || !alpha)
    return false;
  color->fR = static_cast<float>(red.value());
  color->fG = static_cast<float>(green.value());
  color->fB = static_cast<float>(blue.value());
  color->fA = static_cast<float>(alpha.value());
  return true;
}

// Many quads now store color as an SkColor4f, but older logs will still store
// SkColors (which are ints). For backward compatibility's sake, read either.
bool ColorFromDict(const base::DictValue& dict,
                   std::string_view key,
                   SkColor4f* output_color) {
  const base::DictValue* color_key = dict.FindDict(key);
  SkColor4f color_4f;
  if (!color_key || !SkColor4fFromDict(*color_key, &color_4f)) {
    std::optional<int> color_int = dict.FindInt(key);
    if (!color_int)
      return false;
    color_4f = SkColor4f::FromColor(static_cast<SkColor>(color_int.value()));
  }
  output_color->fR = color_4f.fR;
  output_color->fG = color_4f.fG;
  output_color->fB = color_4f.fB;
  output_color->fA = color_4f.fA;
  return true;
}

base::DictValue PointFToDict(const gfx::PointF& point) {
  return base::DictValue().Set("x", point.x()).Set("y", point.y());
}

bool PointFFromDict(const base::DictValue& dict, gfx::PointF* point) {
  DCHECK(point);
  std::optional<double> x = dict.FindDouble("x");
  std::optional<double> y = dict.FindDouble("y");
  if (!x || !y) {
    return false;
  }
  point->set_x(static_cast<float>(x.value()));
  point->set_y(static_cast<float>(y.value()));
  return true;
}

base::DictValue Vector2dFToDict(const gfx::Vector2dF& v) {
  return PointFToDict(gfx::PointF(v.x(), v.y()));
}

bool Vector2dFFromDict(const base::DictValue& dict, gfx::Vector2dF* v) {
  DCHECK(v);
  gfx::PointF point;
  if (!PointFFromDict(dict, &point))
    return false;

  v->set_x(point.x());
  v->set_y(point.y());
  return true;
}

base::ListValue FloatArrayToList(base::span<const float> data) {
  base::ListValue list;
  for (float num : data)
    list.Append(num);
  return list;
}

bool FloatArrayFromList(const base::ListValue& list, base::span<float> data) {
  DCHECK(!data.empty());
  size_t count = list.size();
  if (count != data.size()) {
    return false;
  }
  for (size_t ii = 0; ii < count; ++ii) {
    if (!list[ii].is_double())
      return false;
    data[ii] = list[ii].GetDouble();
  }
  return true;
}

#define MAP_RRECTF_TYPE_TO_STRING(NAME) \
  case gfx::RRectF::Type::NAME:         \
    return #NAME;
const char* RRectFTypeToString(gfx::RRectF::Type type) {
  switch (type) {
    MAP_RRECTF_TYPE_TO_STRING(kEmpty)
    MAP_RRECTF_TYPE_TO_STRING(kRect)
    MAP_RRECTF_TYPE_TO_STRING(kSingle)
    MAP_RRECTF_TYPE_TO_STRING(kSimple)
    MAP_RRECTF_TYPE_TO_STRING(kOval)
    MAP_RRECTF_TYPE_TO_STRING(kComplex)
    default:
      NOTREACHED();
  }
}
#undef MAP_RRECTF_TYPE_TO_STRING

#define MAP_STRING_TO_RRECTF_TYPE(NAME) \
  if (str == #NAME)                     \
    return static_cast<int>(gfx::RRectF::Type::NAME);
int StringToRRectFType(const std::string& str) {
  MAP_STRING_TO_RRECTF_TYPE(kEmpty)
  MAP_STRING_TO_RRECTF_TYPE(kRect)
  MAP_STRING_TO_RRECTF_TYPE(kSingle)
  MAP_STRING_TO_RRECTF_TYPE(kSimple)
  MAP_STRING_TO_RRECTF_TYPE(kOval)
  MAP_STRING_TO_RRECTF_TYPE(kComplex)
  return -1;
}
#undef MAP_STRING_TO_RRECTF_TYPE

base::DictValue RRectFToDict(const gfx::RRectF& rect) {
  base::DictValue dict;
  dict.Set("type", RRectFTypeToString(rect.GetType()));
  if (rect.GetType() != gfx::RRectF::Type::kEmpty) {
    dict.Set("rect", RectFToDict(rect.rect()));
    dict.Set("upper_left.x",
             rect.GetCornerRadii(gfx::RRectF::Corner::kUpperLeft).x());
    dict.Set("upper_left.y",
             rect.GetCornerRadii(gfx::RRectF::Corner::kUpperLeft).y());
    dict.Set("upper_right.x",
             rect.GetCornerRadii(gfx::RRectF::Corner::kUpperRight).x());
    dict.Set("upper_right.y",
             rect.GetCornerRadii(gfx::RRectF::Corner::kUpperRight).y());
    dict.Set("lower_right.x",
             rect.GetCornerRadii(gfx::RRectF::Corner::kLowerRight).x());
    dict.Set("lower_right.y",
             rect.GetCornerRadii(gfx::RRectF::Corner::kLowerRight).y());
    dict.Set("lower_left.x",
             rect.GetCornerRadii(gfx::RRectF::Corner::kLowerLeft).x());
    dict.Set("lower_left.y",
             rect.GetCornerRadii(gfx::RRectF::Corner::kLowerLeft).y());
  }
  return dict;
}

bool RRectFFromDict(const base::DictValue& dict, gfx::RRectF* out) {
  DCHECK(out);
  const std::string* type = dict.FindString("type");
  if (!type)
    return false;
  int type_index = StringToRRectFType(*type);
  if (type_index < 0)
    return false;
  gfx::RRectF::Type t_type = static_cast<gfx::RRectF::Type>(type_index);
  if (t_type == gfx::RRectF::Type::kEmpty) {
    *out = gfx::RRectF();
    DCHECK_EQ(gfx::RRectF::Type::kEmpty, out->GetType());
    return true;
  }
  const base::DictValue* rect = dict.FindDict("rect");
  std::optional<double> upper_left_x = dict.FindDouble("upper_left.x");
  std::optional<double> upper_left_y = dict.FindDouble("upper_left.y");
  std::optional<double> upper_right_x = dict.FindDouble("upper_right.x");
  std::optional<double> upper_right_y = dict.FindDouble("upper_right.y");
  std::optional<double> lower_right_x = dict.FindDouble("lower_right.x");
  std::optional<double> lower_right_y = dict.FindDouble("lower_right.y");
  std::optional<double> lower_left_x = dict.FindDouble("lower_left.x");
  std::optional<double> lower_left_y = dict.FindDouble("lower_left.y");
  if (!rect || !upper_left_x || !upper_left_y || !upper_right_x ||
      !upper_right_y || !lower_right_x || !lower_right_y || !lower_left_x ||
      !lower_left_y) {
    return false;
  }
  gfx::RectF rect_f;
  if (!RectFFromDict(*rect, &rect_f))
    return false;
  gfx::RRectF rrectf(rect_f, static_cast<float>(upper_left_x.value()),
                     static_cast<float>(upper_left_y.value()),
                     static_cast<float>(upper_right_x.value()),
                     static_cast<float>(upper_right_y.value()),
                     static_cast<float>(lower_right_x.value()),
                     static_cast<float>(lower_right_y.value()),
                     static_cast<float>(lower_left_x.value()),
                     static_cast<float>(lower_left_y.value()));
  if (rrectf.GetType() != t_type)
    return false;
  *out = rrectf;
  return true;
}

base::Value::BlobStorage SkPathToBlob(const SkPath& path) {
  base::Value::BlobStorage blob(path.writeToMemory(nullptr));
  CHECK(path.writeToMemory(blob.data()));
  return blob;
}

bool SkPathFromBlob(const base::Value::BlobStorage& blob, SkPath* out) {
  if (auto path = SkPath::ReadFromMemory(blob.data(), blob.size())) {
    *out = std::move(*path);
    return true;
  }
  return false;
}

base::DictValue LinearGradientToDict(const gfx::LinearGradient& gradient_mask) {
  base::ListValue steps;
  for (size_t i = 0; i < gradient_mask.step_count(); ++i) {
    steps.Append(
        base::DictValue()
            .Set("fraction",
                 static_cast<double>(gradient_mask.steps()[i].fraction))
            .Set("alpha", static_cast<int>(gradient_mask.steps()[i].alpha)));
  }

  return base::DictValue()
      .Set("angle", static_cast<double>(gradient_mask.angle()))
      .Set("step_count", static_cast<int>(gradient_mask.step_count()))
      .Set("steps", std::move(steps));
}

bool LinearGradientFromDict(const base::DictValue& dict,
                            gfx::LinearGradient* out) {
  std::optional<double> angle = dict.FindDouble("angle");
  std::optional<int> step_count = dict.FindInt("step_count");
  if (!angle || !step_count)
    return false;

  gfx::LinearGradient gradient_mask = gfx::LinearGradient(*angle);
  const base::ListValue* steps = dict.FindList("steps");
  if (!steps)
    return false;
  for (const base::Value& v : *steps) {
    const base::DictValue* step = v.GetIfDict();
    if (!step)
      return false;

    std::optional<double> fraction = step->FindDouble("fraction");
    std::optional<int> alpha = step->FindInt("alpha");
    if (!fraction || !alpha)
      return false;

    gradient_mask.AddStep(*fraction, *alpha);
  }

  *out = gradient_mask;
  return true;
}

base::DictValue MaskFilterInfoToDict(
    const gfx::MaskFilterInfo& mask_filter_info) {
  auto dict = base::DictValue().Set(
      "rounded_corner_bounds",
      RRectFToDict(mask_filter_info.rounded_corner_bounds()));
  if (mask_filter_info.HasGradientMask()) {
    dict.Set("gradient_mask",
             LinearGradientToDict(*mask_filter_info.gradient_mask()));
  }
  return dict;
}

bool MaskFilterInfoFromDict(const base::DictValue& dict,
                            gfx::MaskFilterInfo* out) {
  DCHECK(out);
  const base::DictValue* rounded_corner_bounds =
      dict.FindDict("rounded_corner_bounds");
  if (!rounded_corner_bounds)
    return false;
  gfx::RRectF t_rounded_corner_bounds;
  if (!RRectFFromDict(*rounded_corner_bounds, &t_rounded_corner_bounds))
    return false;

  const base::DictValue* gradient_mask = dict.FindDict("gradient_mask");
  if (!gradient_mask) {
    *out = gfx::MaskFilterInfo(t_rounded_corner_bounds);
    return true;
  }

  gfx::LinearGradient t_gradient_mask;
  if (!LinearGradientFromDict(*gradient_mask, &t_gradient_mask))
    return false;

  *out = gfx::MaskFilterInfo(t_rounded_corner_bounds, t_gradient_mask);
  return true;
}

base::ListValue TransformToList(const gfx::Transform& transform) {
  base::ListValue list;
  float data[16];
  transform.GetColMajorF(data);
  for (float value : data)
    list.Append(value);
  return list;
}

bool TransformFromList(const base::ListValue& list, gfx::Transform* transform) {
  DCHECK(transform);
  if (list.size() != 16)
    return false;
  std::array<float, 16> data;
  for (size_t ii = 0; ii < 16; ++ii) {
    if (!list[ii].is_double())
      return false;
    data[ii] = list[ii].GetDouble();
  }
  *transform = gfx::Transform::ColMajorF(data);
  return true;
}

base::ListValue ShapeRectsToList(const cc::FilterOperation::ShapeRects& shape) {
  base::ListValue list;
  for (const auto& ii : shape) {
    list.Append(RectToDict(ii));
  }
  return list;
}

bool ShapeRectsFromList(const base::ListValue& list,
                        cc::FilterOperation::ShapeRects* shape) {
  DCHECK(shape);
  size_t size = list.size();
  cc::FilterOperation::ShapeRects data;
  data.resize(size);
  for (size_t ii = 0; ii < size; ++ii) {
    const base::Value& dict_value = list[ii];
    if (!dict_value.is_dict())
      return false;
    if (!RectFromDict(dict_value.GetDict(), &data[ii]))
      return false;
  }
  *shape = data;
  return true;
}

std::string PaintFilterToString(const sk_sp<cc::PaintFilter>& filter) {
  // TODO(zmo): Expand to readable fields. Such recorded data becomes invalid
  // when we update any data structure.
  std::vector<uint8_t> buffer(cc::PaintOpWriter::SerializedSize(filter.get()));
  // No need to populate the SerializeOptions here since the security
  // constraints explicitly disable serializing images using the transfer cache
  // and serialization of PaintRecords.
  cc::PaintOp::SerializeOptions options;
  cc::PaintOpWriter writer(buffer, options,
                           true /* enable_security_constraints */);
  writer.Write(filter.get(), SkM44());
  if (writer.size() == 0)
    return "";
  buffer.resize(writer.size());

  return base::Base64Encode(buffer);
}

sk_sp<cc::PaintFilter> PaintFilterFromString(const std::string& encoded) {
  if (encoded.empty()) {
    return nullptr;
  }

  std::string buffer;
  if (!base::Base64Decode(encoded, &buffer))
    return nullptr;

  // We don't need to populate the DeserializeOptions here since the security
  // constraints explicitly disable serializing images using the transfer cache
  // and serialization of PaintRecords.
  std::vector<uint8_t> scratch_buffer;
  cc::PaintOp::DeserializeOptions options{.scratch_buffer = scratch_buffer};
  cc::PaintOpReader reader(base::as_byte_span(buffer), options,
                           /*enable_security_constraints=*/true);
  sk_sp<cc::PaintFilter> filter;
  reader.Read(&filter);
  if (!reader.valid())
    return nullptr;
  // We must have consumed all bytes written when reading this filter.
  if (reader.remaining_bytes() != 0u)
    return nullptr;

  return filter;
}

base::DictValue FilterOperationToDict(const cc::FilterOperation& filter) {
  base::DictValue dict;
  cc::FilterOperation::FilterType type = filter.type();

  dict.Set("type", type);
  if (type != cc::FilterOperation::COLOR_MATRIX &&
      type != cc::FilterOperation::REFERENCE &&
      type != cc::FilterOperation::OFFSET) {
    dict.Set("amount", filter.amount());
  }
  switch (type) {
    case cc::FilterOperation::ALPHA_THRESHOLD:
      dict.Set("shape", ShapeRectsToList(filter.shape()));
      break;
    case cc::FilterOperation::DROP_SHADOW:
      dict.Set("offset", PointToDict(filter.offset()));
      dict.Set("drop_shadow_color",
               SkColor4fToDict(filter.drop_shadow_color()));
      break;
    case cc::FilterOperation::REFERENCE:
      dict.Set("image_filter", PaintFilterToString(filter.image_filter()));
      break;
    case cc::FilterOperation::COLOR_MATRIX:
      dict.Set("matrix", FloatArrayToList(filter.matrix()));
      break;
    case cc::FilterOperation::ZOOM:
      dict.Set("zoom_inset", filter.zoom_inset());
      break;
    case cc::FilterOperation::BLUR:
      dict.Set("blur_tile_mode", static_cast<int>(filter.blur_tile_mode()));
      break;
    case cc::FilterOperation::OFFSET:
      dict.Set("offset", PointToDict(filter.offset()));
      break;
    default:
      break;
  }
  return dict;
}

bool FilterOperationFromDict(const base::Value& dict_value,
                             cc::FilterOperation* out) {
  DCHECK(out);
  if (!dict_value.is_dict()) {
    return false;
  }

  const base::DictValue& dict = dict_value.GetDict();
  std::optional<int> type = dict.FindInt("type");
  std::optional<double> amount = dict.FindDouble("amount");
  const base::DictValue* offset = dict.FindDict("offset");
  const std::string* image_filter = dict.FindString("image_filter");
  const base::ListValue* matrix = dict.FindList("matrix");
  std::optional<int> zoom_inset = dict.FindInt("zoom_inset");
  const base::ListValue* shape = dict.FindList("shape");
  std::optional<int> blur_tile_mode = dict.FindInt("blur_tile_mode");

  cc::FilterOperation filter;

  if (!type)
    return false;
  cc::FilterOperation::FilterType filter_type =
      static_cast<cc::FilterOperation::FilterType>(type.value());
  filter.set_type(filter_type);
  if (filter_type != cc::FilterOperation::COLOR_MATRIX &&
      filter_type != cc::FilterOperation::REFERENCE &&
      filter_type != cc::FilterOperation::OFFSET) {
    if (!amount)
      return false;
    filter.set_amount(static_cast<float>(amount.value()));
  }
  switch (filter_type) {
    case cc::FilterOperation::ALPHA_THRESHOLD: {
      cc::FilterOperation::ShapeRects shape_rects;
      if (!shape || !ShapeRectsFromList(*shape, &shape_rects)) {
        return false;
      }
      filter.set_shape(shape_rects);
    } break;
    case cc::FilterOperation::DROP_SHADOW: {
      gfx::Point drop_shadow_offset;
      if (!offset || !PointFromDict(*offset, &drop_shadow_offset)) {
        return false;
      }
      filter.set_offset(drop_shadow_offset);

      SkColor4f t_drop_shadow_color;
      if (!ColorFromDict(dict, "drop_shadow_color", &t_drop_shadow_color)) {
        return false;
      }
      filter.set_drop_shadow_color(t_drop_shadow_color);
    } break;
    case cc::FilterOperation::REFERENCE:
      if (!image_filter)
        return false;
      filter.set_image_filter(PaintFilterFromString(*image_filter));
      break;
    case cc::FilterOperation::COLOR_MATRIX: {
      cc::FilterOperation::Matrix mat;
      if (!matrix || !FloatArrayFromList(*matrix, mat)) {
        return false;
      }
      filter.set_matrix(mat);
    } break;
    case cc::FilterOperation::ZOOM:
      if (!zoom_inset)
        return false;
      filter.set_zoom_inset(zoom_inset.value());
      break;
    case cc::FilterOperation::BLUR:
      if (!blur_tile_mode)
        return false;
      filter.set_blur_tile_mode(
          static_cast<SkTileMode>(blur_tile_mode.value()));
      break;
    case cc::FilterOperation::OFFSET: {
      gfx::Point filter_offset;
      if (!offset || !PointFromDict(*offset, &filter_offset)) {
        return false;
      }
      filter.set_offset(filter_offset);
    } break;
    default:
      break;
  }

  *out = filter;
  return true;
}

base::ListValue FilterOperationsToList(const cc::FilterOperations& filters) {
  base::ListValue list;
  for (size_t ii = 0; ii < filters.size(); ++ii) {
    base::DictValue filter_dict = FilterOperationToDict(filters.at(ii));
    list.Append(std::move(filter_dict));
  }
  return list;
}

bool FilterOperationsFromList(const base::ListValue& list,
                              cc::FilterOperations* filters) {
  DCHECK(filters);
  cc::FilterOperations data;
  for (const auto& entry : list) {
    cc::FilterOperation filter;
    if (!FilterOperationFromDict(entry, &filter))
      return false;
    data.Append(filter);
  }
  *filters = data;
  return true;
}

#define MATCH_ENUM_CASE(TYPE, NAME) \
  case gfx::ColorSpace::TYPE::NAME: \
    return #NAME;

const char* ColorSpacePrimaryIdToString(gfx::ColorSpace::PrimaryID id) {
  switch (id) {
    MATCH_ENUM_CASE(PrimaryID, INVALID)
    MATCH_ENUM_CASE(PrimaryID, BT709)
    MATCH_ENUM_CASE(PrimaryID, BT470M)
    MATCH_ENUM_CASE(PrimaryID, BT470BG)
    MATCH_ENUM_CASE(PrimaryID, SMPTE170M)
    MATCH_ENUM_CASE(PrimaryID, SMPTE240M)
    MATCH_ENUM_CASE(PrimaryID, FILM)
    MATCH_ENUM_CASE(PrimaryID, BT2020)
    MATCH_ENUM_CASE(PrimaryID, SMPTEST428_1)
    MATCH_ENUM_CASE(PrimaryID, SMPTEST431_2)
    MATCH_ENUM_CASE(PrimaryID, P3)
    MATCH_ENUM_CASE(PrimaryID, XYZ_D50)
    MATCH_ENUM_CASE(PrimaryID, ADOBE_RGB)
    MATCH_ENUM_CASE(PrimaryID, APPLE_GENERIC_RGB)
    MATCH_ENUM_CASE(PrimaryID, WIDE_GAMUT_COLOR_SPIN)
    MATCH_ENUM_CASE(PrimaryID, CUSTOM)
    MATCH_ENUM_CASE(PrimaryID, EBU_3213_E)
  }
}

const char* ColorSpaceTransferIdToString(gfx::ColorSpace::TransferID id) {
  switch (id) {
    MATCH_ENUM_CASE(TransferID, INVALID)
    MATCH_ENUM_CASE(TransferID, BT709)
    MATCH_ENUM_CASE(TransferID, BT709_APPLE)
    MATCH_ENUM_CASE(TransferID, GAMMA18)
    MATCH_ENUM_CASE(TransferID, GAMMA22)
    MATCH_ENUM_CASE(TransferID, GAMMA24)
    MATCH_ENUM_CASE(TransferID, GAMMA28)
    MATCH_ENUM_CASE(TransferID, SMPTE170M)
    MATCH_ENUM_CASE(TransferID, SMPTE240M)
    MATCH_ENUM_CASE(TransferID, LINEAR)
    MATCH_ENUM_CASE(TransferID, LOG)
    MATCH_ENUM_CASE(TransferID, LOG_SQRT)
    MATCH_ENUM_CASE(TransferID, IEC61966_2_4)
    MATCH_ENUM_CASE(TransferID, BT1361_ECG)
    MATCH_ENUM_CASE(TransferID, SRGB)
    MATCH_ENUM_CASE(TransferID, BT2020_10)
    MATCH_ENUM_CASE(TransferID, BT2020_12)
    MATCH_ENUM_CASE(TransferID, PQ)
    MATCH_ENUM_CASE(TransferID, SMPTEST428_1)
    MATCH_ENUM_CASE(TransferID, HLG)
    MATCH_ENUM_CASE(TransferID, SRGB_HDR)
    MATCH_ENUM_CASE(TransferID, LINEAR_HDR)
    MATCH_ENUM_CASE(TransferID, CUSTOM)
    MATCH_ENUM_CASE(TransferID, CUSTOM_HDR)
    MATCH_ENUM_CASE(TransferID, SCRGB_LINEAR_80_NITS)
  }
}

const char* ColorSpaceMatrixIdToString(gfx::ColorSpace::MatrixID id) {
  switch (id) {
    MATCH_ENUM_CASE(MatrixID, INVALID)
    MATCH_ENUM_CASE(MatrixID, RGB)
    MATCH_ENUM_CASE(MatrixID, BT709)
    MATCH_ENUM_CASE(MatrixID, FCC)
    MATCH_ENUM_CASE(MatrixID, BT470BG)
    MATCH_ENUM_CASE(MatrixID, SMPTE170M)
    MATCH_ENUM_CASE(MatrixID, SMPTE240M)
    MATCH_ENUM_CASE(MatrixID, YCOCG)
    MATCH_ENUM_CASE(MatrixID, BT2020_NCL)
    MATCH_ENUM_CASE(MatrixID, YDZDX)
    MATCH_ENUM_CASE(MatrixID, GBR)
  }
}

const char* ColorSpaceRangeIdToString(gfx::ColorSpace::RangeID id) {
  switch (id) {
    MATCH_ENUM_CASE(RangeID, INVALID)
    MATCH_ENUM_CASE(RangeID, LIMITED)
    MATCH_ENUM_CASE(RangeID, FULL)
    MATCH_ENUM_CASE(RangeID, DERIVED)
  }
}
#undef MATCH_ENUM_CASE

#define MATCH_ENUM_CASE(TYPE, NAME) \
  if (token == #NAME)               \
    return static_cast<uint8_t>(gfx::ColorSpace::TYPE::NAME);

uint8_t StringToColorSpacePrimaryId(const std::string& token) {
  MATCH_ENUM_CASE(PrimaryID, INVALID)
  MATCH_ENUM_CASE(PrimaryID, BT709)
  MATCH_ENUM_CASE(PrimaryID, BT470M)
  MATCH_ENUM_CASE(PrimaryID, BT470BG)
  MATCH_ENUM_CASE(PrimaryID, SMPTE170M)
  MATCH_ENUM_CASE(PrimaryID, SMPTE240M)
  MATCH_ENUM_CASE(PrimaryID, FILM)
  MATCH_ENUM_CASE(PrimaryID, BT2020)
  MATCH_ENUM_CASE(PrimaryID, SMPTEST428_1)
  MATCH_ENUM_CASE(PrimaryID, SMPTEST431_2)
  MATCH_ENUM_CASE(PrimaryID, P3)
  MATCH_ENUM_CASE(PrimaryID, XYZ_D50)
  MATCH_ENUM_CASE(PrimaryID, ADOBE_RGB)
  MATCH_ENUM_CASE(PrimaryID, APPLE_GENERIC_RGB)
  MATCH_ENUM_CASE(PrimaryID, WIDE_GAMUT_COLOR_SPIN)
  MATCH_ENUM_CASE(PrimaryID, CUSTOM)
  MATCH_ENUM_CASE(PrimaryID, EBU_3213_E)
  return -1;
}

uint8_t StringToColorSpaceTransferId(const std::string& token) {
  MATCH_ENUM_CASE(TransferID, INVALID)
  MATCH_ENUM_CASE(TransferID, BT709)
  MATCH_ENUM_CASE(TransferID, BT709_APPLE)
  MATCH_ENUM_CASE(TransferID, GAMMA18)
  MATCH_ENUM_CASE(TransferID, GAMMA22)
  MATCH_ENUM_CASE(TransferID, GAMMA24)
  MATCH_ENUM_CASE(TransferID, GAMMA28)
  MATCH_ENUM_CASE(TransferID, SMPTE170M)
  MATCH_ENUM_CASE(TransferID, SMPTE240M)
  MATCH_ENUM_CASE(TransferID, LINEAR)
  MATCH_ENUM_CASE(TransferID, LOG)
  MATCH_ENUM_CASE(TransferID, LOG_SQRT)
  MATCH_ENUM_CASE(TransferID, IEC61966_2_4)
  MATCH_ENUM_CASE(TransferID, BT1361_ECG)
  MATCH_ENUM_CASE(TransferID, SRGB)
  MATCH_ENUM_CASE(TransferID, BT2020_10)
  MATCH_ENUM_CASE(TransferID, BT2020_12)
  MATCH_ENUM_CASE(TransferID, PQ)
  MATCH_ENUM_CASE(TransferID, SMPTEST428_1)
  MATCH_ENUM_CASE(TransferID, HLG)
  MATCH_ENUM_CASE(TransferID, SRGB_HDR)
  MATCH_ENUM_CASE(TransferID, LINEAR_HDR)
  MATCH_ENUM_CASE(TransferID, CUSTOM)
  MATCH_ENUM_CASE(TransferID, CUSTOM_HDR)
  MATCH_ENUM_CASE(TransferID, SCRGB_LINEAR_80_NITS)
  return -1;
}

uint8_t StringToColorSpaceMatrixId(const std::string& token) {
  MATCH_ENUM_CASE(MatrixID, INVALID)
  MATCH_ENUM_CASE(MatrixID, RGB)
  MATCH_ENUM_CASE(MatrixID, BT709)
  MATCH_ENUM_CASE(MatrixID, FCC)
  MATCH_ENUM_CASE(MatrixID, BT470BG)
  MATCH_ENUM_CASE(MatrixID, SMPTE170M)
  MATCH_ENUM_CASE(MatrixID, SMPTE240M)
  MATCH_ENUM_CASE(MatrixID, YCOCG)
  MATCH_ENUM_CASE(MatrixID, BT2020_NCL)
  MATCH_ENUM_CASE(MatrixID, YDZDX)
  MATCH_ENUM_CASE(MatrixID, GBR)
  return -1;
}

uint8_t StringToColorSpaceRangeId(const std::string& token) {
  MATCH_ENUM_CASE(RangeID, INVALID)
  MATCH_ENUM_CASE(RangeID, LIMITED)
  MATCH_ENUM_CASE(RangeID, FULL)
  MATCH_ENUM_CASE(RangeID, DERIVED)
  return -1;
}
#undef MATCH_ENUM_CASE

base::ListValue Matrix3x3ToList(const skcms_Matrix3x3& mat) {
  static_assert(sizeof(mat.vals) == sizeof(float) * 9u);
  // SAFETY: `mat.vals` is 3x3 array of floats.
  return FloatArrayToList(UNSAFE_BUFFERS(base::span(&mat.vals[0][0], 9u)));
}

bool Matrix3x3FromList(const base::ListValue& list, skcms_Matrix3x3& mat) {
  static_assert(sizeof(mat.vals) == sizeof(float) * 9u);
  // SAFETY: `mat.vals` is 3x3 array of floats.
  return FloatArrayFromList(list,
                            UNSAFE_BUFFERS(base::span(&mat.vals[0][0], 9u)));
}

base::ListValue TransferFunctionToList(const skcms_TransferFunction& fn) {
  float data[7];
  data[0] = fn.a;
  data[1] = fn.b;
  data[2] = fn.c;
  data[3] = fn.d;
  data[4] = fn.e;
  data[5] = fn.f;
  data[6] = fn.g;
  return FloatArrayToList(data);
}

bool TransferFunctionFromList(const base::ListValue& list,
                              skcms_TransferFunction* fn) {
  DCHECK(fn);
  std::array<float, 7> data;
  if (!FloatArrayFromList(list, data)) {
    return false;
  }
  fn->a = data[0];
  fn->b = data[1];
  fn->c = data[2];
  fn->d = data[3];
  fn->e = data[4];
  fn->f = data[5];
  fn->g = data[6];
  return true;
}

base::DictValue ColorSpaceToDict(const gfx::ColorSpace& color_space) {
  auto dict =
      base::DictValue()
          .Set("primaries",
               ColorSpacePrimaryIdToString(color_space.GetPrimaryID()))
          .Set("transfer",
               ColorSpaceTransferIdToString(color_space.GetTransferID()))
          .Set("matrix", ColorSpaceMatrixIdToString(color_space.GetMatrixID()))
          .Set("range", ColorSpaceRangeIdToString(color_space.GetRangeID()));
  if (color_space.GetPrimaryID() == gfx::ColorSpace::PrimaryID::CUSTOM) {
    skcms_Matrix3x3 mat;
    color_space.GetPrimaryMatrix(&mat);
    dict.Set("custom_primary_matrix", Matrix3x3ToList(mat));
  }
  if (color_space.GetTransferID() == gfx::ColorSpace::TransferID::CUSTOM ||
      color_space.GetTransferID() == gfx::ColorSpace::TransferID::CUSTOM_HDR) {
    skcms_TransferFunction fn;
    color_space.GetTransferFunction(&fn);
    dict.Set("custom_transfer_params", TransferFunctionToList(fn));
  }
  return dict;
}

bool ColorSpaceFromDict(const base::DictValue& dict,
                        gfx::ColorSpace* color_space) {
  DCHECK(color_space);
  const std::string* primaries = dict.FindString("primaries");
  const std::string* transfer = dict.FindString("transfer");
  const std::string* matrix = dict.FindString("matrix");
  const std::string* range = dict.FindString("range");
  if (!primaries || !transfer || !matrix || !range)
    return false;
  uint8_t primary_id = StringToColorSpacePrimaryId(*primaries);
  uint8_t transfer_id = StringToColorSpaceTransferId(*transfer);
  uint8_t matrix_id = StringToColorSpaceMatrixId(*matrix);
  uint8_t range_id = StringToColorSpaceRangeId(*range);
  if (primary_id < 0 || transfer_id < 0 || matrix_id < 0 || range_id < 0)
    return false;
  skcms_Matrix3x3 t_custom_primary_matrix;
  bool uses_custom_primary_matrix =
      primary_id == static_cast<uint8_t>(gfx::ColorSpace::PrimaryID::CUSTOM);
  if (uses_custom_primary_matrix) {
    const base::ListValue* custom_primary_matrix =
        dict.FindList("custom_primary_matrix");
    if (!custom_primary_matrix ||
        !Matrix3x3FromList(*custom_primary_matrix, t_custom_primary_matrix)) {
      return false;
    }
  }
  skcms_TransferFunction t_custom_transfer_params;
  bool uses_custom_transfer_params =
      transfer_id ==
          static_cast<uint8_t>(gfx::ColorSpace::TransferID::CUSTOM) ||
      transfer_id ==
          static_cast<uint8_t>(gfx::ColorSpace::TransferID::CUSTOM_HDR);
  if (uses_custom_transfer_params) {
    const base::ListValue* custom_transfer_params =
        dict.FindList("custom_transfer_params");
    if (!custom_transfer_params ||
        !TransferFunctionFromList(*custom_transfer_params,
                                  &t_custom_transfer_params)) {
      return false;
    }
  }
  *color_space = gfx::ColorSpace(
      static_cast<gfx::ColorSpace::PrimaryID>(primary_id),
      static_cast<gfx::ColorSpace::TransferID>(transfer_id),
      static_cast<gfx::ColorSpace::MatrixID>(matrix_id),
      static_cast<gfx::ColorSpace::RangeID>(range_id),
      uses_custom_primary_matrix ? &t_custom_primary_matrix : nullptr,
      uses_custom_transfer_params ? &t_custom_transfer_params : nullptr);
  return true;
}

base::ListValue DrawQuadResourceToList(ResourceId resource_id) {
  base::ListValue list;
  if (resource_id != kInvalidResourceId) {
    list.Append(static_cast<int>(resource_id.GetUnsafeValue()));
  }
  return list;
}

bool DrawQuadResourceFromList(const base::ListValue& list,
                              ResourceId& resource_id) {
  size_t size = list.size();
  if (size == 0u) {
    return true;
  }
  // DrawQuad resources are stored as a list as quads used to have multiple
  // resources. Now they should all have at most a single resource.
  if (size > 1) {
    return false;
  }
  if (!list[0].is_int()) {
    return false;
  }

  resource_id = ResourceId(list[0].GetInt());

  return true;
}

base::DictValue SurfaceIdToDict(const SurfaceId& id) {
  return base::DictValue()
      .Set("client_id", static_cast<int>(id.frame_sink_id().client_id()))
      .Set("sink_id", static_cast<int>(id.frame_sink_id().sink_id()))
      .Set("parent_seq",
           static_cast<int>(id.local_surface_id().parent_sequence_number()))
      .Set("child_seq",
           static_cast<int>(id.local_surface_id().child_sequence_number()))
      .Set("embed_token",
           base::UnguessableTokenToValue(id.local_surface_id().embed_token()));
}

std::optional<SurfaceId> SurfaceIdFromDict(const base::DictValue& dict) {
  std::optional<int> client_id = dict.FindInt("client_id");
  std::optional<int> sink_id = dict.FindInt("sink_id");
  std::optional<int> parent_seq = dict.FindInt("parent_seq");
  std::optional<int> child_seq = dict.FindInt("child_seq");
  const base::Value* embed_token_value = dict.Find("embed_token");
  if (!client_id || !sink_id || !parent_seq || !child_seq || !embed_token_value)
    return std::nullopt;

  auto token = base::ValueToUnguessableToken(*embed_token_value);
  if (!token) {
    return std::nullopt;
  }

  return SurfaceId(FrameSinkId(*client_id, *sink_id),
                   LocalSurfaceId(*parent_seq, *child_seq, *token));
}

base::DictValue SurfaceRangeToDict(const SurfaceRange& range) {
  base::DictValue dict;
  if (range.start().has_value())
    dict.Set("start", SurfaceIdToDict(*(range.start())));
  dict.Set("end", SurfaceIdToDict(range.end()));
  return dict;
}

std::optional<SurfaceRange> SurfaceRangeFromDict(const base::DictValue& dict) {
  const base::DictValue* start_dict = dict.FindDict("start");
  const base::DictValue* end_dict = dict.FindDict("end");
  if (!end_dict)
    return std::nullopt;
  std::optional<SurfaceId> start =
      start_dict ? SurfaceIdFromDict(*start_dict) : std::nullopt;
  std::optional<SurfaceId> end = SurfaceIdFromDict(*end_dict);
  if (!end || (start_dict && !start))
    return std::nullopt;

  return SurfaceRange(start, *end);
}

int GetSharedQuadStateIndex(const SharedQuadStateList& shared_quad_state_list,
                            const SharedQuadState* shared_quad_state) {
  for (auto iter = shared_quad_state_list.begin();
       iter != shared_quad_state_list.end(); ++iter) {
    if (*iter == shared_quad_state)
      return static_cast<int>(iter.index());
  }
  return -1;
}

#define MAP_STRING_TO_MATERIAL(NAME) \
  if (str == #NAME)                  \
    return static_cast<int>(DrawQuad::Material::NAME);
int StringToDrawQuadMaterial(const std::string& str) {
  MAP_STRING_TO_MATERIAL(kInvalid)
  MAP_STRING_TO_MATERIAL(kDebugBorder)
  MAP_STRING_TO_MATERIAL(kPictureContent)
  MAP_STRING_TO_MATERIAL(kCompositorRenderPass)
  MAP_STRING_TO_MATERIAL(kSharedElement)
  MAP_STRING_TO_MATERIAL(kSolidColor)
  MAP_STRING_TO_MATERIAL(kSurfaceContent)
  MAP_STRING_TO_MATERIAL(kTextureContent)
  MAP_STRING_TO_MATERIAL(kTiledContent)
  MAP_STRING_TO_MATERIAL(kVideoHole)
  return -1;
}
#undef MAP_STRING_TO_MATERIAL

void DrawQuadCommonToDict(const DrawQuad* draw_quad,
                          base::DictValue* dict,
                          const SharedQuadStateList& shared_quad_state_list) {
  DCHECK(draw_quad);
  DCHECK(dict);
  dict->Set("material", DrawQuadMaterialToString(draw_quad->material));
  dict->Set("rect", RectToDict(draw_quad->rect));
  dict->Set("visible_rect", RectToDict(draw_quad->visible_rect));
  dict->Set("needs_blending", draw_quad->needs_blending);
  int shared_quad_state_index = GetSharedQuadStateIndex(
      shared_quad_state_list, draw_quad->shared_quad_state);
  DCHECK_LE(0, shared_quad_state_index);
  dict->Set("shared_quad_state_index", shared_quad_state_index);
  dict->Set("resources", DrawQuadResourceToList(draw_quad->resource_id));
}

void ContentDrawQuadCommonToDict(const ContentDrawQuadBase* draw_quad,
                                 base::DictValue* dict) {
  DCHECK(draw_quad);
  DCHECK(dict);
  dict->Set("tex_coord_rect", RectFToDict(draw_quad->tex_coord_rect));
  dict->Set("nearest_neighbor", draw_quad->nearest_neighbor);
  dict->Set("force_anti_aliasing_off", draw_quad->force_anti_aliasing_off);
}

struct DrawQuadCommon {
  DrawQuad::Material material = DrawQuad::Material::kInvalid;
  gfx::Rect rect;
  gfx::Rect visible_rect;
  bool needs_blending = false;
  raw_ptr<const SharedQuadState> shared_quad_state = nullptr;
  ResourceId resource_id;
};

std::optional<DrawQuadCommon> GetDrawQuadCommonFromDict(
    const base::DictValue& dict,
    const SharedQuadStateList& shared_quad_state_list) {
  const std::string* material = dict.FindString("material");
  const base::DictValue* rect = dict.FindDict("rect");
  const base::DictValue* visible_rect = dict.FindDict("visible_rect");
  std::optional<bool> needs_blending = dict.FindBool("needs_blending");
  std::optional<int> shared_quad_state_index =
      dict.FindInt("shared_quad_state_index");
  const base::ListValue* resources = dict.FindList("resources");
  if (!material || !rect || !visible_rect || !needs_blending ||
      !shared_quad_state_index || !resources) {
    return std::nullopt;
  }
  int material_index = StringToDrawQuadMaterial(*material);
  if (material_index < 0)
    return std::nullopt;
  int sqs_index = shared_quad_state_index.value();
  if (sqs_index < 0 ||
      static_cast<size_t>(sqs_index) >= shared_quad_state_list.size()) {
    return std::nullopt;
  }
  gfx::Rect t_rect, t_visible_rect;
  if (!RectFromDict(*rect, &t_rect) ||
      !RectFromDict(*visible_rect, &t_visible_rect)) {
    return std::nullopt;
  }
  ResourceId t_resource;
  if (!DrawQuadResourceFromList(*resources, t_resource)) {
    return std::nullopt;
  }

  return DrawQuadCommon{static_cast<DrawQuad::Material>(material_index),
                        t_rect,
                        t_visible_rect,
                        needs_blending.value(),
                        shared_quad_state_list.ElementAt(sqs_index),
                        t_resource};
}

struct ContentDrawQuadCommon {
  gfx::RectF tex_coord_rect;
  bool is_premultiplied;
  bool nearest_neighbor;
  bool force_anti_aliasing_off;
};

std::optional<ContentDrawQuadCommon> GetContentDrawQuadCommonFromDict(
    const base::DictValue& dict) {
  const base::DictValue* tex_coord_rect = dict.FindDict("tex_coord_rect");
  std::optional<bool> is_premultiplied = dict.FindBool("is_premultiplied");
  std::optional<bool> nearest_neighbor = dict.FindBool("nearest_neighbor");
  std::optional<bool> force_anti_aliasing_off =
      dict.FindBool("force_anti_aliasing_off");

  if (!tex_coord_rect || !is_premultiplied || !nearest_neighbor ||
      !force_anti_aliasing_off) {
    return std::nullopt;
  }
  gfx::RectF t_tex_coord_rect;
  if (!RectFFromDict(*tex_coord_rect, &t_tex_coord_rect)) {
    return std::nullopt;
  }

  return ContentDrawQuadCommon{t_tex_coord_rect, is_premultiplied.value(),
                               nearest_neighbor.value(),
                               force_anti_aliasing_off.value()};
}

void CompositorRenderPassDrawQuadToDict(
    const CompositorRenderPassDrawQuad* draw_quad,
    base::DictValue* dict) {
  DCHECK(draw_quad);
  DCHECK(dict);
  dict->Set(
      "render_pass_id",
      base::NumberToString(static_cast<uint64_t>(draw_quad->render_pass_id)));
  dict->Set("mask_uv_rect", RectFToDict(draw_quad->mask_uv_rect));
  dict->Set("mask_texture_size", SizeToDict(draw_quad->mask_texture_size));
  dict->Set("filters_scale", Vector2dFToDict(draw_quad->filters_scale));
  dict->Set("filters_origin", PointFToDict(draw_quad->filters_origin));
  dict->Set("backdrop_filter_quality", draw_quad->backdrop_filter_quality);
  dict->Set("force_anti_aliasing_off", draw_quad->force_anti_aliasing_off);
  dict->Set("intersects_damage_under", draw_quad->intersects_damage_under);
}

void SolidColorDrawQuadToDict(const SolidColorDrawQuad* draw_quad,
                              base::DictValue* dict) {
  DCHECK(draw_quad);
  DCHECK(dict);
  dict->Set("color", SkColor4fToDict(draw_quad->color));
  dict->Set("force_anti_aliasing_off", draw_quad->force_anti_aliasing_off);
}

#define MAP_VIDEO_TYPE_TO_STRING(NAME) \
  case gfx::ProtectedVideoType::NAME:  \
    return #NAME;
const char* ProtectedVideoTypeToString(gfx::ProtectedVideoType type) {
  switch (type) {
    MAP_VIDEO_TYPE_TO_STRING(kClear)
    MAP_VIDEO_TYPE_TO_STRING(kSoftwareProtected)
    MAP_VIDEO_TYPE_TO_STRING(kHardwareProtected)
    default:
      NOTREACHED();
  }
}
#undef MAP_VIDEO_TYPE_TO_STRING

#define MAP_STRING_TO_VIDEO_TYPE(NAME) \
  if (str == #NAME)                    \
    return static_cast<int>(gfx::ProtectedVideoType::NAME);
int StringToProtectedVideoType(const std::string& str) {
  MAP_STRING_TO_VIDEO_TYPE(kClear)
  MAP_STRING_TO_VIDEO_TYPE(kSoftwareProtected)
  MAP_STRING_TO_VIDEO_TYPE(kHardwareProtected)
  return -1;
}
#undef MAP_STRING_TO_VIDEO_TYPE

void SurfaceDrawQuadToDict(const SurfaceDrawQuad* draw_quad,
                           base::DictValue* dict) {
  DCHECK(draw_quad);
  DCHECK(dict);
  dict->Set("surface_range", SurfaceRangeToDict(draw_quad->surface_range));
  dict->Set("default_background_color",
            SkColor4fToDict(draw_quad->default_background_color));
  dict->Set("stretch_content", draw_quad->stretch_content_to_fill_bounds);
  dict->Set("is_reflection", draw_quad->is_reflection);
  dict->Set("allow_merge", draw_quad->allow_merge);
}

}  // namespace

// TODO(crbug.com/451876192): Move this function back into an anonymous
// namespace after the refactor to to make TextureDrawQuad use unnormalized
// coordinates is complete
void TextureDrawQuadToDict(const TextureDrawQuad* draw_quad,
                           base::DictValue* dict) {
  DCHECK(draw_quad);
  DCHECK(dict);
  // Set premultiplied_alpha to not break backwards-compatibility with unit test
  // data.
  dict->Set("premultiplied_alpha", true);
  dict->Set("tex_coord_rect", RectFToDict(draw_quad->tex_coord_rect_));
  dict->Set("is_normalized_coords", draw_quad->is_normalized_coords);
  dict->Set("background_color", SkColor4fToDict(draw_quad->background_color));
  // TODO(crbug.com/40942150): Update
  // "components/test/data/viz/render_pass_data/" to reflect the deprecation of
  // vertex opacity.
  float vertex_opacity[4] = {1.f, 1.0f, 1.0f, 1.f};
  dict->Set("vertex_opacity", FloatArrayToList(vertex_opacity));
  dict->Set("nearest_neighbor", draw_quad->nearest_neighbor);
  dict->Set("secure_output_only", draw_quad->secure_output_only);
  dict->Set("protected_video_type",
            ProtectedVideoTypeToString(draw_quad->protected_video_type));
  if (draw_quad->damage_rect.has_value()) {
    dict->Set("damage_rect", RectToDict(draw_quad->damage_rect.value()));
  }
}

namespace {

void TileDrawQuadToDict(const TileDrawQuad* draw_quad, base::DictValue* dict) {
  DCHECK(draw_quad);
  DCHECK(dict);

  // Set is_premultiplied to not break backwards-compatibility with unit test
  // data.
  dict->Set("is_premultiplied", true);

  ContentDrawQuadCommonToDict(draw_quad, dict);
}

void VideoHoleDrawQuadToDict(const VideoHoleDrawQuad* draw_quad,
                             base::DictValue* dict) {
  DCHECK(draw_quad);
  DCHECK(dict);
  dict->Set("overlay_plane_id.empty", draw_quad->overlay_plane_id.is_empty());
  if (!draw_quad->overlay_plane_id.is_empty()) {
    dict->Set("overlay_plane_id.unguessable_token",
              base::UnguessableTokenToValue(draw_quad->overlay_plane_id));
  }
}

#define UNEXPECTED_DRAW_QUAD_TYPE(NAME) \
  case DrawQuad::Material::NAME:        \
    NOTREACHED() << "Unexpected " << #NAME;

#define WRITE_DRAW_QUAD_TYPE_FIELDS(NAME, TYPE)                    \
  case DrawQuad::Material::NAME:                                   \
    TYPE##ToDict(reinterpret_cast<const TYPE*>(draw_quad), &dict); \
    break;
base::DictValue DrawQuadToDict(
    const DrawQuad* draw_quad,
    const SharedQuadStateList& shared_quad_state_list) {
  DCHECK(draw_quad);
  base::DictValue dict;
  DrawQuadCommonToDict(draw_quad, &dict, shared_quad_state_list);
  switch (draw_quad->material) {
    WRITE_DRAW_QUAD_TYPE_FIELDS(kCompositorRenderPass,
                                CompositorRenderPassDrawQuad)
    WRITE_DRAW_QUAD_TYPE_FIELDS(kSolidColor, SolidColorDrawQuad)
    WRITE_DRAW_QUAD_TYPE_FIELDS(kSurfaceContent, SurfaceDrawQuad)
    WRITE_DRAW_QUAD_TYPE_FIELDS(kTextureContent, TextureDrawQuad)
    WRITE_DRAW_QUAD_TYPE_FIELDS(kTiledContent, TileDrawQuad)
    WRITE_DRAW_QUAD_TYPE_FIELDS(kVideoHole, VideoHoleDrawQuad)
    UNEXPECTED_DRAW_QUAD_TYPE(kPictureContent)
    default:
      break;
  }
  return dict;
}
#undef WRITE_DRAW_QUAD_TYPE_FIELDS
#undef UNEXPECTED_DRAW_QUAD_TYPE

base::ListValue QuadListToList(
    const QuadList& quad_list,
    const SharedQuadStateList& shared_quad_state_list) {
  base::ListValue list;
  for (size_t ii = 0; ii < quad_list.size(); ++ii) {
    list.Append(
        DrawQuadToDict(quad_list.ElementAt(ii), shared_quad_state_list));
  }
  return list;
}

bool CompositorRenderPassDrawQuadFromDict(
    const base::DictValue& dict,
    const DrawQuadCommon& common,
    CompositorRenderPassDrawQuad* draw_quad) {
  DCHECK(draw_quad);

  const std::string* render_pass_id = dict.FindString("render_pass_id");
  const base::DictValue* mask_uv_rect = dict.FindDict("mask_uv_rect");
  const base::DictValue* mask_texture_size = dict.FindDict("mask_texture_size");
  const base::DictValue* filters_scale = dict.FindDict("filters_scale");
  const base::DictValue* filters_origin = dict.FindDict("filters_origin");
  std::optional<double> backdrop_filter_quality =
      dict.FindDouble("backdrop_filter_quality");
  std::optional<bool> force_anti_aliasing_off =
      dict.FindBool("force_anti_aliasing_off");
  std::optional<bool> intersects_damage_under =
      dict.FindBool("intersects_damage_under");

  if (!render_pass_id || !mask_uv_rect || !mask_texture_size ||
      !filters_scale || !filters_origin || !backdrop_filter_quality ||
      !force_anti_aliasing_off) {
    return false;
  }
  uint64_t render_pass_id_as_int;
  gfx::RectF t_mask_uv_rect;
  gfx::Size t_mask_texture_size;
  gfx::Vector2dF t_filters_scale;
  gfx::PointF t_filters_origin;
  if (!base::StringToUint64(*render_pass_id, &render_pass_id_as_int) ||
      !RectFFromDict(*mask_uv_rect, &t_mask_uv_rect) ||
      !SizeFromDict(*mask_texture_size, &t_mask_texture_size) ||
      !Vector2dFFromDict(*filters_scale, &t_filters_scale) ||
      !PointFFromDict(*filters_origin, &t_filters_origin)) {
    return false;
  }
  CompositorRenderPassId t_render_pass_id{render_pass_id_as_int};

  ResourceId mask_resource_id = common.resource_id;
  draw_quad->SetAll(common.shared_quad_state, common.rect, common.visible_rect,
                    common.needs_blending, t_render_pass_id, mask_resource_id,
                    t_mask_uv_rect, t_mask_texture_size, t_filters_scale,
                    t_filters_origin, force_anti_aliasing_off.value(),
                    backdrop_filter_quality.value(),
                    intersects_damage_under && intersects_damage_under.value());
  return true;
}

bool SolidColorDrawQuadFromDict(const base::DictValue& dict,
                                const DrawQuadCommon& common,
                                SolidColorDrawQuad* draw_quad) {
  DCHECK(draw_quad);
  std::optional<bool> force_anti_aliasing_off =
      dict.FindBool("force_anti_aliasing_off");
  if (!force_anti_aliasing_off)
    return false;

  SkColor4f t_color;
  if (!ColorFromDict(dict, "color", &t_color))
    return false;

  draw_quad->SetAll(common.shared_quad_state, common.rect, common.visible_rect,
                    common.needs_blending, t_color,
                    force_anti_aliasing_off.value());
  return true;
}

bool SurfaceDrawQuadFromDict(const base::DictValue& dict,
                             const DrawQuadCommon& common,
                             SurfaceDrawQuad* draw_quad) {
  DCHECK(draw_quad);

  const base::DictValue* surface_range_dict = dict.FindDict("surface_range");
  if (!surface_range_dict)
    return false;
  std::optional<SurfaceRange> surface_range =
      SurfaceRangeFromDict(*surface_range_dict);
  std::optional<bool> stretch_content = dict.FindBool("stretch_content");
  std::optional<bool> is_reflection = dict.FindBool("is_reflection");
  std::optional<bool> allow_merge = dict.FindBool("allow_merge");
  if (!surface_range || !stretch_content || !is_reflection || !allow_merge)
    return false;

  SkColor4f t_default_background_color;
  if (!ColorFromDict(dict, "default_background_color",
                     &t_default_background_color)) {
    return false;
  }

  draw_quad->SetAll(common.shared_quad_state, common.rect, common.visible_rect,
                    common.needs_blending, *surface_range,
                    t_default_background_color, *stretch_content,
                    *is_reflection, *allow_merge);
  return true;
}

bool TextureDrawQuadFromDict(const base::DictValue& dict,
                             const DrawQuadCommon& common,
                             TextureDrawQuad* draw_quad) {
  DCHECK(draw_quad);

  const base::DictValue* tex_coord_rect = dict.FindDict("tex_coord_rect");
  // TODO(crbug.com/40942150): Update
  // "components/test/data/viz/render_pass_data/" to reflect the deprecation of
  // vertex opacity.
  const base::ListValue* vertex_opacity = dict.FindList("vertex_opacity");
  const base::DictValue* damage_rect = dict.FindDict("damage_rect");
  std::optional<bool> nearest_neighbor = dict.FindBool("nearest_neighbor");
  std::optional<bool> secure_output_only = dict.FindBool("secure_output_only");
  std::optional<bool> is_normalized_coords =
      dict.FindBool("is_normalized_coords");
  const std::string* protected_video_type =
      dict.FindString("protected_video_type");

  if (!tex_coord_rect || !vertex_opacity || !nearest_neighbor ||
      !secure_output_only || !protected_video_type) {
    return false;
  }
  int protected_video_type_index =
      StringToProtectedVideoType(*protected_video_type);
  if (protected_video_type_index < 0)
    return false;
  gfx::RectF t_tex_coord_rect;
  SkColor4f t_background_color;
  if (!RectFFromDict(*tex_coord_rect, &t_tex_coord_rect) ||
      !ColorFromDict(dict, "background_color", &t_background_color)) {
    return false;
  }

  ResourceId resource_id = common.resource_id;
  draw_quad->SetAll(
      common.shared_quad_state, common.rect, common.visible_rect,
      common.needs_blending, resource_id, t_tex_coord_rect.origin(),
      t_tex_coord_rect.bottom_right(), t_background_color,
      nearest_neighbor.value(), secure_output_only.value(),
      static_cast<gfx::ProtectedVideoType>(protected_video_type_index),
      is_normalized_coords.value_or(true));

  gfx::Rect t_damage_rect;
  if (damage_rect && RectFromDict(*damage_rect, &t_damage_rect)) {
    draw_quad->damage_rect = t_damage_rect;
  }

  return true;
}

bool TileDrawQuadFromDict(const base::DictValue& dict,
                          const DrawQuadCommon& common,
                          TileDrawQuad* draw_quad) {
  DCHECK(draw_quad);

  std::optional<ContentDrawQuadCommon> content_common =
      GetContentDrawQuadCommonFromDict(dict);
  if (!content_common)
    return false;

  ResourceId resource_id = common.resource_id;

  draw_quad->SetAll(common.shared_quad_state, common.rect, common.visible_rect,
                    common.needs_blending, resource_id,
                    content_common->tex_coord_rect,
                    content_common->nearest_neighbor,
                    content_common->force_anti_aliasing_off);
  return true;
}

bool VideoHoleDrawQuadFromDict(const base::DictValue& dict,
                               const DrawQuadCommon& common,
                               VideoHoleDrawQuad* draw_quad) {
  DCHECK(draw_quad);

  std::optional<bool> overlay_plane_id_empty =
      dict.FindBool("overlay_plane_id.empty");
  if (!overlay_plane_id_empty)
    return false;

  base::UnguessableToken overlay_plane_id;
  DCHECK(overlay_plane_id.is_empty());
  if (!overlay_plane_id_empty.value()) {
    std::optional<base::UnguessableToken> deserialized_overlay_plane_id =
        base::ValueToUnguessableToken(
            dict.Find("overlay_plane_id.unguessable_token"));
    if (!deserialized_overlay_plane_id) {
      return false;
    }
    overlay_plane_id = deserialized_overlay_plane_id.value();
  }
  draw_quad->SetAll(common.shared_quad_state, common.rect, common.visible_rect,
                    common.needs_blending, overlay_plane_id);
  return true;
}

#define UNEXPECTED_DRAW_QUAD_TYPE(NAME) \
  case DrawQuad::Material::NAME:        \
    NOTREACHED() << "Unexpected " << #NAME;
#define GET_QUAD_FROM_DICT(NAME, TYPE)                             \
  case DrawQuad::Material::NAME: {                                 \
    TYPE* quad = quads.AllocateAndConstruct<TYPE>();               \
    if (!list[ii].is_dict())                                       \
      return false;                                                \
    if (!TYPE##FromDict(list[ii].GetDict(), common.value(), quad)) \
      return false;                                                \
  } break;
bool QuadListFromList(const base::ListValue& list,
                      QuadList* quad_list,
                      const SharedQuadStateList& shared_quad_state_list) {
  DCHECK(quad_list);
  size_t size = list.size();
  if (size == 0) {
    quad_list->clear();
    return true;
  }
  QuadList quads(size);
  for (size_t ii = 0; ii < size; ++ii) {
    if (!list[ii].is_dict())
      return false;
    std::optional<DrawQuadCommon> common =
        GetDrawQuadCommonFromDict(list[ii].GetDict(), shared_quad_state_list);
    if (!common)
      return false;
    switch (common->material) {
      GET_QUAD_FROM_DICT(kCompositorRenderPass, CompositorRenderPassDrawQuad)
      GET_QUAD_FROM_DICT(kSolidColor, SolidColorDrawQuad)
      GET_QUAD_FROM_DICT(kSurfaceContent, SurfaceDrawQuad)
      GET_QUAD_FROM_DICT(kTextureContent, TextureDrawQuad)
      GET_QUAD_FROM_DICT(kTiledContent, TileDrawQuad)
      GET_QUAD_FROM_DICT(kVideoHole, VideoHoleDrawQuad)
      UNEXPECTED_DRAW_QUAD_TYPE(kPictureContent)
      default:
        break;
    }
  }
  quad_list->swap(quads);
  return true;
}
#undef GET_QUAD_FROM_DICT
#undef UNEXPECTED_DRAW_QUAD_TYPE

base::DictValue SharedQuadStateToDict(const SharedQuadState& sqs) {
  auto dict =
      base::DictValue()
          .Set("quad_to_target_transform",
               TransformToList(sqs.quad_to_target_transform))
          .Set("quad_layer_rect", RectToDict(sqs.quad_layer_rect))
          .Set("visible_quad_layer_rect",
               RectToDict(sqs.visible_quad_layer_rect))
          .Set("mask_filter_info", MaskFilterInfoToDict(sqs.mask_filter_info))
          .Set("are_contents_opaque", sqs.are_contents_opaque)
          .Set("opacity", sqs.opacity)
          .Set("blend_mode", BlendModeToString(sqs.blend_mode))
          .Set("sorting_context_id", sqs.sorting_context_id)
          .Set("is_fast_rounded_corner", sqs.is_fast_rounded_corner);
  if (sqs.clip_rect) {
    dict.Set("clip_rect", RectToDict(*sqs.clip_rect));
  }
  return dict;
}

#define MAP_STRING_TO_BLEND_MODE(NAME) \
  if (str == #NAME)                    \
    return static_cast<int>(SkBlendMode::NAME);
int StringToBlendMode(const std::string& str) {
  MAP_STRING_TO_BLEND_MODE(kClear)
  MAP_STRING_TO_BLEND_MODE(kSrc)
  MAP_STRING_TO_BLEND_MODE(kDst)
  MAP_STRING_TO_BLEND_MODE(kSrcOver)
  MAP_STRING_TO_BLEND_MODE(kDstOver)
  MAP_STRING_TO_BLEND_MODE(kSrcIn)
  MAP_STRING_TO_BLEND_MODE(kDstIn)
  MAP_STRING_TO_BLEND_MODE(kSrcOut)
  MAP_STRING_TO_BLEND_MODE(kDstOut)
  MAP_STRING_TO_BLEND_MODE(kSrcATop)
  MAP_STRING_TO_BLEND_MODE(kDstATop)
  MAP_STRING_TO_BLEND_MODE(kXor)
  MAP_STRING_TO_BLEND_MODE(kPlus)
  MAP_STRING_TO_BLEND_MODE(kModulate)
  MAP_STRING_TO_BLEND_MODE(kScreen)
  MAP_STRING_TO_BLEND_MODE(kOverlay)
  MAP_STRING_TO_BLEND_MODE(kDarken)
  MAP_STRING_TO_BLEND_MODE(kLighten)
  MAP_STRING_TO_BLEND_MODE(kColorDodge)
  MAP_STRING_TO_BLEND_MODE(kColorBurn)
  MAP_STRING_TO_BLEND_MODE(kHardLight)
  MAP_STRING_TO_BLEND_MODE(kSoftLight)
  MAP_STRING_TO_BLEND_MODE(kDifference)
  MAP_STRING_TO_BLEND_MODE(kExclusion)
  MAP_STRING_TO_BLEND_MODE(kMultiply)
  MAP_STRING_TO_BLEND_MODE(kHue)
  MAP_STRING_TO_BLEND_MODE(kSaturation)
  MAP_STRING_TO_BLEND_MODE(kColor)
  MAP_STRING_TO_BLEND_MODE(kLuminosity)
  return -1;
}
#undef MAP_STRING_TO_BLEND_MODE

bool SharedQuadStateFromDict(const base::DictValue& dict,
                             SharedQuadState* sqs) {
  DCHECK(sqs);
  const base::ListValue* quad_to_target_transform =
      dict.FindList("quad_to_target_transform");
  const base::DictValue* quad_layer_rect = dict.FindDict("quad_layer_rect");
  const base::DictValue* visible_quad_layer_rect =
      dict.FindDict("visible_quad_layer_rect");
  const base::DictValue* mask_filter_info = dict.FindDict("mask_filter_info");
  const base::DictValue* clip_rect = dict.FindDict("clip_rect");
  std::optional<bool> is_clipped = dict.FindBool("is_clipped");
  std::optional<bool> are_contents_opaque =
      dict.FindBool("are_contents_opaque");
  std::optional<double> opacity = dict.FindDouble("opacity");
  const std::string* blend_mode = dict.FindString("blend_mode");
  std::optional<int> sorting_context_id = dict.FindInt("sorting_context_id");
  std::optional<bool> is_fast_rounded_corner =
      dict.FindBool("is_fast_rounded_corner");

  if (!quad_to_target_transform || !quad_layer_rect ||
      !visible_quad_layer_rect || !are_contents_opaque || !opacity ||
      !blend_mode || !sorting_context_id || !is_fast_rounded_corner) {
    return false;
  }
  gfx::Transform t_quad_to_target_transform;
  gfx::Rect t_quad_layer_rect, t_visible_quad_layer_rect, t_clip_rect;
  if (!TransformFromList(*quad_to_target_transform,
                         &t_quad_to_target_transform) ||
      !RectFromDict(*quad_layer_rect, &t_quad_layer_rect) ||
      !RectFromDict(*visible_quad_layer_rect, &t_visible_quad_layer_rect) ||
      (clip_rect && !RectFromDict(*clip_rect, &t_clip_rect))) {
    return false;
  }

  gfx::MaskFilterInfo t_mask_filter_info;
  if (mask_filter_info &&
      !MaskFilterInfoFromDict(*mask_filter_info, &t_mask_filter_info)) {
    return false;
  }

  std::optional<gfx::Rect> clip_rect_opt;
  // Some older files still use the is_clipped field.  If it's present, we'll
  // respect it, and ignore clip_rect if it's false.
  if (is_clipped.has_value()) {
    if (is_clipped.value()) {
      clip_rect_opt = t_clip_rect;
    }
  } else if (clip_rect) {
    clip_rect_opt = t_clip_rect;
  }

  int blend_mode_index = StringToBlendMode(*blend_mode);
  DCHECK_GE(static_cast<int>(SkBlendMode::kLastMode), blend_mode_index);
  if (blend_mode_index < 0)
    return false;
  SkBlendMode t_blend_mode = static_cast<SkBlendMode>(blend_mode_index);
  sqs->SetAll(t_quad_to_target_transform, t_quad_layer_rect,
              t_visible_quad_layer_rect, t_mask_filter_info, clip_rect_opt,
              are_contents_opaque.value(), static_cast<float>(opacity.value()),
              t_blend_mode, sorting_context_id.value(), /*layer_id=*/0u,
              is_fast_rounded_corner.value());
  return true;
}

base::ListValue SharedQuadStateListToList(
    const SharedQuadStateList& shared_quad_state_list) {
  base::ListValue list;
  for (size_t ii = 0; ii < shared_quad_state_list.size(); ++ii)
    list.Append(SharedQuadStateToDict(*(shared_quad_state_list.ElementAt(ii))));
  return list;
}

bool SharedQuadStateListFromList(const base::ListValue& list,
                                 SharedQuadStateList* shared_quad_state_list) {
  DCHECK(shared_quad_state_list);
  size_t size = list.size();
  SharedQuadStateList states(alignof(SharedQuadState), sizeof(SharedQuadState),
                             size);
  for (size_t ii = 0; ii < size; ++ii) {
    if (!list[ii].is_dict())
      return false;
    SharedQuadState* sqs = states.AllocateAndConstruct<SharedQuadState>();
    if (!SharedQuadStateFromDict(list[ii].GetDict(), sqs))
      return false;
  }
  shared_quad_state_list->swap(states);
  return true;
}

base::DictValue GetRenderPassMetadata(const CompositorRenderPass& render_pass) {
  return base::DictValue()
      .Set("render_pass_id",
           base::NumberToString(static_cast<uint64_t>(render_pass.id)))
      .Set("quad_count", static_cast<int>(render_pass.quad_list.size()))
      .Set("shared_quad_state_count",
           static_cast<int>(render_pass.shared_quad_state_list.size()));
}

base::ListValue GetRenderPassListMetadata(
    const CompositorRenderPassList& render_pass_list) {
  base::ListValue metadata;
  for (const auto& render_pass : render_pass_list) {
    metadata.Append(GetRenderPassMetadata(*(render_pass.get())));
  }
  return metadata;
}

}  // namespace

#define MAP_BLEND_MODE_TO_STRING(NAME) \
  case SkBlendMode::NAME:              \
    return #NAME;
const char* BlendModeToString(SkBlendMode blend_mode) {
  switch (blend_mode) {
    MAP_BLEND_MODE_TO_STRING(kClear)
    MAP_BLEND_MODE_TO_STRING(kSrc)
    MAP_BLEND_MODE_TO_STRING(kDst)
    MAP_BLEND_MODE_TO_STRING(kSrcOver)
    MAP_BLEND_MODE_TO_STRING(kDstOver)
    MAP_BLEND_MODE_TO_STRING(kSrcIn)
    MAP_BLEND_MODE_TO_STRING(kDstIn)
    MAP_BLEND_MODE_TO_STRING(kSrcOut)
    MAP_BLEND_MODE_TO_STRING(kDstOut)
    MAP_BLEND_MODE_TO_STRING(kSrcATop)
    MAP_BLEND_MODE_TO_STRING(kDstATop)
    MAP_BLEND_MODE_TO_STRING(kXor)
    MAP_BLEND_MODE_TO_STRING(kPlus)
    MAP_BLEND_MODE_TO_STRING(kModulate)
    MAP_BLEND_MODE_TO_STRING(kScreen)
    MAP_BLEND_MODE_TO_STRING(kOverlay)
    MAP_BLEND_MODE_TO_STRING(kDarken)
    MAP_BLEND_MODE_TO_STRING(kLighten)
    MAP_BLEND_MODE_TO_STRING(kColorDodge)
    MAP_BLEND_MODE_TO_STRING(kColorBurn)
    MAP_BLEND_MODE_TO_STRING(kHardLight)
    MAP_BLEND_MODE_TO_STRING(kSoftLight)
    MAP_BLEND_MODE_TO_STRING(kDifference)
    MAP_BLEND_MODE_TO_STRING(kExclusion)
    MAP_BLEND_MODE_TO_STRING(kMultiply)
    MAP_BLEND_MODE_TO_STRING(kHue)
    MAP_BLEND_MODE_TO_STRING(kSaturation)
    MAP_BLEND_MODE_TO_STRING(kColor)
    MAP_BLEND_MODE_TO_STRING(kLuminosity)
    default:
      NOTREACHED();
  }
}
#undef MAP_BLEND_MODE_TO_STRING

#define MAP_MATERIAL_TO_STRING(NAME) \
  case DrawQuad::Material::NAME:     \
    return #NAME;
const char* DrawQuadMaterialToString(DrawQuad::Material material) {
  switch (material) {
    MAP_MATERIAL_TO_STRING(kInvalid)
    MAP_MATERIAL_TO_STRING(kDebugBorder)
    MAP_MATERIAL_TO_STRING(kPictureContent)
    MAP_MATERIAL_TO_STRING(kCompositorRenderPass)
    MAP_MATERIAL_TO_STRING(kAggregatedRenderPass)
    MAP_MATERIAL_TO_STRING(kSharedElement)
    MAP_MATERIAL_TO_STRING(kSolidColor)
    MAP_MATERIAL_TO_STRING(kSurfaceContent)
    MAP_MATERIAL_TO_STRING(kTextureContent)
    MAP_MATERIAL_TO_STRING(kTiledContent)
    MAP_MATERIAL_TO_STRING(kVideoHole)
    default:
      NOTREACHED();
  }
}
#undef MAP_MATERIAL_TO_STRING

base::DictValue CompositorRenderPassToDict(
    const CompositorRenderPass& render_pass) {
  base::DictValue dict;
  if (ProcessRenderPassField(kRenderPassID))
    dict.Set("id", base::NumberToString(static_cast<uint64_t>(render_pass.id)));
  if (ProcessRenderPassField(kRenderPassOutputRect))
    dict.Set("output_rect", RectToDict(render_pass.output_rect));
  if (ProcessRenderPassField(kRenderPassDamageRect))
    dict.Set("damage_rect", RectToDict(render_pass.damage_rect));
  if (ProcessRenderPassField(kRenderPassTransformToRootTarget)) {
    dict.Set("transform_to_root_target",
             TransformToList(render_pass.transform_to_root_target));
  }
  if (ProcessRenderPassField(kRenderPassFilters))
    dict.Set("filters", FilterOperationsToList(render_pass.filters));
  if (ProcessRenderPassField(kRenderPassBackdropFilters)) {
    dict.Set("backdrop_filters",
             FilterOperationsToList(render_pass.backdrop_filters));
  }
  if (ProcessRenderPassField(kRenderPassBackdropFilterBounds) &&
      render_pass.backdrop_filter_bounds) {
    dict.Set("backdrop_filter_bounds",
             SkPathToBlob(render_pass.backdrop_filter_bounds.value()));
  }
  if (ProcessRenderPassField(kRenderPassColorSpace)) {
    // CompositorRenderPasses used to have a color space field, but this was
    // removed in favor of color usage. https://crbug.com/1049334
    gfx::ColorSpace render_pass_color_space = gfx::ColorSpace::CreateSRGB();
    dict.Set("color_space", ColorSpaceToDict(render_pass_color_space));
  }
  if (ProcessRenderPassField(kRenderPassHasTransparentBackground)) {
    dict.Set("has_transparent_background",
             render_pass.has_transparent_background);
  }
  if (ProcessRenderPassField(kRenderPassCacheRenderPass))
    dict.Set("cache_render_pass", render_pass.cache_render_pass);
  if (ProcessRenderPassField(kRenderPassHasPreQuadDamage)) {
    // Set the dict value only if it is not the non default value.
    if (render_pass.has_per_quad_damage)
      dict.Set("has_per_quad_damage", render_pass.has_per_quad_damage);
  }
  if (ProcessRenderPassField(kRenderPassHasDamageFromContributingContent)) {
    dict.Set("has_damage_from_contributing_content",
             render_pass.has_damage_from_contributing_content);
  }
  if (ProcessRenderPassField(kRenderPassGenerateMipmap))
    dict.Set("generate_mipmap", render_pass.generate_mipmap);
  if (ProcessRenderPassField(kRenderPassCopyRequests)) {
    // TODO(zmo): Write copy_requests.
  }
  if (ProcessRenderPassField(kRenderPassQuadList)) {
    dict.Set("quad_list", QuadListToList(render_pass.quad_list,
                                         render_pass.shared_quad_state_list));
  }
  if (ProcessRenderPassField(kRenderPassSharedQuadStateList)) {
    dict.Set("shared_quad_state_list",
             SharedQuadStateListToList(render_pass.shared_quad_state_list));
  }
  return dict;
}

std::unique_ptr<CompositorRenderPass> CompositorRenderPassFromDict(
    const base::DictValue& dict) {
  auto pass = CompositorRenderPass::Create();

  if (ProcessRenderPassField(kRenderPassID)) {
    const std::string* id = dict.FindString("id");
    if (!id)
      return nullptr;
    uint64_t pass_id_as_int = 0;
    if (!base::StringToUint64(*id, &pass_id_as_int))
      return nullptr;
    pass->id = CompositorRenderPassId{pass_id_as_int};
  }

  if (ProcessRenderPassField(kRenderPassOutputRect)) {
    const base::DictValue* output_rect = dict.FindDict("output_rect");
    if (!output_rect)
      return nullptr;
    if (!RectFromDict(*output_rect, &(pass->output_rect)))
      return nullptr;
  }

  if (ProcessRenderPassField(kRenderPassDamageRect)) {
    const base::DictValue* damage_rect = dict.FindDict("damage_rect");
    if (!damage_rect)
      return nullptr;
    if (!RectFromDict(*damage_rect, &(pass->damage_rect)))
      return nullptr;
  }

  if (ProcessRenderPassField(kRenderPassTransformToRootTarget)) {
    const base::ListValue* transform_to_root_target =
        dict.FindList("transform_to_root_target");
    if (!transform_to_root_target)
      return nullptr;
    if (!TransformFromList(*transform_to_root_target,
                           &(pass->transform_to_root_target))) {
      return nullptr;
    }
  }

  if (ProcessRenderPassField(kRenderPassFilters)) {
    const base::ListValue* filters = dict.FindList("filters");
    if (!filters)
      return nullptr;
    if (!FilterOperationsFromList(*filters, &(pass->filters)))
      return nullptr;
  }

  if (ProcessRenderPassField(kRenderPassBackdropFilters)) {
    const base::ListValue* backdrop_filters = dict.FindList("backdrop_filters");
    if (!backdrop_filters)
      return nullptr;
    if (!FilterOperationsFromList(*backdrop_filters,
                                  &(pass->backdrop_filters))) {
      return nullptr;
    }
  }

  if (ProcessRenderPassField(kRenderPassBackdropFilterBounds)) {
    const base::Value::BlobStorage* backdrop_filter_bounds =
        dict.FindBlob("backdrop_filter_bounds");
    if (backdrop_filter_bounds) {
      SkPath bounds;
      if (!SkPathFromBlob(*backdrop_filter_bounds, &bounds)) {
        return nullptr;
      }
      pass->backdrop_filter_bounds = bounds;
    }
  }

  if (ProcessRenderPassField(kRenderPassColorSpace)) {
    const base::DictValue* color_space = dict.FindDict("color_space");
    if (!color_space)
      return nullptr;

    // CompositorRenderPasses used to have a color space field, but this was
    // removed in favor of color usage. https://crbug.com/1049334
    gfx::ColorSpace pass_color_space = gfx::ColorSpace::CreateSRGB();
    if (!ColorSpaceFromDict(*color_space, &pass_color_space))
      return nullptr;
  }

  if (ProcessRenderPassField(kRenderPassHasTransparentBackground)) {
    const std::optional<bool> has_transparent_background =
        dict.FindBool("has_transparent_background");
    if (!has_transparent_background)
      return nullptr;
    pass->has_transparent_background = has_transparent_background.value();
  }

  if (ProcessRenderPassField(kRenderPassCacheRenderPass)) {
    const std::optional<bool> cache_render_pass =
        dict.FindBool("cache_render_pass");
    if (!cache_render_pass)
      return nullptr;
    pass->cache_render_pass = cache_render_pass.value();
  }

  if (ProcessRenderPassField(kRenderPassHasPreQuadDamage)) {
    const std::optional<bool> has_per_quad_damage =
        dict.FindBool("has_per_quad_damage");
    if (has_per_quad_damage)
      pass->has_per_quad_damage = has_per_quad_damage.value();
  }

  if (ProcessRenderPassField(kRenderPassHasDamageFromContributingContent)) {
    const std::optional<bool> has_damage_from_contributing_content =
        dict.FindBool("has_damage_from_contributing_content");
    if (!has_damage_from_contributing_content)
      return nullptr;
    pass->has_damage_from_contributing_content =
        has_damage_from_contributing_content.value();
  }

  if (ProcessRenderPassField(kRenderPassGenerateMipmap)) {
    const std::optional<bool> generate_mipmap =
        dict.FindBool("generate_mipmap");
    if (!generate_mipmap)
      return nullptr;
    pass->generate_mipmap = generate_mipmap.value();
  }

  if (ProcessRenderPassField(kRenderPassCopyRequests)) {
    // TODO(zmo): Read copy_requests.
  }

  // shared_quad_state_list has to be processed before quad_list.
  if (ProcessRenderPassField(kRenderPassSharedQuadStateList)) {
    const base::ListValue* shared_quad_state_list =
        dict.FindList("shared_quad_state_list");
    if (!shared_quad_state_list)
      return nullptr;
    if (!SharedQuadStateListFromList(*shared_quad_state_list,
                                     &(pass->shared_quad_state_list))) {
      return nullptr;
    }
  }

  if (ProcessRenderPassField(kRenderPassQuadList)) {
    const base::ListValue* quad_list = dict.FindList("quad_list");
    if (!quad_list)
      return nullptr;
    if (!QuadListFromList(*quad_list, &(pass->quad_list),
                          pass->shared_quad_state_list)) {
      return nullptr;
    }
  }

  return pass;
}

base::DictValue CompositorRenderPassListToDict(
    const CompositorRenderPassList& render_pass_list) {
  base::ListValue list;
  for (const auto& pass : render_pass_list) {
    list.Append(CompositorRenderPassToDict(*pass));
  }

  return base::DictValue()
      .Set("render_pass_count", static_cast<int>(render_pass_list.size()))
      .Set("metadata", GetRenderPassListMetadata(render_pass_list))
      .Set("render_pass_list", std::move(list));
}

bool CompositorRenderPassListFromDict(
    const base::DictValue& dict,
    CompositorRenderPassList* render_pass_list) {
  DCHECK(render_pass_list);
  DCHECK(render_pass_list->empty());

  const base::ListValue* list = dict.FindList("render_pass_list");
  if (!list) {
    return false;
  }

  for (const auto& item : *list) {
    const base::DictValue* item_dict = item.GetIfDict();
    if (!item_dict) {
      render_pass_list->clear();
      return false;
    }
    std::unique_ptr<CompositorRenderPass> render_pass =
        CompositorRenderPassFromDict(*item_dict);
    if (!render_pass) {
      render_pass_list->clear();
      return false;
    }
    render_pass_list->push_back(std::move(render_pass));
  }

  return true;
}

base::DictValue CompositorFrameToDict(const CompositorFrame& compositor_frame) {
  base::ListValue referenced_surfaces;
  for (auto& surface_range : compositor_frame.metadata.referenced_surfaces) {
    referenced_surfaces.Append(SurfaceRangeToDict(surface_range));
  }

  return base::DictValue()
      .Set("render_pass_list",
           CompositorRenderPassListToDict(compositor_frame.render_pass_list))
      .Set("metadata", base::DictValue().Set("referenced_surfaces",
                                             std::move(referenced_surfaces)));
}

bool CompositorFrameFromDict(const base::DictValue& dict,
                             CompositorFrame* compositor_frame) {
  DCHECK(compositor_frame);

  const base::DictValue* render_pass_list = dict.FindDict("render_pass_list");
  if (!render_pass_list) {
    return false;
  }
  if (!CompositorRenderPassListFromDict(*render_pass_list,
                                        &compositor_frame->render_pass_list)) {
    return false;
  }

  const base::DictValue* metadata = dict.FindDict("metadata");
  if (!metadata) {
    return false;
  }
  const base::ListValue* referenced_surfaces =
      metadata->FindList("referenced_surfaces");
  if (!referenced_surfaces) {
    return false;
  }
  for (auto& referenced_surface_dict : *referenced_surfaces) {
    auto referenced_surface =
        SurfaceRangeFromDict(referenced_surface_dict.GetDict());
    if (!referenced_surface) {
      return false;
    }
    compositor_frame->metadata.referenced_surfaces.push_back(
        *referenced_surface);
  }

  return true;
}

base::ListValue FrameDataToList(const std::vector<FrameData>& frame_data_list) {
  base::ListValue list;

  for (auto& frame_data : frame_data_list) {
    list.Append(
        base::DictValue()
            .Set("surface_id", SurfaceIdToDict(frame_data.surface_id))
            // This cast will be safe because we should never have more than
            // |INT_MAX| frames in recorded data.
            .Set("frame_index", static_cast<int>(frame_data.frame_index))
            .Set("compositor_frame",
                 CompositorFrameToDict(frame_data.compositor_frame)));
  }
  return list;
}

bool FrameDataFromList(const base::ListValue& list,
                       std::vector<FrameData>* frame_data_list) {
  DCHECK(frame_data_list);
  DCHECK(frame_data_list->empty());

  for (const auto& frame_data_value : list) {
    const base::DictValue* frame_data_dict = frame_data_value.GetIfDict();
    if (!frame_data_dict) {
      return false;
    }

    FrameData frame_data;
    const base::DictValue* surface_id_dict =
        frame_data_dict->FindDict("surface_id");
    if (!surface_id_dict) {
      return false;
    }
    std::optional<SurfaceId> surface_id = SurfaceIdFromDict(*surface_id_dict);
    if (!surface_id) {
      return false;
    }
    frame_data.surface_id = *surface_id;

    std::optional<int> frame_index = frame_data_dict->FindInt("frame_index");
    if (!frame_index) {
      return false;
    }
    frame_data.frame_index = *frame_index;

    const base::DictValue* compositor_frame_dict =
        frame_data_dict->FindDict("compositor_frame");
    if (!compositor_frame_dict) {
      return false;
    }
    if (!CompositorFrameFromDict(*compositor_frame_dict,
                                 &frame_data.compositor_frame)) {
      return false;
    }

    frame_data_list->push_back(std::move(frame_data));
  }

  return true;
}

FrameData::FrameData() = default;
FrameData::FrameData(FrameData&& other) = default;
FrameData& FrameData::operator=(FrameData&& other) = default;

FrameData::FrameData(const SurfaceId& surface_id,
                     const uint64_t frame_index,
                     const CompositorFrame& compositor_frame)
    : surface_id(surface_id), frame_index(frame_index) {
  this->compositor_frame.metadata = compositor_frame.metadata.Clone();
  this->compositor_frame.resource_list = compositor_frame.resource_list;
  for (const auto& render_pass : compositor_frame.render_pass_list) {
    this->compositor_frame.render_pass_list.push_back(render_pass->DeepCopy());
  }
}

}  // namespace viz
