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

#include "flutter/testing/testing.h"
#include "impeller/aiks/canvas.h"
#include "impeller/geometry/path_builder.h"

// TODO(zanderso): https://github.com/flutter/flutter/issues/127701
// NOLINTBEGIN(bugprone-unchecked-optional-access)

namespace impeller {
namespace testing {

using AiksCanvasTest = ::testing::Test;

TEST(AiksCanvasTest, EmptyCullRect) {
  Canvas canvas;

  ASSERT_FALSE(canvas.GetCurrentLocalCullingBounds().has_value());
}

TEST(AiksCanvasTest, InitialCullRect) {
  Rect initial_cull(0, 0, 10, 10);

  Canvas canvas(initial_cull);

  ASSERT_TRUE(canvas.GetCurrentLocalCullingBounds().has_value());
  ASSERT_EQ(canvas.GetCurrentLocalCullingBounds().value(), initial_cull);
}

TEST(AiksCanvasTest, TranslatedCullRect) {
  Rect initial_cull(5, 5, 10, 10);
  Rect translated_cull(0, 0, 10, 10);

  Canvas canvas(initial_cull);
  canvas.Translate(Vector3(5, 5, 0));

  ASSERT_TRUE(canvas.GetCurrentLocalCullingBounds().has_value());
  ASSERT_EQ(canvas.GetCurrentLocalCullingBounds().value(), translated_cull);
}

TEST(AiksCanvasTest, ScaledCullRect) {
  Rect initial_cull(5, 5, 10, 10);
  Rect scaled_cull(10, 10, 20, 20);

  Canvas canvas(initial_cull);
  canvas.Scale(Vector2(0.5, 0.5));

  ASSERT_TRUE(canvas.GetCurrentLocalCullingBounds().has_value());
  ASSERT_EQ(canvas.GetCurrentLocalCullingBounds().value(), scaled_cull);
}

TEST(AiksCanvasTest, RectClipIntersectAgainstEmptyCullRect) {
  Rect rect_clip(5, 5, 10, 10);

  Canvas canvas;
  canvas.ClipRect(rect_clip, Entity::ClipOperation::kIntersect);

  ASSERT_TRUE(canvas.GetCurrentLocalCullingBounds().has_value());
  ASSERT_EQ(canvas.GetCurrentLocalCullingBounds().value(), rect_clip);
}

TEST(AiksCanvasTest, RectClipDiffAgainstEmptyCullRect) {
  Rect rect_clip(5, 5, 10, 10);

  Canvas canvas;
  canvas.ClipRect(rect_clip, Entity::ClipOperation::kDifference);

  ASSERT_FALSE(canvas.GetCurrentLocalCullingBounds().has_value());
}

TEST(AiksCanvasTest, RectClipIntersectAgainstCullRect) {
  Rect initial_cull(0, 0, 10, 10);
  Rect rect_clip(5, 5, 10, 10);
  Rect result_cull(5, 5, 5, 5);

  Canvas canvas(initial_cull);
  canvas.ClipRect(rect_clip, Entity::ClipOperation::kIntersect);

  ASSERT_TRUE(canvas.GetCurrentLocalCullingBounds().has_value());
  ASSERT_EQ(canvas.GetCurrentLocalCullingBounds().value(), result_cull);
}

TEST(AiksCanvasTest, RectClipDiffAgainstNonCoveredCullRect) {
  Rect initial_cull(0, 0, 10, 10);
  Rect rect_clip(5, 5, 10, 10);
  Rect result_cull(0, 0, 10, 10);

  Canvas canvas(initial_cull);
  canvas.ClipRect(rect_clip, Entity::ClipOperation::kDifference);

  ASSERT_TRUE(canvas.GetCurrentLocalCullingBounds().has_value());
  ASSERT_EQ(canvas.GetCurrentLocalCullingBounds().value(), result_cull);
}

TEST(AiksCanvasTest, RectClipDiffAboveCullRect) {
  Rect initial_cull(5, 5, 10, 10);
  Rect rect_clip(0, 0, 20, 4);
  Rect result_cull(5, 5, 10, 10);

  Canvas canvas(initial_cull);
  canvas.ClipRect(rect_clip, Entity::ClipOperation::kDifference);

  ASSERT_TRUE(canvas.GetCurrentLocalCullingBounds().has_value());
  ASSERT_EQ(canvas.GetCurrentLocalCullingBounds().value(), result_cull);
}

TEST(AiksCanvasTest, RectClipDiffBelowCullRect) {
  Rect initial_cull(5, 5, 10, 10);
  Rect rect_clip(0, 16, 20, 4);
  Rect result_cull(5, 5, 10, 10);

  Canvas canvas(initial_cull);
  canvas.ClipRect(rect_clip, Entity::ClipOperation::kDifference);

  ASSERT_TRUE(canvas.GetCurrentLocalCullingBounds().has_value());
  ASSERT_EQ(canvas.GetCurrentLocalCullingBounds().value(), result_cull);
}

TEST(AiksCanvasTest, RectClipDiffLeftOfCullRect) {
  Rect initial_cull(5, 5, 10, 10);
  Rect rect_clip(0, 0, 4, 20);
  Rect result_cull(5, 5, 10, 10);

  Canvas canvas(initial_cull);
  canvas.ClipRect(rect_clip, Entity::ClipOperation::kDifference);

  ASSERT_TRUE(canvas.GetCurrentLocalCullingBounds().has_value());
  ASSERT_EQ(canvas.GetCurrentLocalCullingBounds().value(), result_cull);
}

TEST(AiksCanvasTest, RectClipDiffRightOfCullRect) {
  Rect initial_cull(5, 5, 10, 10);
  Rect rect_clip(16, 0, 4, 20);
  Rect result_cull(5, 5, 10, 10);

  Canvas canvas(initial_cull);
  canvas.ClipRect(rect_clip, Entity::ClipOperation::kDifference);

  ASSERT_TRUE(canvas.GetCurrentLocalCullingBounds().has_value());
  ASSERT_EQ(canvas.GetCurrentLocalCullingBounds().value(), result_cull);
}

TEST(AiksCanvasTest, RectClipDiffAgainstVCoveredCullRect) {
  Rect initial_cull(0, 0, 10, 10);
  Rect rect_clip(5, 0, 10, 10);
  Rect result_cull(0, 0, 5, 10);

  Canvas canvas(initial_cull);
  canvas.ClipRect(rect_clip, Entity::ClipOperation::kDifference);

  ASSERT_TRUE(canvas.GetCurrentLocalCullingBounds().has_value());
  ASSERT_EQ(canvas.GetCurrentLocalCullingBounds().value(), result_cull);
}

TEST(AiksCanvasTest, RectClipDiffAgainstHCoveredCullRect) {
  Rect initial_cull(0, 0, 10, 10);
  Rect rect_clip(0, 5, 10, 10);
  Rect result_cull(0, 0, 10, 5);

  Canvas canvas(initial_cull);
  canvas.ClipRect(rect_clip, Entity::ClipOperation::kDifference);

  ASSERT_TRUE(canvas.GetCurrentLocalCullingBounds().has_value());
  ASSERT_EQ(canvas.GetCurrentLocalCullingBounds().value(), result_cull);
}

TEST(AiksCanvasTest, RRectClipIntersectAgainstEmptyCullRect) {
  Rect rect_clip(5, 5, 10, 10);

  Canvas canvas;
  canvas.ClipRRect(rect_clip, 1, Entity::ClipOperation::kIntersect);

  ASSERT_TRUE(canvas.GetCurrentLocalCullingBounds().has_value());
  ASSERT_EQ(canvas.GetCurrentLocalCullingBounds().value(), rect_clip);
}

TEST(AiksCanvasTest, RRectClipDiffAgainstEmptyCullRect) {
  Rect rect_clip(5, 5, 10, 10);

  Canvas canvas;
  canvas.ClipRRect(rect_clip, 1, Entity::ClipOperation::kDifference);

  ASSERT_FALSE(canvas.GetCurrentLocalCullingBounds().has_value());
}

TEST(AiksCanvasTest, RRectClipIntersectAgainstCullRect) {
  Rect initial_cull(0, 0, 10, 10);
  Rect rect_clip(5, 5, 10, 10);
  Rect result_cull(5, 5, 5, 5);

  Canvas canvas(initial_cull);
  canvas.ClipRRect(rect_clip, 1, Entity::ClipOperation::kIntersect);

  ASSERT_TRUE(canvas.GetCurrentLocalCullingBounds().has_value());
  ASSERT_EQ(canvas.GetCurrentLocalCullingBounds().value(), result_cull);
}

TEST(AiksCanvasTest, RRectClipDiffAgainstNonCoveredCullRect) {
  Rect initial_cull(0, 0, 10, 10);
  Rect rect_clip(5, 5, 10, 10);
  Rect result_cull(0, 0, 10, 10);

  Canvas canvas(initial_cull);
  canvas.ClipRRect(rect_clip, 1, Entity::ClipOperation::kDifference);

  ASSERT_TRUE(canvas.GetCurrentLocalCullingBounds().has_value());
  ASSERT_EQ(canvas.GetCurrentLocalCullingBounds().value(), result_cull);
}

TEST(AiksCanvasTest, RRectClipDiffAgainstVPartiallyCoveredCullRect) {
  Rect initial_cull(0, 0, 10, 10);
  Rect rect_clip(5, 0, 10, 10);
  Rect result_cull(0, 0, 6, 10);

  Canvas canvas(initial_cull);
  canvas.ClipRRect(rect_clip, 1, Entity::ClipOperation::kDifference);

  ASSERT_TRUE(canvas.GetCurrentLocalCullingBounds().has_value());
  ASSERT_EQ(canvas.GetCurrentLocalCullingBounds().value(), result_cull);
}

TEST(AiksCanvasTest, RRectClipDiffAgainstVFullyCoveredCullRect) {
  Rect initial_cull(0, 0, 10, 10);
  Rect rect_clip(5, -2, 10, 14);
  Rect result_cull(0, 0, 5, 10);

  Canvas canvas(initial_cull);
  canvas.ClipRRect(rect_clip, 1, Entity::ClipOperation::kDifference);

  ASSERT_TRUE(canvas.GetCurrentLocalCullingBounds().has_value());
  ASSERT_EQ(canvas.GetCurrentLocalCullingBounds().value(), result_cull);
}

TEST(AiksCanvasTest, RRectClipDiffAgainstHPartiallyCoveredCullRect) {
  Rect initial_cull(0, 0, 10, 10);
  Rect rect_clip(0, 5, 10, 10);
  Rect result_cull(0, 0, 10, 6);

  Canvas canvas(initial_cull);
  canvas.ClipRRect(rect_clip, 1, Entity::ClipOperation::kDifference);

  ASSERT_TRUE(canvas.GetCurrentLocalCullingBounds().has_value());
  ASSERT_EQ(canvas.GetCurrentLocalCullingBounds().value(), result_cull);
}

TEST(AiksCanvasTest, RRectClipDiffAgainstHFullyCoveredCullRect) {
  Rect initial_cull(0, 0, 10, 10);
  Rect rect_clip(-2, 5, 14, 10);
  Rect result_cull(0, 0, 10, 5);

  Canvas canvas(initial_cull);
  canvas.ClipRRect(rect_clip, 1, Entity::ClipOperation::kDifference);

  ASSERT_TRUE(canvas.GetCurrentLocalCullingBounds().has_value());
  ASSERT_EQ(canvas.GetCurrentLocalCullingBounds().value(), result_cull);
}

TEST(AiksCanvasTest, PathClipIntersectAgainstEmptyCullRect) {
  PathBuilder builder;
  builder.AddRect({5, 5, 1, 1});
  builder.AddRect({5, 14, 1, 1});
  builder.AddRect({14, 5, 1, 1});
  builder.AddRect({14, 14, 1, 1});
  Path path = builder.TakePath();
  Rect rect_clip(5, 5, 10, 10);

  Canvas canvas;
  canvas.ClipPath(path, Entity::ClipOperation::kIntersect);

  ASSERT_TRUE(canvas.GetCurrentLocalCullingBounds().has_value());
  ASSERT_EQ(canvas.GetCurrentLocalCullingBounds().value(), rect_clip);
}

TEST(AiksCanvasTest, PathClipDiffAgainstEmptyCullRect) {
  PathBuilder builder;
  builder.AddRect({5, 5, 1, 1});
  builder.AddRect({5, 14, 1, 1});
  builder.AddRect({14, 5, 1, 1});
  builder.AddRect({14, 14, 1, 1});
  Path path = builder.TakePath();

  Canvas canvas;
  canvas.ClipPath(path, Entity::ClipOperation::kDifference);

  ASSERT_FALSE(canvas.GetCurrentLocalCullingBounds().has_value());
}

TEST(AiksCanvasTest, PathClipIntersectAgainstCullRect) {
  Rect initial_cull(0, 0, 10, 10);
  PathBuilder builder;
  builder.AddRect({5, 5, 1, 1});
  builder.AddRect({5, 14, 1, 1});
  builder.AddRect({14, 5, 1, 1});
  builder.AddRect({14, 14, 1, 1});
  Path path = builder.TakePath();
  Rect result_cull(5, 5, 5, 5);

  Canvas canvas(initial_cull);
  canvas.ClipPath(path, Entity::ClipOperation::kIntersect);

  ASSERT_TRUE(canvas.GetCurrentLocalCullingBounds().has_value());
  ASSERT_EQ(canvas.GetCurrentLocalCullingBounds().value(), result_cull);
}

TEST(AiksCanvasTest, PathClipDiffAgainstNonCoveredCullRect) {
  Rect initial_cull(0, 0, 10, 10);
  PathBuilder builder;
  builder.AddRect({5, 5, 1, 1});
  builder.AddRect({5, 14, 1, 1});
  builder.AddRect({14, 5, 1, 1});
  builder.AddRect({14, 14, 1, 1});
  Path path = builder.TakePath();
  Rect result_cull(0, 0, 10, 10);

  Canvas canvas(initial_cull);
  canvas.ClipPath(path, Entity::ClipOperation::kDifference);

  ASSERT_TRUE(canvas.GetCurrentLocalCullingBounds().has_value());
  ASSERT_EQ(canvas.GetCurrentLocalCullingBounds().value(), result_cull);
}

TEST(AiksCanvasTest, PathClipDiffAgainstFullyCoveredCullRect) {
  Rect initial_cull(5, 5, 10, 10);
  PathBuilder builder;
  builder.AddRect({0, 0, 100, 100});
  Path path = builder.TakePath();
  // Diff clip of Paths is ignored due to complexity
  Rect result_cull(5, 5, 10, 10);

  Canvas canvas(initial_cull);
  canvas.ClipPath(path, Entity::ClipOperation::kDifference);

  ASSERT_TRUE(canvas.GetCurrentLocalCullingBounds().has_value());
  ASSERT_EQ(canvas.GetCurrentLocalCullingBounds().value(), result_cull);
}

}  // namespace testing
}  // namespace impeller

// NOLINTEND(bugprone-unchecked-optional-access)
