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

#include "tests/PathOpsExtendedTest.h"
#include "tests/PathOpsThreadedCommon.h"
#include "tests/Test.h"

static SkPath build_squircle(SkPath::Verb verb, const SkRect& rect, SkPathDirection dir) {
    SkPath path;
    bool reverse = SkPathDirection::kCCW == dir;
    switch (verb) {
        case SkPath::kLine_Verb:
            path.addRect(rect, dir);
            reverse = false;
            break;
        case SkPath::kQuad_Verb:
            path.moveTo(rect.centerX(), rect.fTop);
            path.quadTo(rect.fRight, rect.fTop, rect.fRight, rect.centerY());
            path.quadTo(rect.fRight, rect.fBottom, rect.centerX(), rect.fBottom);
            path.quadTo(rect.fLeft, rect.fBottom, rect.fLeft, rect.centerY());
            path.quadTo(rect.fLeft, rect.fTop, rect.centerX(), rect.fTop);
            break;
        case SkPath::kConic_Verb:
            path.addCircle(rect.centerX(), rect.centerY(), rect.width() / 2, dir);
            reverse = false;
            break;
        case SkPath::kCubic_Verb: {
            SkScalar aX14 = rect.fLeft + rect.width() * 1 / 4;
            SkScalar aX34 = rect.fLeft + rect.width() * 3 / 4;
            SkScalar aY14 = rect.fTop + rect.height() * 1 / 4;
            SkScalar aY34 = rect.fTop + rect.height() * 3 / 4;
            path.moveTo(rect.centerX(), rect.fTop);
            path.cubicTo(aX34, rect.fTop, rect.fRight, aY14, rect.fRight, rect.centerY());
            path.cubicTo(rect.fRight, aY34, aX34, rect.fBottom, rect.centerX(), rect.fBottom);
            path.cubicTo(aX14, rect.fBottom, rect.fLeft, aY34, rect.fLeft, rect.centerY());
            path.cubicTo(rect.fLeft, aY14, aX14, rect.fTop, rect.centerX(), rect.fTop);
            } break;
        default:
            SkASSERT(0);
    }
    if (reverse) {
        SkPath temp;
        temp.reverseAddPath(path);
        path.swap(temp);
    }
    return path;
}

DEF_TEST(PathOpsAsWinding, reporter) {
    SkPath test, result;
    test.addRect({1, 2, 3, 4});
    // if test is winding
    REPORTER_ASSERT(reporter, AsWinding(test, &result));
    REPORTER_ASSERT(reporter, test == result);
    // if test is empty
    test.reset();
    test.setFillType(SkPathFillType::kEvenOdd);
    REPORTER_ASSERT(reporter, AsWinding(test, &result));
    REPORTER_ASSERT(reporter, result.isEmpty());
    REPORTER_ASSERT(reporter, result.getFillType() == SkPathFillType::kWinding);
    // if test is convex
    test.addCircle(5, 5, 10);
    REPORTER_ASSERT(reporter, AsWinding(test, &result));
    REPORTER_ASSERT(reporter, result.isConvex());
    test.setFillType(SkPathFillType::kWinding);
    REPORTER_ASSERT(reporter, test == result);
    // if test has infinity
    test.reset();
    test.addRect({1, 2, 3, SK_ScalarInfinity});
    test.setFillType(SkPathFillType::kEvenOdd);
    REPORTER_ASSERT(reporter, !AsWinding(test, &result));
    // if test has only one contour
    test.reset();
    SkPoint ell[] = {{0, 0}, {4, 0}, {4, 1}, {1, 1}, {1, 4}, {0, 4}};
    test.addPoly(ell, SK_ARRAY_COUNT(ell), true);
    test.setFillType(SkPathFillType::kEvenOdd);
    REPORTER_ASSERT(reporter, AsWinding(test, &result));
    REPORTER_ASSERT(reporter, !result.isConvex());
    test.setFillType(SkPathFillType::kWinding);
    REPORTER_ASSERT(reporter, test == result);
    // test two contours that do not overlap or share bounds
    test.addRect({5, 2, 6, 3});
    test.setFillType(SkPathFillType::kEvenOdd);
    REPORTER_ASSERT(reporter, AsWinding(test, &result));
    REPORTER_ASSERT(reporter, !result.isConvex());
    test.setFillType(SkPathFillType::kWinding);
    REPORTER_ASSERT(reporter, test == result);
    // test two contours that do not overlap but share bounds
    test.reset();
    test.addPoly(ell, SK_ARRAY_COUNT(ell), true);
    test.addRect({2, 2, 3, 3});
    test.setFillType(SkPathFillType::kEvenOdd);
    REPORTER_ASSERT(reporter, AsWinding(test, &result));
    REPORTER_ASSERT(reporter, !result.isConvex());
    test.setFillType(SkPathFillType::kWinding);
    REPORTER_ASSERT(reporter, test == result);
    // test two contours that partially overlap
    test.reset();
    test.addRect({0, 0, 3, 3});
    test.addRect({1, 1, 4, 4});
    test.setFillType(SkPathFillType::kEvenOdd);
    REPORTER_ASSERT(reporter, AsWinding(test, &result));
    REPORTER_ASSERT(reporter, !result.isConvex());
    test.setFillType(SkPathFillType::kWinding);
    REPORTER_ASSERT(reporter, test == result);
    // test that result may be input
    SkPath copy = test;
    test.setFillType(SkPathFillType::kEvenOdd);
    REPORTER_ASSERT(reporter, AsWinding(test, &test));
    REPORTER_ASSERT(reporter, !test.isConvex());
    REPORTER_ASSERT(reporter, test == copy);
    // test a in b, b in a, cw/ccw
    constexpr SkRect rectA = {0, 0, 3, 3};
    constexpr SkRect rectB = {1, 1, 2, 2};
    const std::initializer_list<SkPoint> revBccw = {{1, 2}, {2, 2}, {2, 1}, {1, 1}};
    const std::initializer_list<SkPoint> revBcw  = {{2, 1}, {2, 2}, {1, 2}, {1, 1}};
    for (bool aFirst : {false, true}) {
        for (auto dirA : {SkPathDirection::kCW, SkPathDirection::kCCW}) {
            for (auto dirB : {SkPathDirection::kCW, SkPathDirection::kCCW}) {
                test.reset();
                test.setFillType(SkPathFillType::kEvenOdd);
                if (aFirst) {
                    test.addRect(rectA, dirA);
                    test.addRect(rectB, dirB);
                } else {
                    test.addRect(rectB, dirB);
                    test.addRect(rectA, dirA);
                }
                SkPath original = test;
                REPORTER_ASSERT(reporter, AsWinding(test, &result));
                REPORTER_ASSERT(reporter, result.getFillType() == SkPathFillType::kWinding);
                test.reset();
                if (aFirst) {
                    test.addRect(rectA, dirA);
                }
                if (dirA != dirB) {
                    test.addRect(rectB, dirB);
                } else {
                    test.addPoly(SkPathDirection::kCW == dirA ? revBccw : revBcw, true);
                }
                if (!aFirst) {
                    test.addRect(rectA, dirA);
                }
                REPORTER_ASSERT(reporter, test == result);
                // test that result may be input
                REPORTER_ASSERT(reporter, AsWinding(original, &original));
                REPORTER_ASSERT(reporter, original.getFillType() == SkPathFillType::kWinding);
                REPORTER_ASSERT(reporter, original == result);
            }
        }
    }
    // Test curve types with donuts. Create a donut with outer and hole in all directions.
    // After converting to winding, all donuts should have a hole in the middle.
    for (bool aFirst : {false, true}) {
        for (auto dirA : {SkPathDirection::kCW, SkPathDirection::kCCW}) {
            for (auto dirB : {SkPathDirection::kCW, SkPathDirection::kCCW}) {
                for (auto curveA : { SkPath::kLine_Verb, SkPath::kQuad_Verb,
                                     SkPath::kConic_Verb, SkPath::kCubic_Verb } ) {
                    SkPath pathA = build_squircle(curveA, rectA, dirA);
                    for (auto curveB : { SkPath::kLine_Verb, SkPath::kQuad_Verb,
                                     SkPath::kConic_Verb, SkPath::kCubic_Verb } ) {
                        test = aFirst ? pathA : SkPath();
                        test.addPath(build_squircle(curveB, rectB, dirB));
                        if (!aFirst) {
                            test.addPath(pathA);
                        }
                        test.setFillType(SkPathFillType::kEvenOdd);
                        REPORTER_ASSERT(reporter, AsWinding(test, &result));
                       REPORTER_ASSERT(reporter, result.getFillType() == SkPathFillType::kWinding);
                        for (SkScalar x = rectA.fLeft - 1; x <= rectA.fRight + 1; ++x) {
                            for (SkScalar y = rectA.fTop - 1; y <= rectA.fBottom + 1; ++y) {
                                bool evenOddContains = test.contains(x, y);
                                bool windingContains = result.contains(x, y);
                                REPORTER_ASSERT(reporter, evenOddContains == windingContains);
                            }
                        }
                    }
                }
            }
        }
    }
}
