// 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 "skia/ext/geometry.h"

#include <array>

#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/skia/include/core/SkBitmap.h"

namespace skia {

namespace {

TEST(Geometry, ScaleRectProportional) {
  SkRect output_rect;

  output_rect = ScaleSkRectProportional(SkRect::MakeXYWH(0, 0, 20, 20),
                                        SkRect::MakeXYWH(0, 0, 40, 40),
                                        SkRect::MakeXYWH(10, 10, 20, 20));
  EXPECT_EQ(output_rect, SkRect::MakeXYWH(5, 5, 10, 10));

  output_rect = ScaleSkRectProportional(SkRect::MakeXYWH(0, 10, 20, 80),
                                        SkRect::MakeXYWH(10, 20, 40, 80),
                                        SkRect::MakeXYWH(0, 30, 60, 20));
  EXPECT_EQ(output_rect, SkRect::MakeXYWH(-5, 20, 30, 20));
}

TEST(Geometry, TileSimple) {
  SkBitmap bm0;
  SkBitmap bm1;

  // Simple tiling that divides the region into two tiles.
  bm0.allocPixels(SkImageInfo::MakeN32Premul(20, 40));
  bm1.allocPixels(SkImageInfo::MakeN32Premul(10, 20));
  SkRect dest_rect = SkRect::MakeXYWH(10, 10, 20, 40);
  std::vector<SkRect> source_rects = {
      SkRect::MakeXYWH(0, 0, 20, 40),
      SkRect::MakeXYWH(0, 0, 10, 20),
  };
  std::vector<sk_sp<SkImage>> source_images = {
      SkImages::RasterFromBitmap(bm0),
      SkImages::RasterFromBitmap(bm1),
  };
  int32_t source_max_size = 22;

  Tiling t(dest_rect, source_rects, source_images, source_max_size);
  EXPECT_EQ(1, t.GetTileCountX());
  EXPECT_EQ(2, t.GetTileCountY());

  SkRect tile_dest_rect;
  std::vector<SkRect> tile_source_rects;
  std::vector<std::optional<SkIRect>> tile_subset_rects;

  t.GetTileRect(0, 0, tile_dest_rect, tile_source_rects, tile_subset_rects);
  EXPECT_EQ(tile_dest_rect, SkRect::MakeXYWH(10, 10, 20, 20));
  EXPECT_EQ(tile_source_rects[0], SkRect::MakeXYWH(0, 0, 20, 20));
  EXPECT_EQ(tile_source_rects[1], SkRect::MakeXYWH(0, 0, 10, 10));
  EXPECT_EQ(tile_subset_rects[0].value(), SkIRect::MakeXYWH(0, 0, 20, 20));
  EXPECT_EQ(tile_subset_rects[1].value(), SkIRect::MakeXYWH(0, 0, 10, 10));

  t.GetTileRect(0, 1, tile_dest_rect, tile_source_rects, tile_subset_rects);
  EXPECT_EQ(tile_dest_rect, SkRect::MakeXYWH(10, 30, 20, 20));
  EXPECT_EQ(tile_source_rects[0], SkRect::MakeXYWH(0, 0, 20, 20));
  EXPECT_EQ(tile_source_rects[1], SkRect::MakeXYWH(0, 0, 10, 10));
  EXPECT_EQ(tile_subset_rects[0].value(), SkIRect::MakeXYWH(0, 20, 20, 20));
  EXPECT_EQ(tile_subset_rects[1].value(), SkIRect::MakeXYWH(0, 10, 10, 10));
}

TEST(Geometry, TileSmallX) {
  SkBitmap bm0;

  // A tiling that has very different width and height.
  bm0.allocPixels(SkImageInfo::MakeN32Premul(5, 50));
  SkRect dest_rect = SkRect::MakeXYWH(0, 0, 5, 50);
  std::vector<SkRect> source_rects = {
      SkRect::MakeXYWH(0, 0, 5, 50),
  };
  std::vector<sk_sp<SkImage>> source_images = {
      SkImages::RasterFromBitmap(bm0),
  };
  int32_t source_max_size = 21;

  Tiling t(dest_rect, source_rects, source_images, source_max_size);
  EXPECT_EQ(1, t.GetTileCountX());
  EXPECT_EQ(3, t.GetTileCountY());

  SkRect tile_dest_rect;
  std::vector<SkRect> tile_source_rects;
  std::vector<std::optional<SkIRect>> tile_subset_rects;

  t.GetTileRect(0, 0, tile_dest_rect, tile_source_rects, tile_subset_rects);
  EXPECT_EQ(tile_dest_rect, SkRect::MakeXYWH(0, 0, 5, 20));
  EXPECT_EQ(tile_source_rects[0], SkRect::MakeXYWH(0, 0, 5, 20));
  EXPECT_EQ(tile_subset_rects[0].value(), SkIRect::MakeXYWH(0, 0, 5, 20));

  t.GetTileRect(0, 1, tile_dest_rect, tile_source_rects, tile_subset_rects);
  EXPECT_EQ(tile_dest_rect, SkRect::MakeXYWH(0, 20, 5, 20));
  EXPECT_EQ(tile_source_rects[0], SkRect::MakeXYWH(0, 0, 5, 20));
  EXPECT_EQ(tile_subset_rects[0].value(), SkIRect::MakeXYWH(0, 20, 5, 20));

  t.GetTileRect(0, 2, tile_dest_rect, tile_source_rects, tile_subset_rects);
  EXPECT_EQ(tile_dest_rect, SkRect::MakeXYWH(0, 40, 5, 10));
  EXPECT_EQ(tile_source_rects[0], SkRect::MakeXYWH(0, 0, 5, 10));
  EXPECT_EQ(tile_subset_rects[0].value(), SkIRect::MakeXYWH(0, 40, 5, 10));
}

TEST(Geometry, TileSmallY) {
  SkBitmap bm0;

  // A tiling that has very different width and height.
  bm0.allocPixels(SkImageInfo::MakeN32Premul(50, 5));
  SkRect dest_rect = SkRect::MakeXYWH(0, 0, 50, 5);
  std::vector<SkRect> source_rects = {
      SkRect::MakeXYWH(0, 0, 50, 5),
  };
  std::vector<sk_sp<SkImage>> source_images = {
      SkImages::RasterFromBitmap(bm0),
  };
  int32_t source_max_size = 21;

  Tiling t(dest_rect, source_rects, source_images, source_max_size);
  EXPECT_EQ(3, t.GetTileCountX());
  EXPECT_EQ(1, t.GetTileCountY());

  SkRect tile_dest_rect;
  std::vector<SkRect> tile_source_rects;
  std::vector<std::optional<SkIRect>> tile_subset_rects;

  t.GetTileRect(0, 0, tile_dest_rect, tile_source_rects, tile_subset_rects);
  EXPECT_EQ(tile_dest_rect, SkRect::MakeXYWH(0, 0, 20, 5));
  EXPECT_EQ(tile_source_rects[0], SkRect::MakeXYWH(0, 0, 20, 5));
  EXPECT_EQ(tile_subset_rects[0].value(), SkIRect::MakeXYWH(0, 0, 20, 5));

  t.GetTileRect(1, 0, tile_dest_rect, tile_source_rects, tile_subset_rects);
  EXPECT_EQ(tile_dest_rect, SkRect::MakeXYWH(20, 0, 20, 5));
  EXPECT_EQ(tile_source_rects[0], SkRect::MakeXYWH(0, 0, 20, 5));
  EXPECT_EQ(tile_subset_rects[0].value(), SkIRect::MakeXYWH(20, 0, 20, 5));

  t.GetTileRect(2, 0, tile_dest_rect, tile_source_rects, tile_subset_rects);
  EXPECT_EQ(tile_dest_rect, SkRect::MakeXYWH(40, 0, 10, 5));
  EXPECT_EQ(tile_source_rects[0], SkRect::MakeXYWH(0, 0, 10, 5));
  EXPECT_EQ(tile_subset_rects[0].value(), SkIRect::MakeXYWH(40, 0, 10, 5));
}

TEST(Geometry, Overflow) {
  SkBitmap bm0;
  SkBitmap bm1;

  // The tiling will overflow the bounds of bm1. Ensure that its subsets
  // are set to empty instead of overflowing values.
  bm0.allocPixels(SkImageInfo::MakeN32Premul(40, 80));
  bm1.allocPixels(SkImageInfo::MakeN32Premul(10, 20));
  SkRect dest_rect = SkRect::MakeXYWH(10, 10, 20, 40);
  std::vector<SkRect> source_rects = {
      SkRect::MakeXYWH(10, 10, 20, 40),
      SkRect::MakeXYWH(5, 5, 10, 20),
  };
  std::vector<sk_sp<SkImage>> source_images = {
      SkImages::RasterFromBitmap(bm0),
      SkImages::RasterFromBitmap(bm1),
  };
  int32_t source_max_size = 15;

  Tiling t(dest_rect, source_rects, source_images, source_max_size);

  constexpr int kTilesX = 2;
  constexpr int kTilesY = 3;
  EXPECT_EQ(kTilesX, t.GetTileCountX());
  EXPECT_EQ(kTilesY, t.GetTileCountY());

  // Save the output from all tiles for comparison.
  std::array<std::array<SkRect, kTilesY>, kTilesX> t_dest;
  std::array<std::array<SkRect, kTilesY>, kTilesX> t_base;
  std::array<std::array<SkRect, kTilesY>, kTilesX> t_gain;
  std::array<std::array<SkIRect, kTilesY>, kTilesX> s_base;
  std::array<std::array<SkIRect, kTilesY>, kTilesX> s_gain;

  for (int x = 0; x < kTilesX; ++x) {
    for (int y = 0; y < kTilesY; ++y) {
      SkRect tile_dest_rect;
      std::vector<SkRect> tile_source_rects;
      std::vector<std::optional<SkIRect>> tile_subset_rects;
      t.GetTileRect(x, y, tile_dest_rect, tile_source_rects, tile_subset_rects);
      t_dest[x][y] = tile_dest_rect;
      t_base[x][y] = tile_source_rects[0];
      t_gain[x][y] = tile_source_rects[1];
      s_base[x][y] = tile_subset_rects[0].value();
      s_gain[x][y] = tile_subset_rects[1].value();
    }
  }

  EXPECT_EQ(t_dest[0][0], SkRect::MakeLTRB(10, 10, 23, 24));
  EXPECT_EQ(t_dest[0][1], SkRect::MakeLTRB(10, 24, 23, 38));
  EXPECT_EQ(t_dest[0][2], SkRect::MakeLTRB(10, 38, 23, 50));
  EXPECT_EQ(t_dest[1][0], SkRect::MakeLTRB(23, 10, 30, 24));
  EXPECT_EQ(t_dest[1][1], SkRect::MakeLTRB(23, 24, 30, 38));
  EXPECT_EQ(t_dest[1][2], SkRect::MakeLTRB(23, 38, 30, 50));

  EXPECT_EQ(t_base[0][0], SkRect::MakeLTRB(0, 0, 13, 14));
  EXPECT_EQ(t_base[0][1], SkRect::MakeLTRB(0, 0, 13, 14));
  EXPECT_EQ(t_base[0][2], SkRect::MakeLTRB(0, 0, 13, 12));
  EXPECT_EQ(t_base[1][0], SkRect::MakeLTRB(0, 0, 7, 14));
  EXPECT_EQ(t_base[1][1], SkRect::MakeLTRB(0, 0, 7, 14));
  EXPECT_EQ(t_base[1][2], SkRect::MakeLTRB(0, 0, 7, 12));

  EXPECT_EQ(t_gain[0][0], SkRect::MakeLTRB(0, 0, 6.5, 7));
  EXPECT_EQ(t_gain[0][1], SkRect::MakeLTRB(0, 0, 6.5, 7));
  EXPECT_EQ(t_gain[0][2], SkRect::MakeLTRB(0, 0, 6.5, 6));
  EXPECT_EQ(t_gain[1][0], SkRect::MakeEmpty());
  EXPECT_EQ(t_gain[1][1], SkRect::MakeEmpty());
  EXPECT_EQ(t_gain[1][2], SkRect::MakeEmpty());

  EXPECT_EQ(s_base[0][0], SkIRect::MakeLTRB(10, 10, 23, 24));
  EXPECT_EQ(s_base[0][1], SkIRect::MakeLTRB(10, 24, 23, 38));
  EXPECT_EQ(s_base[0][2], SkIRect::MakeLTRB(10, 38, 23, 50));
  EXPECT_EQ(s_base[1][0], SkIRect::MakeLTRB(23, 10, 30, 24));
  EXPECT_EQ(s_base[1][1], SkIRect::MakeLTRB(23, 24, 30, 38));
  EXPECT_EQ(s_base[1][2], SkIRect::MakeLTRB(23, 38, 30, 50));

  EXPECT_EQ(s_gain[0][0], SkIRect::MakeLTRB(5, 5, 10, 12));
  EXPECT_EQ(s_gain[0][1], SkIRect::MakeLTRB(5, 12, 10, 19));
  EXPECT_EQ(s_gain[0][2], SkIRect::MakeLTRB(5, 19, 10, 20));
  EXPECT_EQ(s_gain[1][0], SkIRect::MakeEmpty());
  EXPECT_EQ(s_gain[1][1], SkIRect::MakeEmpty());
  EXPECT_EQ(s_gain[1][2], SkIRect::MakeEmpty());
}

TEST(Geometry, Fractional) {
  SkBitmap bm0;
  SkBitmap bm1;

  // Test where the source rects are not pixel-aligned.
  bm0.allocPixels(SkImageInfo::MakeN32Premul(30, 50));
  SkRect dest_rect = SkRect::MakeXYWH(0, 0, 20, 40);
  std::vector<SkRect> source_rects = {
      SkRect::MakeXYWH(5.5, 5.5, 20, 40),
  };
  std::vector<sk_sp<SkImage>> source_images = {
      SkImages::RasterFromBitmap(bm0),
  };
  int32_t source_max_size = 20;

  Tiling t(dest_rect, source_rects, source_images, source_max_size);

  constexpr int kTilesX = 2;
  constexpr int kTilesY = 3;
  EXPECT_EQ(kTilesX, t.GetTileCountX());
  EXPECT_EQ(kTilesY, t.GetTileCountY());

  // Save the output from all tiles for comparison.
  std::array<std::array<SkRect, kTilesY>, kTilesX> out_dest;
  std::array<std::array<SkRect, kTilesY>, kTilesX> out_source;
  std::array<std::array<SkIRect, kTilesY>, kTilesX> out_subset;

  for (int x = 0; x < kTilesX; ++x) {
    for (int y = 0; y < kTilesY; ++y) {
      SkRect tile_dest_rect;
      std::vector<SkRect> tile_source_rects;
      std::vector<std::optional<SkIRect>> tile_subset_rects;
      t.GetTileRect(x, y, tile_dest_rect, tile_source_rects, tile_subset_rects);
      out_dest[x][y] = tile_dest_rect;
      out_source[x][y] = tile_source_rects[0];
      out_subset[x][y] = tile_subset_rects[0].value();
    }
  }

  // The subsets for the image will be overlapping (e.g, the
  // first entry ends at Y=25, then the next one starts at
  // Y=24).
  EXPECT_EQ(out_subset[0][0], SkIRect::MakeLTRB(5, 5, 24, 25));
  EXPECT_EQ(out_subset[0][1], SkIRect::MakeLTRB(5, 24, 24, 44));
  EXPECT_EQ(out_subset[0][2], SkIRect::MakeLTRB(5, 43, 24, 46));
  EXPECT_EQ(out_subset[1][0], SkIRect::MakeLTRB(23, 5, 26, 25));
  EXPECT_EQ(out_subset[1][1], SkIRect::MakeLTRB(23, 24, 26, 44));
  EXPECT_EQ(out_subset[1][2], SkIRect::MakeLTRB(23, 43, 26, 46));

  // All of the sources are aligned to a half-pixel.
  EXPECT_EQ(out_source[0][0], SkRect::MakeLTRB(0.5, 0.5, 18.5, 19.5));
  EXPECT_EQ(out_source[0][1], SkRect::MakeLTRB(0.5, 0.5, 18.5, 19.5));
  EXPECT_EQ(out_source[0][2], SkRect::MakeLTRB(0.5, 0.5, 18.5, 2.5));
  EXPECT_EQ(out_source[1][0], SkRect::MakeLTRB(0.5, 0.5, 2.5, 19.5));
  EXPECT_EQ(out_source[1][1], SkRect::MakeLTRB(0.5, 0.5, 2.5, 19.5));
  EXPECT_EQ(out_source[1][2], SkRect::MakeLTRB(0.5, 0.5, 2.5, 2.5));

  // The destination tiles cover `dest_rect`.
  EXPECT_EQ(out_dest[0][0], SkRect::MakeLTRB(0, 0, 18, 19));
  EXPECT_EQ(out_dest[0][1], SkRect::MakeLTRB(0, 19, 18, 38));
  EXPECT_EQ(out_dest[0][2], SkRect::MakeLTRB(0, 38, 18, 40));
  EXPECT_EQ(out_dest[1][0], SkRect::MakeLTRB(18, 0, 20, 19));
  EXPECT_EQ(out_dest[1][1], SkRect::MakeLTRB(18, 19, 20, 38));
  EXPECT_EQ(out_dest[1][2], SkRect::MakeLTRB(18, 38, 20, 40));
}

TEST(Geometry, FractionTriangleCovered) {
  constexpr float kEpsilon = 0.0001f;

  std::array<SkPoint, 3> a = {SkPoint::Make(0.0f, 0.0f),
                              SkPoint::Make(1.0f, 0.0f),
                              SkPoint::Make(0.0f, 1.0f)};
  std::array<SkPoint, 3> b = {SkPoint::Make(0.0f, 0.0f),
                              SkPoint::Make(0.5f, 0.0f),
                              SkPoint::Make(0.0f, 0.5f)};

  // Self coverage
  EXPECT_NEAR(FractionTriangleCovered(a, a), 1.0f, kEpsilon);

  // Triangle b is completely inside triangle a (area of b = 0.125, area of a =
  // 0.5)
  EXPECT_NEAR(FractionTriangleCovered(a, b), 1.0f, kEpsilon);

  // Triangle a covers 100% of b, but triangle a's area covered by b is 0.125 /
  // 0.5 = 0.25
  EXPECT_NEAR(FractionTriangleCovered(b, a), 0.25f, kEpsilon);

  // Clockwise order for triangle a should be swapped to CCW in place and give
  // the same result
  std::array<SkPoint, 3> a_cw = {SkPoint::Make(0.0f, 0.0f),
                                 SkPoint::Make(0.0f, 1.0f),
                                 SkPoint::Make(1.0f, 0.0f)};
  EXPECT_NEAR(FractionTriangleCovered(a_cw, b), 1.0f, kEpsilon);
  // Verify that a_cw was swapped to CCW in place
  EXPECT_GT(SkPoint::CrossProduct(a_cw[1] - a_cw[0], a_cw[2] - a_cw[0]), 0.f);

  // Clockwise order for triangle b should be swapped to CCW in place and give
  // the same result
  std::array<SkPoint, 3> b_cw = {SkPoint::Make(0.0f, 0.0f),
                                 SkPoint::Make(0.0f, 0.5f),
                                 SkPoint::Make(0.5f, 0.0f)};
  EXPECT_NEAR(FractionTriangleCovered(a, b_cw), 1.0f, kEpsilon);
  // Verify that b_cw was swapped to CCW in place
  EXPECT_GT(SkPoint::CrossProduct(b_cw[1] - b_cw[0], b_cw[2] - b_cw[0]), 0.f);

  // Disjoint triangles
  std::array<SkPoint, 3> disjoint = {SkPoint::Make(2.0f, 2.0f),
                                     SkPoint::Make(3.0f, 2.0f),
                                     SkPoint::Make(2.0f, 3.0f)};
  EXPECT_EQ(FractionTriangleCovered(disjoint, a), 0.0f);
  EXPECT_EQ(FractionTriangleCovered(a, disjoint), 0.0f);

  // Degenerate / collinear triangles
  std::array<SkPoint, 3> collinear = {SkPoint::Make(0.1f, 0.1f),
                                      SkPoint::Make(0.2f, 0.2f),
                                      SkPoint::Make(0.3f, 0.3f)};
  EXPECT_EQ(FractionTriangleCovered(collinear, a), 0.0f);
  EXPECT_EQ(FractionTriangleCovered(a, collinear), 0.0f);
}

}  // namespace

}  // namespace skia
