/*
 * Copyright 2013 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/SkBitmap.h"
#include "include/core/SkCanvas.h"
#include "include/core/SkColor.h"
#include "include/core/SkPaint.h"
#include "include/core/SkPath.h"
#include "include/core/SkRect.h"
#include "include/pathops/SkPathOps.h"
#include "include/private/base/SkTDArray.h"
#include "src/base/SkRandom.h"
#include "tests/PathOpsExtendedTest.h"
#include "tests/PathOpsThreadedCommon.h"
#include "tests/Test.h"

#include <algorithm>
#include <cstdint>

static void testTightBoundsLines(PathOpsThreadState* data) {
    SkRandom ran;
    for (int index = 0; index < 1000; ++index) {
        SkPath path;
        int contourCount = ran.nextRangeU(1, 10);
        for (int cIndex = 0; cIndex < contourCount; ++cIndex) {
            int lineCount = ran.nextRangeU(1, 10);
            path.moveTo(ran.nextRangeF(-1000, 1000), ran.nextRangeF(-1000, 1000));
            for (int lIndex = 0; lIndex < lineCount; ++lIndex) {
                path.lineTo(ran.nextRangeF(-1000, 1000), ran.nextRangeF(-1000, 1000));
            }
            if (ran.nextBool()) {
                path.close();
            }
        }
        SkRect classicBounds = path.getBounds();
        SkRect tightBounds;
        REPORTER_ASSERT(data->fReporter, TightBounds(path, &tightBounds));
        REPORTER_ASSERT(data->fReporter, classicBounds == tightBounds);
    }
}

DEF_TEST(PathOpsTightBoundsLines, reporter) {
    initializeTests(reporter, "tightBoundsLines");
    PathOpsThreadedTestRunner testRunner(reporter);
    int outerCount = reporter->allowExtendedTest() ? 100 : 1;
    for (int index = 0; index < outerCount; ++index) {
        for (int idx2 = 0; idx2 < 10; ++idx2) {
            *testRunner.fRunnables.append() =
                    new PathOpsThreadedRunnable(&testTightBoundsLines, 0, 0, 0, 0, &testRunner);
        }
    }
    testRunner.render();
}

static void testTightBoundsQuads(PathOpsThreadState* data) {
    SkRandom ran;
    const int bitWidth = 32;
    const int bitHeight = 32;
    const float pathMin = 1;
    const float pathMax = (float) (bitHeight - 2);
    SkBitmap& bits = *data->fBitmap;
    if (bits.width() == 0) {
        bits.allocN32Pixels(bitWidth, bitHeight);
    }
    SkCanvas canvas(bits);
    SkPaint paint;
    for (int index = 0; index < 100; ++index) {
        SkPath path;
        int contourCount = ran.nextRangeU(1, 10);
        for (int cIndex = 0; cIndex < contourCount; ++cIndex) {
            int lineCount = ran.nextRangeU(1, 10);
            path.moveTo(ran.nextRangeF(1, pathMax), ran.nextRangeF(pathMin, pathMax));
            for (int lIndex = 0; lIndex < lineCount; ++lIndex) {
                if (ran.nextBool()) {
                    path.lineTo(ran.nextRangeF(pathMin, pathMax), ran.nextRangeF(pathMin, pathMax));
                } else {
                    path.quadTo(ran.nextRangeF(pathMin, pathMax), ran.nextRangeF(pathMin, pathMax),
                            ran.nextRangeF(pathMin, pathMax), ran.nextRangeF(pathMin, pathMax));
                }
            }
            if (ran.nextBool()) {
                path.close();
            }
        }
        SkRect classicBounds = path.getBounds();
        SkRect tightBounds;
        REPORTER_ASSERT(data->fReporter, TightBounds(path, &tightBounds));
        REPORTER_ASSERT(data->fReporter, classicBounds.contains(tightBounds));
        canvas.drawColor(SK_ColorWHITE);
        canvas.drawPath(path, paint);
        SkIRect bitsWritten = {31, 31, 0, 0};
        for (int y = 0; y < bitHeight; ++y) {
            uint32_t* addr1 = data->fBitmap->getAddr32(0, y);
            bool lineWritten = false;
            for (int x = 0; x < bitWidth; ++x) {
                if (addr1[x] == (uint32_t) -1) {
                    continue;
                }
                lineWritten = true;
                bitsWritten.fLeft = std::min(bitsWritten.fLeft, x);
                bitsWritten.fRight = std::max(bitsWritten.fRight, x);
            }
            if (!lineWritten) {
                continue;
            }
            bitsWritten.fTop = std::min(bitsWritten.fTop, y);
            bitsWritten.fBottom = std::max(bitsWritten.fBottom, y);
        }
        if (!bitsWritten.isEmpty()) {
            SkIRect tightOut;
            tightBounds.roundOut(&tightOut);
            REPORTER_ASSERT(data->fReporter, tightOut.contains(bitsWritten));
        }
    }
}

DEF_TEST(PathOpsTightBoundsQuads, reporter) {
    initializeTests(reporter, "tightBoundsQuads");
    PathOpsThreadedTestRunner testRunner(reporter);
    int outerCount = reporter->allowExtendedTest() ? 100 : 1;
    for (int index = 0; index < outerCount; ++index) {
        for (int idx2 = 0; idx2 < 10; ++idx2) {
            *testRunner.fRunnables.append() =
                    new PathOpsThreadedRunnable(&testTightBoundsQuads, 0, 0, 0, 0, &testRunner);
        }
    }
    testRunner.render();
}

DEF_TEST(PathOpsTightBoundsMove, reporter) {
    SkPath path;
    path.moveTo(10, 10);
    path.close();
    path.moveTo(20, 20);
    path.lineTo(20, 20);
    path.close();
    path.moveTo(15, 15);
    path.lineTo(15, 15);
    path.close();
    const SkRect& bounds = path.getBounds();
    SkRect tight;
    REPORTER_ASSERT(reporter, TightBounds(path, &tight));
    REPORTER_ASSERT(reporter, bounds == tight);
}

DEF_TEST(PathOpsTightBoundsMoveOne, reporter) {
    SkPath path;
    path.moveTo(20, 20);
    const SkRect& bounds = path.getBounds();
    SkRect tight;
    REPORTER_ASSERT(reporter, TightBounds(path, &tight));
    REPORTER_ASSERT(reporter, bounds == tight);
}

DEF_TEST(PathOpsTightBoundsMoveTwo, reporter) {
    SkPath path;
    path.moveTo(20, 20);
    path.moveTo(40, 40);
    const SkRect& bounds = path.getBounds();
    SkRect tight;
    REPORTER_ASSERT(reporter, TightBounds(path, &tight));
    REPORTER_ASSERT(reporter, bounds == tight);
}

DEF_TEST(PathOpsTightBoundsTiny, reporter) {
    SkPath path;
    path.moveTo(1, 1);
    path.quadTo(1.000001f, 1, 1, 1);
    const SkRect& bounds = path.getBounds();
    SkRect tight;
    REPORTER_ASSERT(reporter, TightBounds(path, &tight));
    SkRect moveBounds = {1, 1, 1, 1};
    REPORTER_ASSERT(reporter, bounds != tight);
    REPORTER_ASSERT(reporter, moveBounds == tight);
}

DEF_TEST(PathOpsTightBoundsWellBehaved, reporter) {
    SkPath path;
    path.moveTo(1, 1);
    path.quadTo(2, 3, 4, 5);
    const SkRect& bounds = path.getBounds();
    SkRect tight;
    REPORTER_ASSERT(reporter, TightBounds(path, &tight));
    REPORTER_ASSERT(reporter, bounds == tight);
}

DEF_TEST(PathOpsTightBoundsIllBehaved, reporter) {
    SkPath path;
    path.moveTo(1, 1);
    path.quadTo(4, 3, 2, 2);
    const SkRect& bounds = path.getBounds();
    SkRect tight;
    REPORTER_ASSERT(reporter, TightBounds(path, &tight));
    REPORTER_ASSERT(reporter, bounds != tight);
}

DEF_TEST(PathOpsTightBoundsIllBehavedScaled, reporter) {
    SkPath path;
    path.moveTo(0, 0);
    path.quadTo(1048578, 1048577, 1048576, 1048576);
    const SkRect& bounds = path.getBounds();
    SkRect tight;
    REPORTER_ASSERT(reporter, TightBounds(path, &tight));
    REPORTER_ASSERT(reporter, bounds != tight);
    REPORTER_ASSERT(reporter, tight.right() == 1048576);
    REPORTER_ASSERT(reporter, tight.bottom() == 1048576);
}
