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

#include <vector>

#include "base/command_line.h"
#include "skia/ext/switches.h"
#include "third_party/fuzztest/src/fuzztest/fuzztest.h"
#include "third_party/skia/include/core/SkColor.h"
#include "ui/gfx/canvas.h"
#include "ui/gfx/geometry/size.h"
#include "ui/gfx/paint_vector_icon.h"
#include "ui/gfx/vector_icon_types.h"
#include "ui/gfx/vector_icon_utils.h"

namespace {

using fuzztest::Arbitrary;
using fuzztest::Domain;
using fuzztest::ElementOf;
using fuzztest::Finite;
using fuzztest::FlatMap;
using fuzztest::InRange;
using fuzztest::Just;
using fuzztest::Map;
using fuzztest::NonEmpty;
using fuzztest::StructOf;
using fuzztest::VariantOf;
using fuzztest::VectorOf;

// Fuzztest does not yet support enums out of the box, but thankfully the
// `gfx::CommandType` enum is defined through a pair of macros that work very
// well for us. `DECLARE_VECTOR_COMMAND(x)` is supposed to be overridden like
// this before `DECLARE_VECTOR_COMMANDS` can be used. Dependency injection!
#define DECLARE_VECTOR_COMMAND(x) gfx::CommandType::x,

// That allows us to define a domain that contains only valid vector commands.
auto AnyCommandType() {
  return ElementOf({DECLARE_VECTOR_COMMANDS});
}

// Each command type has a specific number of args it expects, otherwise the
// command validation code CHECK-fails. We thus make sure to generate only
// valid sequences of `gfx::PathElement`s by packaging commands with the right
// number of arguments.
struct Command {
  gfx::CommandType type;
  std::vector<SkScalar> args;
};

// Returns the domain of all possible arguments for the given command.
//
// We need this to account for the fact that not all arguments are valid for
// all commands, and passing invalid arguments can trigger shallow CHECK
// failures that prevent deeper fuzzing.
Domain<std::vector<SkScalar>> AnyArgsForCommandType(gfx::CommandType type) {
  int args_count = gfx::GetCommandArgumentCount(type);
  switch (type) {
    case gfx::PATH_COLOR_ARGB:
      return VectorOf(InRange(SkScalar(0.0), SkScalar(255.0)))
          .WithSize(args_count);
    case gfx::CANVAS_DIMENSIONS:
      return VectorOf(InRange(SkScalar(1.0), SkScalar(1024.0)))
          .WithSize(args_count);
    default:
      return VectorOf(Finite<SkScalar>()).WithSize(args_count);
  }
}

Domain<Command> AnyCommandWithType(gfx::CommandType type) {
  return StructOf<Command>(Just(type), AnyArgsForCommandType(type));
}

// Returns the domain of all possible commands.
auto AnyCommand() {
  return FlatMap(AnyCommandWithType, AnyCommandType());
}

// Flattens the given `commands` into a sequence of path elements that can be
// passed to `PaintVectorIcon()`.
std::vector<gfx::PathElement> ConvertCommands(
    const std::vector<Command>& commands) {
  std::vector<gfx::PathElement> path;
  for (const auto& command : commands) {
    path.emplace_back(command.type);
    for (SkScalar arg : command.args) {
      path.emplace_back(arg);
    }
  }
  return path;
}

class PaintVectorIconFuzzTest {
 public:
  PaintVectorIconFuzzTest() {
    // `Init()` ignores its arguments on Windows, so init with nothing and add
    // switches later.
    CHECK(base::CommandLine::Init(0, nullptr));

    // Set command-line arguments correctly to avoid check failures down the
    // line.
    base::CommandLine& command_line = *base::CommandLine::ForCurrentProcess();
    command_line.AppendSwitchASCII(switches::kTextContrast, "1.0");
    command_line.AppendSwitchASCII(switches::kTextGamma, "1.0");
  }

  void PaintVectorIcon(std::vector<Command> commands) {
    std::vector<gfx::PathElement> path = ConvertCommands(commands);

    // An icon can contain multiple representations. We do not fuzz the code
    // that chooses which representation to draw based on canvas size and scale,
    // and instead use a single representation.
    gfx::VectorIconRep rep{path};
    gfx::VectorIcon icon(&rep, /*reps_size=*/1u, "icon");

    constexpr float kImageScale = 1.f;
    constexpr bool kIsOpaque = true;
    gfx::Canvas canvas(gfx::Size(1024, 1024), kImageScale, kIsOpaque);

    // The length of a single edge of the square icon, in device-independent
    // pixels.
    constexpr int kDipSize = 1024;
    constexpr SkColor kBlack = SkColorSetARGB(255, 0, 0, 0);
    gfx::PaintVectorIcon(&canvas, icon, kDipSize, kBlack);
  }
};

FUZZ_TEST_F(PaintVectorIconFuzzTest, PaintVectorIcon)
    .WithDomains(NonEmpty(VectorOf(AnyCommand())));

}  // namespace
