/*
 * Copyright 2011 Google Inc.
 *
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */

#include "include/core/SkMatrix.h"
#include "include/core/SkPath.h"
#include "include/core/SkPathBuilder.h"
#include "include/core/SkPathTypes.h"
#include "include/core/SkPoint.h"
#include "include/core/SkRRect.h"
#include "include/core/SkRect.h"
#include "include/core/SkRegion.h"
#include "include/core/SkScalar.h"
#include "include/core/SkTypes.h"
#include "include/private/SkDebug.h"
#include "src/core/SkAutoMalloc.h"
#include "src/core/SkRandom.h"
#include "src/core/SkScan.h"
#include "tests/Test.h"

#include <array>
#include <cstddef>
#include <cstdint>

static void Union(SkRegion* rgn, const SkIRect& rect) {
    rgn->op(rect, SkRegion::kUnion_Op);
}

#define TEST_NO_INTERSECT(rgn, rect)    REPORTER_ASSERT(reporter, !rgn.intersects(rect))
#define TEST_INTERSECT(rgn, rect)       REPORTER_ASSERT(reporter, rgn.intersects(rect))
#define TEST_NO_CONTAINS(rgn, rect)     REPORTER_ASSERT(reporter, !rgn.contains(rect))

// inspired by https://issues.skia.org/issues/40032017
//
static void test_fromchrome(skiatest::Reporter* reporter) {
    SkRegion r;
    Union(&r, SkIRect::MakeXYWH(0, 0, 1, 1));
    TEST_NO_INTERSECT(r, SkIRect::MakeXYWH(0, 0, 0, 0));
    TEST_INTERSECT(r, SkIRect::MakeXYWH(0, 0, 2, 2));
    TEST_INTERSECT(r, SkIRect::MakeXYWH(-1, 0, 2, 2));
    TEST_INTERSECT(r, SkIRect::MakeXYWH(-1, -1, 2, 2));
    TEST_INTERSECT(r, SkIRect::MakeXYWH(0, -1, 2, 2));
    TEST_INTERSECT(r, SkIRect::MakeXYWH(-1, -1, 3, 3));

    Union(&r, SkIRect::MakeXYWH(0, 0, 3, 3));
    Union(&r, SkIRect::MakeXYWH(10, 0, 3, 3));
    Union(&r, SkIRect::MakeXYWH(0, 10, 13, 3));
    TEST_INTERSECT(r, SkIRect::MakeXYWH(-1, -1, 2, 2));
    TEST_INTERSECT(r, SkIRect::MakeXYWH(2, -1, 2, 2));
    TEST_INTERSECT(r, SkIRect::MakeXYWH(2, 2, 2, 2));
    TEST_INTERSECT(r, SkIRect::MakeXYWH(-1, 2, 2, 2));

    TEST_INTERSECT(r, SkIRect::MakeXYWH(9, -1, 2, 2));
    TEST_INTERSECT(r, SkIRect::MakeXYWH(12, -1, 2, 2));
    TEST_INTERSECT(r, SkIRect::MakeXYWH(12, 2, 2, 2));
    TEST_INTERSECT(r, SkIRect::MakeXYWH(9, 2, 2, 2));

    TEST_INTERSECT(r, SkIRect::MakeXYWH(0, -1, 13, 5));
    TEST_INTERSECT(r, SkIRect::MakeXYWH(1, -1, 11, 5));
    TEST_INTERSECT(r, SkIRect::MakeXYWH(2, -1, 9, 5));
    TEST_INTERSECT(r, SkIRect::MakeXYWH(2, -1, 8, 5));
    TEST_INTERSECT(r, SkIRect::MakeXYWH(3, -1, 8, 5));

    TEST_INTERSECT(r, SkIRect::MakeXYWH(0, 1, 13, 1));
    TEST_INTERSECT(r, SkIRect::MakeXYWH(1, 1, 11, 1));
    TEST_INTERSECT(r, SkIRect::MakeXYWH(2, 1, 9, 1));
    TEST_INTERSECT(r, SkIRect::MakeXYWH(2, 1, 8, 1));
    TEST_INTERSECT(r, SkIRect::MakeXYWH(3, 1, 8, 1));

    TEST_INTERSECT(r, SkIRect::MakeXYWH(0, 0, 13, 13));
    TEST_INTERSECT(r, SkIRect::MakeXYWH(0, 1, 13, 11));
    TEST_INTERSECT(r, SkIRect::MakeXYWH(0, 2, 13, 9));
    TEST_INTERSECT(r, SkIRect::MakeXYWH(0, 2, 13, 8));


    // These test SkRegion::contains(Rect) and SkRegion::contains(Region)

    SkRegion container;
    Union(&container, SkIRect::MakeXYWH(0, 0, 40, 20));
    Union(&container, SkIRect::MakeXYWH(30, 20, 10, 20));
    TEST_NO_CONTAINS(container, SkIRect::MakeXYWH(0, 0, 10, 39));
    TEST_NO_CONTAINS(container, SkIRect::MakeXYWH(29, 0, 10, 39));

    {
        SkRegion rgn;
        Union(&rgn, SkIRect::MakeXYWH(0, 0, 10, 10));
        Union(&rgn, SkIRect::MakeLTRB(5, 10, 20, 20));
        TEST_INTERSECT(rgn, SkIRect::MakeXYWH(15, 0, 5, 11));
    }
}

static void test_empties(skiatest::Reporter* reporter) {
    SkRegion valid(SkIRect::MakeWH(10, 10));
    SkRegion empty, empty2;

    REPORTER_ASSERT(reporter, empty.isEmpty());
    REPORTER_ASSERT(reporter, !valid.isEmpty());

    // test intersects
    REPORTER_ASSERT(reporter, !empty.intersects(empty2));
    REPORTER_ASSERT(reporter, !valid.intersects(empty));

    // test contains
    REPORTER_ASSERT(reporter, !empty.contains(empty2));
    REPORTER_ASSERT(reporter, !valid.contains(empty));
    REPORTER_ASSERT(reporter, !empty.contains(valid));

    SkPath emptyPath = SkPathBuilder()
                       .moveTo(1, 5)
                       .close()
                       .detach();
    SkRegion openClip;
    openClip.setRect({-16000, -16000, 16000, 16000});
    empty.setPath(emptyPath, openClip);  // should not assert
}

enum {
    W = 256,
    H = 256
};

static SkIRect randRect(SkRandom& rand) {
    int x = rand.nextU() % W;
    int y = rand.nextU() % H;
    int w = rand.nextU() % W;
    int h = rand.nextU() % H;
    return SkIRect::MakeXYWH(x, y, w >> 1, h >> 1);
}

static void randRgn(SkRandom& rand, SkRegion* rgn, int n) {
    rgn->setEmpty();
    for (int i = 0; i < n; ++i) {
        rgn->op(randRect(rand), SkRegion::kUnion_Op);
    }
}

static bool slow_contains(const SkRegion& outer, const SkRegion& inner) {
    SkRegion tmp;
    tmp.op(outer, inner, SkRegion::kUnion_Op);
    return outer == tmp;
}

static bool slow_contains(const SkRegion& outer, const SkIRect& r) {
    SkRegion tmp;
    tmp.op(outer, SkRegion(r), SkRegion::kUnion_Op);
    return outer == tmp;
}

static bool slow_intersects(const SkRegion& outer, const SkRegion& inner) {
    SkRegion tmp;
    return tmp.op(outer, inner, SkRegion::kIntersect_Op);
}

static void test_contains_iter(skiatest::Reporter* reporter, const SkRegion& rgn) {
    SkRegion::Iterator iter(rgn);
    while (!iter.done()) {
        SkIRect r = iter.rect();
        REPORTER_ASSERT(reporter, rgn.contains(r));
        r.inset(-1, -1);
        REPORTER_ASSERT(reporter, !rgn.contains(r));
        iter.next();
    }
}

static void contains_proc(skiatest::Reporter* reporter,
                          const SkRegion& a, const SkRegion& b) {
    // test rgn
    bool c0 = a.contains(b);
    bool c1 = slow_contains(a, b);
    REPORTER_ASSERT(reporter, c0 == c1);

    // test rect
    SkIRect r = a.getBounds();
    r.inset(r.width()/4, r.height()/4);
    c0 = a.contains(r);
    c1 = slow_contains(a, r);
    REPORTER_ASSERT(reporter, c0 == c1);

    test_contains_iter(reporter, a);
    test_contains_iter(reporter, b);
}

static void test_intersects_iter(skiatest::Reporter* reporter, const SkRegion& rgn) {
    SkRegion::Iterator iter(rgn);
    while (!iter.done()) {
        SkIRect r = iter.rect();
        REPORTER_ASSERT(reporter, rgn.intersects(r));
        r.inset(-1, -1);
        REPORTER_ASSERT(reporter, rgn.intersects(r));
        iter.next();
    }
}

static void intersects_proc(skiatest::Reporter* reporter,
                          const SkRegion& a, const SkRegion& b) {
    bool c0 = a.intersects(b);
    bool c1 = slow_intersects(a, b);
    REPORTER_ASSERT(reporter, c0 == c1);

    test_intersects_iter(reporter, a);
    test_intersects_iter(reporter, b);
}

static void test_proc(skiatest::Reporter* reporter,
                      void (*proc)(skiatest::Reporter*,
                                   const SkRegion& a, const SkRegion&)) {
    SkRandom rand;
    for (int i = 0; i < 10000; ++i) {
        SkRegion outer;
        randRgn(rand, &outer, 8);
        SkRegion inner;
        randRgn(rand, &inner, 2);
        proc(reporter, outer, inner);
    }
}

DEF_TEST(Region, reporter) {
    test_proc(reporter, contains_proc);
    test_proc(reporter, intersects_proc);
    test_empties(reporter);
    test_fromchrome(reporter);
}

// Test that writeToMemory reports the same number of bytes whether there was a
// buffer to write to or not.
static void test_write(const SkRegion& region, skiatest::Reporter* r) {
    const size_t bytesNeeded = region.writeToMemory(nullptr);
    SkAutoMalloc storage(bytesNeeded);
    const size_t bytesWritten = region.writeToMemory(storage.get());
    REPORTER_ASSERT(r, bytesWritten == bytesNeeded);

    // Also check that the bytes are meaningful.
    SkRegion copy;
    REPORTER_ASSERT(r, copy.readFromMemory(storage.get(), bytesNeeded));
    REPORTER_ASSERT(r, region == copy);
}

DEF_TEST(Region_writeToMemory, r) {
    // Test an empty region.
    SkRegion region;
    REPORTER_ASSERT(r, region.isEmpty());
    test_write(region, r);

    // Test a rectangular region
    bool nonEmpty = region.setRect({0, 0, 50, 50});
    REPORTER_ASSERT(r, nonEmpty);
    REPORTER_ASSERT(r, region.isRect());
    test_write(region, r);

    // Test a complex region
    nonEmpty = region.op({50, 50, 100, 100}, SkRegion::kUnion_Op);
    REPORTER_ASSERT(r, nonEmpty);
    REPORTER_ASSERT(r, region.isComplex());
    test_write(region, r);

    SkRegion complexRegion;
    Union(&complexRegion, SkIRect::MakeXYWH(0, 0, 1, 1));
    Union(&complexRegion, SkIRect::MakeXYWH(0, 0, 3, 3));
    Union(&complexRegion, SkIRect::MakeXYWH(10, 0, 3, 3));
    Union(&complexRegion, SkIRect::MakeXYWH(0, 10, 13, 3));
    test_write(complexRegion, r);

    Union(&complexRegion, SkIRect::MakeXYWH(10, 20, 3, 3));
    Union(&complexRegion, SkIRect::MakeXYWH(0,  20, 3, 3));
    test_write(complexRegion, r);
}

DEF_TEST(Region_readFromMemory_bad, r) {
    // These assume what our binary format is: conceivably we could change it
    // and might need to remove or change some of these tests.
    SkRegion region;

    {
        // invalid boundary rectangle
        int32_t data[5] = {0, 4, 4, 8, 2};
        REPORTER_ASSERT(r, 0 == region.readFromMemory(data, sizeof(data)));
    }
    // Region Layout, Serialized Format:
    //    COUNT LEFT TOP RIGHT BOTTOM Y_SPAN_COUNT TOTAL_INTERVAL_COUNT
    //    Top ( Bottom Span_Interval_Count ( Left Right )* Sentinel )+ Sentinel
    {
        // Example of valid data
        int32_t data[] = {9, 0, 0, 10, 10, 1, 2, 0, 10, 2, 0, 4, 6, 10,
                          2147483647, 2147483647};
        REPORTER_ASSERT(r, 0 != region.readFromMemory(data, sizeof(data)));
    }
    {
        // Example of valid data with 4 intervals
        int32_t data[] = {19, 0, 0, 30, 30, 3, 4, 0, 10, 2, 0, 10, 20, 30,
                          2147483647, 20, 0, 2147483647, 30, 2, 0, 10, 20, 30,
                          2147483647, 2147483647};
        REPORTER_ASSERT(r, 0 != region.readFromMemory(data, sizeof(data)));
    }
    {
        // Short count
        int32_t data[] = {8, 0, 0, 10, 10, 1, 2, 0, 10, 2, 0, 4, 6, 10,
                          2147483647, 2147483647};
        REPORTER_ASSERT(r, 0 == region.readFromMemory(data, sizeof(data)));
    }
    {
        // bounds don't match
        int32_t data[] = {9, 0, 0, 10, 11, 1, 2, 0, 10, 2, 0, 4, 6, 10,
                          2147483647, 2147483647};
        REPORTER_ASSERT(r, 0 == region.readFromMemory(data, sizeof(data)));
    }
    {
        //  bad yspan count
        int32_t data[] = {9, 0, 0, 10, 10, 2, 2, 0, 10, 2, 0, 4, 6, 10,
                          2147483647, 2147483647};
        REPORTER_ASSERT(r, 0 == region.readFromMemory(data, sizeof(data)));
    }
    {
        // bad int count
        int32_t data[] = {9, 0, 0, 10, 10, 1, 3, 0, 10, 2, 0, 4, 6, 10,
                          2147483647, 2147483647};
        REPORTER_ASSERT(r, 0 == region.readFromMemory(data, sizeof(data)));
    }
    {
        // bad final sentinal
        int32_t data[] = {9, 0, 0, 10, 10, 1, 2, 0, 10, 2, 0, 4, 6, 10,
                          2147483647, -1};
        REPORTER_ASSERT(r, 0 == region.readFromMemory(data, sizeof(data)));
    }
    {
        // bad row sentinal
        int32_t data[] = {9, 0, 0, 10, 10, 1, 2, 0, 10, 2, 0, 4, 6, 10,
                          -1, 2147483647};
        REPORTER_ASSERT(r, 0 == region.readFromMemory(data, sizeof(data)));
    }
    {
        // starts with empty yspan
        int32_t data[] = {12, 0, 0, 10, 10, 2, 2, -5, 0, 0, 2147483647, 10,
                          2, 0, 4, 6, 10, 2147483647, 2147483647};
        REPORTER_ASSERT(r, 0 == region.readFromMemory(data, sizeof(data)));
    }
    {
        // ends with empty yspan
        int32_t data[] = {12, 0, 0, 10, 10, 2, 2, 0, 10, 2, 0, 4, 6, 10,
                          2147483647, 15, 0, 2147483647, 2147483647};
        REPORTER_ASSERT(r, 0 == region.readFromMemory(data, sizeof(data)));
    }
    {
        // y intervals out of order
        int32_t data[] = {19, 0, -20, 30, 10, 3, 4, 0, 10, 2, 0, 10, 20, 30,
                          2147483647, -20, 0, 2147483647, -10, 2, 0, 10, 20, 30,
                          2147483647, 2147483647};
        REPORTER_ASSERT(r, 0 == region.readFromMemory(data, sizeof(data)));
    }
    {
        // x intervals out of order
        int32_t data[] = {9, 0, 0, 10, 10, 1, 2, 0, 10, 2, 6, 10, 0, 4,
                          2147483647, 2147483647};
        REPORTER_ASSERT(r, 0 == region.readFromMemory(data, sizeof(data)));
    }
}

DEF_TEST(region_toobig, reporter) {
    const int big = 1 << 30;
    const SkIRect neg = SkIRect::MakeXYWH(-big, -big, 10, 10);
    const SkIRect pos = SkIRect::MakeXYWH( big,  big, 10, 10);

    REPORTER_ASSERT(reporter, !neg.isEmpty());
    REPORTER_ASSERT(reporter, !pos.isEmpty());

    SkRegion negR(neg);
    SkRegion posR(pos);

    REPORTER_ASSERT(reporter, !negR.isEmpty());
    REPORTER_ASSERT(reporter, !posR.isEmpty());

    SkRegion rgn;
    rgn.op(negR, posR, SkRegion::kUnion_Op);

    // If we union those to rectangles, the resulting coordinates span more than int32_t, so
    // we must mark the region as empty.
    REPORTER_ASSERT(reporter, rgn.isEmpty());
}

DEF_TEST(region_inverse_union_skbug_7491, reporter) {
    SkPath path = SkPathBuilder(SkPathFillType::kInverseWinding)
                  .moveTo(10, 20)
                  .lineTo(10, 30)
                  .lineTo(10.1f, 10)
                  .close()
                  .detach();

    SkRegion clip;
    clip.op(SkIRect::MakeLTRB(10, 10, 15, 20), SkRegion::kUnion_Op);
    clip.op(SkIRect::MakeLTRB(20, 10, 25, 20), SkRegion::kUnion_Op);

    SkRegion rgn;
    rgn.setPath(path, clip);

    REPORTER_ASSERT(reporter, clip == rgn);
}

DEF_TEST(giant_path_region, reporter) {
    const SkScalar big = 32767;
    SkPath path = SkPathBuilder()
                  .moveTo(-big, 0)
                  .quadTo(big, 0, big, big)
                  .detach();
    SkIRect ir = path.getBounds().round();
    SkRegion rgn;
    rgn.setPath(path, SkRegion(ir));
}

DEF_TEST(rrect_region_crbug_850350, reporter) {
    SkMatrix m;
    m.reset();
    m[1] = 0.753662348f;
    m[3] = 1.40079998E+20f;

    const SkPoint corners[] = {
        { 2.65876e-19f, 0.0194088f },
        { 4896, 0.00114702f },
        { 0, 0 },
        { 0.00114702f, 0.00495333f },
    };
    SkRRect rrect;
    rrect.setRectRadii({-8.72387e-31f, 1.29996e-38f, 4896, 1.125f}, corners);

    SkPath path = SkPath::RRect(rrect).makeTransform(m);

    SkRegion rgn;
    rgn.setPath(path, SkRegion{SkIRect{0, 0, 24, 24}});
}

DEF_TEST(region_bug_chromium_873051, reporter) {
    SkRegion region;
    REPORTER_ASSERT(reporter,  region.setRect({0, 0, 0x7FFFFFFE, 0x7FFFFFFE}));
    REPORTER_ASSERT(reporter, !region.setRect({0, 0, 0x7FFFFFFE, 0x7FFFFFFF}));
    REPORTER_ASSERT(reporter, !region.setRect({0, 0, 0x7FFFFFFF, 0x7FFFFFFE}));
    REPORTER_ASSERT(reporter, !region.setRect({0, 0, 0x7FFFFFFF, 0x7FFFFFFF}));
}

DEF_TEST(region_empty_iter, reporter) {
    SkRegion::Iterator emptyIter;
    REPORTER_ASSERT(reporter, !emptyIter.rewind());
    REPORTER_ASSERT(reporter, emptyIter.done());
    auto eRect = emptyIter.rect();
    REPORTER_ASSERT(reporter, eRect.isEmpty());
    REPORTER_ASSERT(reporter, SkIRect::MakeEmpty() == eRect);
    REPORTER_ASSERT(reporter, !emptyIter.rgn());

    SkRegion region;
    SkRegion::Iterator resetIter;
    resetIter.reset(region);
    REPORTER_ASSERT(reporter, resetIter.rewind());
    REPORTER_ASSERT(reporter, resetIter.done());
    auto rRect = resetIter.rect();
    REPORTER_ASSERT(reporter, rRect.isEmpty());
    REPORTER_ASSERT(reporter, SkIRect::MakeEmpty() == rRect);
    REPORTER_ASSERT(reporter, resetIter.rgn());
    REPORTER_ASSERT(reporter, resetIter.rgn()->isEmpty());

    SkRegion::Iterator iter(region);
    REPORTER_ASSERT(reporter, iter.done());
    auto iRect = iter.rect();
    REPORTER_ASSERT(reporter, iRect.isEmpty());
    REPORTER_ASSERT(reporter, SkIRect::MakeEmpty() == iRect);
    REPORTER_ASSERT(reporter, iter.rgn());
    REPORTER_ASSERT(reporter, iter.rgn()->isEmpty());

    SkRegion::Cliperator clipIter(region, {0, 0, 100, 100});
    REPORTER_ASSERT(reporter, clipIter.done());
    auto cRect = clipIter.rect();
    REPORTER_ASSERT(reporter, cRect.isEmpty());
    REPORTER_ASSERT(reporter, SkIRect::MakeEmpty() == cRect);

    SkRegion::Spanerator spanIter(region, 0, 0, 100);
    int left = 0, right = 0;
    REPORTER_ASSERT(reporter, !spanIter.next(&left, &right));
    REPORTER_ASSERT(reporter, !left);
    REPORTER_ASSERT(reporter, !right);
}

DEF_TEST(region_very_large, reporter) {
    SkIRect clipBounds = {-45000, -45000, 45000, 45000};
    REPORTER_ASSERT(reporter, SkScan::PathRequiresTiling(clipBounds));

    // Create a path that is larger than the scan conversion limits of SkScan, which is internally
    // used to convert a path to a region.
    SkPath largePath = SkPath::RRect(SkRRect::MakeRectXY(SkRect::Make(clipBounds), 200.f, 200.f));

    SkRegion largeRegion;
    REPORTER_ASSERT(reporter, largeRegion.setPath(largePath, SkRegion{clipBounds}));

    // The path should have been converted successfully, so the corners of clipBounds should not be
    // contained due to the path's rounded corners.
    REPORTER_ASSERT(reporter, !largeRegion.contains(-44995, -44995));
    REPORTER_ASSERT(reporter, !largeRegion.contains(-44995,  44995));
    REPORTER_ASSERT(reporter, !largeRegion.contains( 44995, -44995));
    REPORTER_ASSERT(reporter, !largeRegion.contains( 44995,  44995));

    // But these points should be within the rounded corners.
    REPORTER_ASSERT(reporter, largeRegion.contains(-44600, -44600));
    REPORTER_ASSERT(reporter, largeRegion.contains(-44600,  44600));
    REPORTER_ASSERT(reporter, largeRegion.contains( 44600, -44600));
    REPORTER_ASSERT(reporter, largeRegion.contains( 44600,  44600));

    // Make another path shaped like a D, so two corners will have its large radii and the other two
    // will be rectangular and thus clipped by the original region.
    static const SkVector kLargeRadii[4] = { {0.f, 0.f},       // TL
                                             {2000.f, 2000.f}, // TR
                                             {2000.f, 2000.f}, // BR
                                             {0.f, 0.f}};      // BL
    SkRRect largeRRect;
    largeRRect.setRectRadii(SkRect::Make(clipBounds), kLargeRadii);
    REPORTER_ASSERT(reporter, largeRegion.setPath(SkPath::RRect(largeRRect), SkRegion{largeRegion}));

    REPORTER_ASSERT(reporter, !largeRegion.contains(-44995, -44995));
    REPORTER_ASSERT(reporter, !largeRegion.contains(-44995,  44995));
    REPORTER_ASSERT(reporter, !largeRegion.contains( 44995, -44995));
    REPORTER_ASSERT(reporter, !largeRegion.contains( 44995,  44995));

    REPORTER_ASSERT(reporter, largeRegion.contains(-44600, -44600));
    REPORTER_ASSERT(reporter, largeRegion.contains(-44600,  44600));
    // Right side has been clipped by an even larger corner radii
    REPORTER_ASSERT(reporter, !largeRegion.contains( 44600, -44600));
    REPORTER_ASSERT(reporter, !largeRegion.contains( 44600,  44600));

    // Now test that the very large path with a small clip also works
    largePath = SkPath::RRect(SkRRect::MakeRectXY({0.f, 0.f, 45000.f, 45000.f}, 200.f, 200.f));
    SkRegion smallRegion;
    REPORTER_ASSERT(reporter, smallRegion.setPath(largePath, SkRegion{{0, 0, 500, 500}}));

    REPORTER_ASSERT(reporter, !smallRegion.contains(5, 5));
    REPORTER_ASSERT(reporter, smallRegion.contains(0, 499));
    REPORTER_ASSERT(reporter, smallRegion.contains(499, 0));
    REPORTER_ASSERT(reporter, smallRegion.contains(499, 499));
}

DEF_TEST(region_b510359475, reporter) {
    SkPath path = SkPathBuilder(SkPathFillType::kInverseEvenOdd)
                          .moveTo(-1.75556736e+09f, 1.75556736e+09f)
                          .lineTo(-0.00604508677f, 0.00604508677f)
                          .quadTo(-0.00604508677f, 0.00604508677f, -0.00604508677f, 0.00604508677f)
                          .lineTo(214328.828f, -214328.828f)
                          .quadTo(214328.828f, -214328.828f, 214328.828f, -214328.828f)
                          .lineTo(214328.828f, -214328.828f)
                          .quadTo(214328.828f, -214328.828f, 214328.828f, -214328.828f)
                          .lineTo(214328.812f, -214328.812f)
                          .detach();

    SkIRect clipBounds = {-1755567360, -214329, 214329, 1755567360};
    SkRegion region;

    // This should fail gracefully and quickly (returning false) instead of timing out.
    bool ok = region.setPath(path, SkRegion(clipBounds));
    REPORTER_ASSERT(reporter, !ok);
    REPORTER_ASSERT(reporter, region.isEmpty());
}

DEF_TEST(SkRegion_Iterator_StepsThroughAllScanlines, reporter) {
    SkRegion rgn1;
    rgn1.op({12, 10, 17, 20}, SkRegion::kUnion_Op);
    rgn1.op({31, 10, 39, 25}, SkRegion::kUnion_Op);
    rgn1.op({16, 30, 23, 40}, SkRegion::kUnion_Op);

    int32_t buffer[32];
    memset(&buffer, 0, 128);
    size_t len = rgn1.writeToMemory(&buffer);
    SkASSERT_RELEASE(len < 128);
    SkRegion rgn2;
    size_t len2 = rgn2.readFromMemory(&buffer, len);
    REPORTER_ASSERT(reporter, len == len2);

    // Make sure the serialized/deserialzed version is the same as the original.
    for (const auto& rgn : {rgn1, rgn2}) {
        SkRegion::Iterator iter(rgn);

        // The first scanline strip starts at Y=10 and ends at Y=20.
        //   The first rectangle there starts at X=12 and ends at X=17
        REPORTER_ASSERT(reporter, !iter.done());
        REPORTER_ASSERT(reporter, iter.rect() == SkIRect::MakeLTRB(12, 10, 17, 20));

        //    There's a second rectangle in that section from X=31 to X=39.
        iter.next();
        REPORTER_ASSERT(reporter, !iter.done());
        REPORTER_ASSERT(reporter, iter.rect() == SkIRect::MakeLTRB(31, 10, 39, 20));

        // The next scanline strip continues at Y=20 and goes til Y=25
        //     The one and only rectangle here starts at X=31 and goes to X=39
        iter.next();
        REPORTER_ASSERT(reporter, !iter.done());
        REPORTER_ASSERT(reporter, iter.rect() == SkIRect::MakeLTRB(31, 20, 39, 25));

        // There's a jump to the final scanline strip from Y=30 to Y=40
        //     The one and only rectangle here starts at X=16 and goes to X=23
        iter.next();
        REPORTER_ASSERT(reporter, !iter.done());
        REPORTER_ASSERT(reporter, iter.rect() == SkIRect::MakeLTRB(16, 30, 23, 40));

        // Call next() -> no more rectangles
        iter.next();
        REPORTER_ASSERT(reporter, iter.done());
    }
}

DEF_TEST(SkRegion_ReadFromMemory_ConsecutiveEmptySlices_Invalid, reporter) {
    constexpr int32_t kSentinel = 0x7FFFFFFF;
    const int32_t corrupt[] = {
        42,             // number of int32s in the RLE portion (after 7 metadata int32s)
        0, 0, 100, 100, // bounds
        12,             // 12 spans (10 are empty)
        2,              // 2 rectangles
        0, 5,           // first stripe is from Y=0 to Y=5,
        1,              // 1 rectangle
        0, 100,         // from x = 0 to 100 (arbitrary)
        kSentinel,
        10, 0, kSentinel, // Empty from  5-10
        20, 0, kSentinel, // Empty from 10-20...
        30, 0, kSentinel,
        40, 0, kSentinel,
        50, 0, kSentinel,
        60, 0, kSentinel,
        70, 0, kSentinel,
        80, 0, kSentinel,
        90, 0, kSentinel,
        95, 0, kSentinel,
        100, 1, 20, 30, kSentinel, // one final real rectangle until Y=100
        kSentinel, // final sentinal
    };

    SkRegion rgn;
    size_t len = rgn.readFromMemory(&corrupt, sizeof(corrupt));
    REPORTER_ASSERT(reporter, len == 0);  // len == 0 means "could not read"

    // When there was a buggy version of this, the following iteration caused a crash.
    SkRegion::Iterator iter(rgn);
    while (!iter.done()) {
        iter.next();
    }
}

DEF_TEST(SkRegion_ReadFromMemory_SingleEmptySlice_Valid, reporter) {
    constexpr int32_t kSentinel = 0x7FFFFFFF;
    const int32_t valid[] = {
        15,             // number of int32s in the RLE portion (after 7 metadata int32s)
        0, 0, 100, 100, // bounds
        3,              // 3 spans (1 is empty)
        2,              // 2 rectangles
        0, 5,           // first stripe is from Y=0 to Y=5,
        1,              // 1 rectangle
        0, 100,         // from x = 0 to 100 (arbitrary)
        kSentinel,
        80, 0, kSentinel, // Empty from  5-80
        100, 1, 20, 30, kSentinel, // one final real rectangle
        kSentinel, // final sentinal
    };

    SkRegion rgn;
    size_t len = rgn.readFromMemory(&valid, sizeof(valid));
    REPORTER_ASSERT(reporter, len > 0);  // len == 0 means "could not read"

    // Make sure we don't read any memory we aren't supposed to.
    SkRegion::Iterator iter(rgn);
    while (!iter.done()) {
        iter.next();
    }
}

static bool test_rects(SkSpan<const SkIRect> rects) {
    SkRegion rgn0, rgn1;

    for (size_t i = 0; i < rects.size(); i++) {
        rgn0.op(rects[i], SkRegion::kUnion_Op);
    }
    rgn1.setRects(rects);

    if (rgn0 != rgn1) {
        SkDebugf("\n");
        for (size_t i = 0; i < rects.size(); i++) {
            SkDebugf(" { %d, %d, %d, %d },\n",
                     rects[i].fLeft, rects[i].fTop,
                     rects[i].fRight, rects[i].fBottom);
        }
        SkDebugf("\n");
        return false;
    }
    return true;
}

DEF_TEST(Region_setRects, reporter) {
    auto test = [&](const char* name, SkSpan<const SkIRect> rects) {
        skiatest::ReporterContext ctx(reporter, SkString(name));
        REPORTER_ASSERT(reporter, test_rects(rects));

        SkRegion rgn;
        bool nonEmpty = rgn.setRects(rects);
        REPORTER_ASSERT(reporter, nonEmpty == !rgn.isEmpty());
    };

    test("0_rects", {});

    const SkIRect r1[] = {{0, 0, 10, 10}};
    test("1_rect", r1);

    const SkIRect r2[] = {{0, 0, 1, 1}, {2, 2, 3, 3}};
    test("2_rects", r2);

    const SkIRect r3[] = {{0, 0, 1, 1}, {2, 2, 3, 3}, {4, 4, 5, 5}};
    test("3_rects", r3);

    const SkIRect r4[] = {
        { 0, 0, 1, 2 },
        { 2, 1, 3, 3 },
        { 4, 0, 5, 1 },
        { 6, 0, 7, 4 },
    };
    test("4_rects", r4);

    const SkIRect rOverlap[] = {{0, 0, 10, 10}, {5, 0, 15, 10}, {0, 5, 10, 15}, {5, 5, 15, 15}};
    test("overlapping_rects", rOverlap);

    const SkIRect rEmpty[] = {{0, 0, 0, 0}, {10, 10, 10, 10}};
    test("empty_rects", rEmpty);

    SkIRect grid[100];
    for (int y = 0; y < 10; ++y) {
        for (int x = 0; x < 10; ++x) {
            grid[y * 10 + x] = SkIRect::MakeXYWH(x * 20, y * 20, 10, 10);
        }
    }
    test("100_rects_grid", grid);
}
