/*
 * 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/SkContourMeasure.h"
#include "include/core/SkPath.h"
#include "include/core/SkPathBuilder.h"
#include "include/core/SkPathMeasure.h"
#include "include/core/SkPathTypes.h"
#include "include/core/SkPoint.h"
#include "include/core/SkRefCnt.h"
#include "include/core/SkScalar.h"
#include "include/core/SkTypes.h"
#include "src/core/SkPathMeasurePriv.h"
#include "tests/Test.h"

#include <array>
#include <cstddef>
#include <initializer_list>
#include <utility>

static void test_small_segment3(skiatest::Reporter* reporter) {
    const SkPoint pts[] = {
        { 0, 0 },
        { 100000000000.0f, 100000000000.0f }, { 0, 0 }, { 10, 10 },
        { 10, 10 }, { 0, 0 }, { 10, 10 }
    };

    SkPathBuilder builder;
    builder.moveTo(pts[0]);
    for (size_t i = 1; i < std::size(pts); i += 3) {
        builder.cubicTo(pts[i], pts[i + 1], pts[i + 2]);
    }

    SkPath path = builder.detach();
    SkPathMeasure meas(path, false);
    meas.getLength();

    // Now check that we cap the segment size even with very large resolution scales.
    // Earlier versions allowed the pathmeasure to recurse without limit in the face
    // of a very large scale.
    //
    //  Before this limit, the above meas had 15K segments, and when built with
    // a resScale of 100, it had 184K segments -- for 1 cubic!
    {
        auto n = SkPathMeasurePriv::CountSegments(meas);
        REPORTER_ASSERT(reporter, n < 300);

        constexpr float resScale = 1000;
        n = SkPathMeasurePriv::CountSegments(SkPathMeasure(path, false, resScale));
        REPORTER_ASSERT(reporter, n < 300);
    }
}

static void test_small_segment2() {
    const SkPoint pts[] = {
        { 0, 0 },
        { 100000000000.0f, 100000000000.0f }, { 0, 0 },
        { 10, 10 }, { 0, 0 },
    };

    SkPathBuilder builder;
    builder.moveTo(pts[0]);
    for (size_t i = 1; i < std::size(pts); i += 2) {
        builder.quadTo(pts[i], pts[i + 1]);
    }
    SkPathMeasure meas(builder.detach(), false);
    meas.getLength();
}

static void test_small_segment() {
    const SkPoint pts[] = {
        { 100000, 100000},
        // big jump between these points, makes a big segment
        { 1.0005f, 0.9999f },
        // tiny (non-zero) jump between these points
        { SK_Scalar1, SK_Scalar1 },
    };

    SkPathMeasure meas(SkPath::Polygon(pts, false), false);

    /*  this would assert (before a fix) because we added a segment with
        the same length as the prev segment, due to the follow (bad) pattern

        d = distance(pts[0], pts[1]);
        distance += d;
        seg->fDistance = distance;

        SkASSERT(d > 0);    // TRUE
        SkASSERT(seg->fDistance > prevSeg->fDistance);  // FALSE

        This 2nd assert failes because (distance += d) didn't affect distance
        because distance >>> d.
     */
    meas.getLength();
}

DEF_TEST(PathMeasure, reporter) {
    SkPath path = SkPathBuilder()
                  .moveTo(0, 0)
                  .lineTo(1, 0)
                  .lineTo(1, 1)
                  .lineTo(0, 1)
                  .detach();

    SkPathMeasure   meas(path, true);
    SkScalar        length = meas.getLength();
    SkASSERT(length == 4);

    path = SkPath::Line({0, 0}, {3, 4});
    meas.setPath(&path, false);
    length = meas.getLength();
    REPORTER_ASSERT(reporter, length == 5);

    path = SkPath::Circle(0, 0, 1);
    meas.setPath(&path, true);
    length = meas.getLength();
//    SkDebugf("circle arc-length = %g\n", length);

    // Test the behavior following a close not followed by a move.
    path = SkPathBuilder()
           .lineTo(1, 0)
           .lineTo(1, 1)
           .lineTo(0, 1)
           .close()
           .lineTo(-1, 0)
           .detach();
    meas.setPath(&path, false);
    length = meas.getLength();
    REPORTER_ASSERT(reporter, length == SK_Scalar1 * 4);
    meas.nextContour();
    length = meas.getLength();
    REPORTER_ASSERT(reporter, length == SK_Scalar1);
    SkPoint position;
    SkVector tangent;
    REPORTER_ASSERT(reporter, meas.getPosTan(SK_ScalarHalf, &position, &tangent));
    REPORTER_ASSERT(reporter,
        SkScalarNearlyEqual(position.fX,
                            -SK_ScalarHalf,
                            0.0001f));
    REPORTER_ASSERT(reporter, position.fY == 0);
    REPORTER_ASSERT(reporter, tangent.fX == -SK_Scalar1);
    REPORTER_ASSERT(reporter, tangent.fY == 0);

    // Test degenerate paths
    path = SkPathBuilder()
           .moveTo(0, 0)
           .lineTo(0, 0)
           .lineTo(1, 0)
           .quadTo(1, 0, 1, 0)
           .quadTo(1, 1, 1, 2)
           .cubicTo(1, 2, 1, 2, 1, 2)
           .cubicTo(2, 2, 3, 2, 4, 2)
           .detach();
    meas.setPath(&path, false);
    length = meas.getLength();
    REPORTER_ASSERT(reporter, length == SK_Scalar1 * 6);
    REPORTER_ASSERT(reporter, meas.getPosTan(SK_ScalarHalf, &position, &tangent));
    REPORTER_ASSERT(reporter,
        SkScalarNearlyEqual(position.fX,
                            SK_ScalarHalf,
                            0.0001f));
    REPORTER_ASSERT(reporter, position.fY == 0);
    REPORTER_ASSERT(reporter, tangent.fX == SK_Scalar1);
    REPORTER_ASSERT(reporter, tangent.fY == 0);
    REPORTER_ASSERT(reporter, meas.getPosTan(2.5f, &position, &tangent));
    REPORTER_ASSERT(reporter,
        SkScalarNearlyEqual(position.fX, SK_Scalar1, 0.0001f));
    REPORTER_ASSERT(reporter,
        SkScalarNearlyEqual(position.fY, 1.5f));
    REPORTER_ASSERT(reporter, tangent.fX == 0);
    REPORTER_ASSERT(reporter, tangent.fY == SK_Scalar1);
    REPORTER_ASSERT(reporter, meas.getPosTan(4.5f, &position, &tangent));
    REPORTER_ASSERT(reporter,
        SkScalarNearlyEqual(position.fX,
                            2.5f,
                            0.0001f));
    REPORTER_ASSERT(reporter,
        SkScalarNearlyEqual(position.fY,
                            2.0f,
                            0.0001f));
    REPORTER_ASSERT(reporter, tangent.fX == SK_Scalar1);
    REPORTER_ASSERT(reporter, tangent.fY == 0);

    path = SkPathBuilder()
           .moveTo(0, 0)
           .lineTo(1, 0)
           .moveTo(1, 1)
           .moveTo(2, 2)
           .lineTo(1, 2)
           .detach();
    meas.setPath(&path, false);
    length = meas.getLength();
    REPORTER_ASSERT(reporter, length == 1);
    REPORTER_ASSERT(reporter, meas.getPosTan(0.5f, &position, &tangent));
    REPORTER_ASSERT(reporter,
        SkScalarNearlyEqual(position.fX,
                            0.5f,
                            0.0001f));
    REPORTER_ASSERT(reporter, position.fY == 0);
    REPORTER_ASSERT(reporter, tangent.fX == 1);
    REPORTER_ASSERT(reporter, tangent.fY == 0);
    meas.nextContour();
    length = meas.getLength();
    REPORTER_ASSERT(reporter, length == 1);
    REPORTER_ASSERT(reporter, meas.getPosTan(0.5f, &position, &tangent));
    REPORTER_ASSERT(reporter,
        SkScalarNearlyEqual(position.fX,
                            1.5f,
                            0.0001f));
    REPORTER_ASSERT(reporter,
        SkScalarNearlyEqual(position.fY,
                            2.0f,
                            0.0001f));
    REPORTER_ASSERT(reporter, tangent.fX == -1);
    REPORTER_ASSERT(reporter, tangent.fY == 0);

    test_small_segment();
    test_small_segment2();
    test_small_segment3(reporter);

    // SkPathMeasure isn't copyable, but it should be move-able
    SkPathMeasure meas2(std::move(meas));
    meas = std::move(meas2);
}

DEF_TEST(PathMeasureConic, reporter) {
    SkPoint stdP, hiP, pts[] = {{0,0}, {100,0}, {100,0}};
    SkPath p = SkPathBuilder()
               .moveTo(0, 0)
               .conicTo(pts[1], pts[2], 1)
               .detach();
    SkPathMeasure stdm(p, false);
    REPORTER_ASSERT(reporter, stdm.getPosTan(20, &stdP, nullptr));
    p = SkPathBuilder()
        .moveTo(0, 0)
        .conicTo(pts[1], pts[2], 10)
        .detach();
    stdm.setPath(&p, false);
    REPORTER_ASSERT(reporter, stdm.getPosTan(20, &hiP, nullptr));
    REPORTER_ASSERT(reporter, 19.5f < stdP.fX && stdP.fX < 20.5f);
    REPORTER_ASSERT(reporter, 19.5f < hiP.fX && hiP.fX < 20.5f);
}

// Regression test for b/26425223
DEF_TEST(PathMeasure_nextctr, reporter) {
    SkPath path = SkPath::Line({0, 0}, {100, 0});

    SkPathMeasure meas(path, false);
    // only expect 1 contour, even if we didn't explicitly call getLength() ourselves
    REPORTER_ASSERT(reporter, !meas.nextContour());
}

static void test_90_degrees(const sk_sp<SkContourMeasure>& cm, SkScalar radius,
                            skiatest::Reporter* reporter) {
    SkPoint pos;
    SkVector tan;
    SkScalar distance = cm->length() / 4;
    bool success = cm->getPosTan(distance, &pos, &tan);

    REPORTER_ASSERT(reporter, success);
    REPORTER_ASSERT(reporter, SkScalarNearlyEqual(pos.fX, 0));
    REPORTER_ASSERT(reporter, SkScalarNearlyEqual(pos.fY, radius));
    REPORTER_ASSERT(reporter, SkScalarNearlyEqual(tan.fX, -1));
    REPORTER_ASSERT(reporter, SkScalarNearlyEqual(tan.fY, 0));
}

static void test_empty_contours(skiatest::Reporter* reporter) {
    SkPath path = SkPathBuilder()
                  .moveTo(0, 0).lineTo(100, 100).lineTo(200, 100)
                  .moveTo(2, 2).moveTo(3, 3)                  // zero-length(s)
                  .moveTo(4, 4).close().close().close()       // zero-length
                  .moveTo(5, 5).lineTo(5, 5)                  // zero-length
                  .moveTo(5, 5).lineTo(5, 5).close()          // zero-length
                  .moveTo(5, 5).lineTo(5, 5).close().close()  // zero-length
                  .moveTo(6, 6).lineTo(7, 7)
                  .moveTo(10, 10)                             // zero-length
                  .detach();

    SkContourMeasureIter fact(path, false);

    // given the above construction, we expect only 2 contours (the rest are "empty")

    REPORTER_ASSERT(reporter, fact.next());
    REPORTER_ASSERT(reporter, fact.next());
    REPORTER_ASSERT(reporter, !fact.next());
}

static void test_MLM_contours(skiatest::Reporter* reporter) {
    // This odd sequence (with a trailing moveTo) used to return a 2nd contour, which is
    // wrong, since the contract for a measure is to only return non-zero length contours.
    SkPath path = SkPathBuilder().moveTo(10, 10).lineTo(20, 20).moveTo(30, 30).detach();

    for (bool forceClosed : {false, true}) {
        SkContourMeasureIter fact(path, forceClosed);
        REPORTER_ASSERT(reporter, fact.next());
        REPORTER_ASSERT(reporter, !fact.next());
    }
}

static void test_shrink(skiatest::Reporter* reporter) {
#ifndef SK_HIDE_PATH_EDIT_METHODS
    SkPath path;
    path.addRect({1, 2, 3, 4});

    SkContourMeasureIter iter(path, false);

    // try to realloc arrays to be sure that the iterator safely made a copy
    path.incReserve(100);

    // Note, this failed (before the fix) on an ASAN build, which notices that we were
    // using an internal iterator of the passed-in path, not our copy.
    while (iter.next())
        ;
#endif
}

DEF_TEST(contour_measure, reporter) {
    SkPath path = SkPathBuilder()
                  .addCircle(0, 0, 100)
                  .addCircle(0, 0, 10)
                  .detach();

    SkContourMeasureIter fact(path, false);

    auto cm0 = fact.next();
    auto cm1 = fact.next();

    REPORTER_ASSERT(reporter, cm0->isClosed());
    REPORTER_ASSERT(reporter, SkScalarNearlyEqual(cm0->length(), 200 * SK_ScalarPI, 1.5f));

    test_90_degrees(cm0, 100, reporter);

    REPORTER_ASSERT(reporter, cm1->isClosed());
    REPORTER_ASSERT(reporter, SkScalarNearlyEqual(cm1->length(), 20 * SK_ScalarPI, 0.5f));

    test_90_degrees(cm1, 10, reporter);

    auto cm2 = fact.next();
    REPORTER_ASSERT(reporter, !cm2);

    test_empty_contours(reporter);
    test_MLM_contours(reporter);

    test_shrink(reporter);
}

DEF_TEST(contour_measure_verbs, reporter) {
    SkPath path = SkPathBuilder()
                  .moveTo(10, 10)
                  .lineTo(10, 30)
                  .lineTo(30, 30)
                  .quadTo({40, 30}, {40, 40})
                  .cubicTo({50, 40}, {50, 50}, {40, 50})
                  .conicTo({50, 50}, {50, 60}, 1.2f)
                  .detach();

    SkContourMeasureIter measure(path, false);

    sk_sp<SkContourMeasure> cmeasure = measure.next();
    REPORTER_ASSERT(reporter, cmeasure);

    SkContourMeasure::ForwardVerbIterator viter = cmeasure->begin();
    {
        REPORTER_ASSERT(reporter, viter != cmeasure->end());
        const auto vmeasure = *viter;
        REPORTER_ASSERT(reporter, vmeasure.fVerb == SkPathVerb::kLine);
        REPORTER_ASSERT(reporter, SkScalarNearlyEqual(vmeasure.fDistance, 20));
        REPORTER_ASSERT(reporter, vmeasure.fPts.size() == 2);
        REPORTER_ASSERT(reporter, vmeasure.fPts[0] == SkPoint::Make(10, 10));
        REPORTER_ASSERT(reporter, vmeasure.fPts[1] == SkPoint::Make(10, 30));
    }

    ++viter;
    {
        REPORTER_ASSERT(reporter, viter != cmeasure->end());
        const auto vmeasure = *viter;
        REPORTER_ASSERT(reporter, vmeasure.fVerb == SkPathVerb::kLine);
        REPORTER_ASSERT(reporter, SkScalarNearlyEqual(vmeasure.fDistance, 40));
        REPORTER_ASSERT(reporter, vmeasure.fPts.size() == 2);
        REPORTER_ASSERT(reporter, vmeasure.fPts[0] == SkPoint::Make(10, 30));
        REPORTER_ASSERT(reporter, vmeasure.fPts[1] == SkPoint::Make(30, 30));
    }

    ++viter;
    {
        REPORTER_ASSERT(reporter, viter != cmeasure->end());
        const auto vmeasure = *viter;
        REPORTER_ASSERT(reporter, vmeasure.fVerb == SkPathVerb::kQuad);
        REPORTER_ASSERT(reporter, SkScalarNearlyEqual(vmeasure.fDistance, 56.127525f));
        REPORTER_ASSERT(reporter, vmeasure.fPts.size() == 3);
        REPORTER_ASSERT(reporter, vmeasure.fPts[0] == SkPoint::Make(30, 30));
        REPORTER_ASSERT(reporter, vmeasure.fPts[1] == SkPoint::Make(40, 30));
        REPORTER_ASSERT(reporter, vmeasure.fPts[2] == SkPoint::Make(40, 40));
    }

    ++viter;
    {
        REPORTER_ASSERT(reporter, viter != cmeasure->end());
        const auto vmeasure = *viter;
        REPORTER_ASSERT(reporter, vmeasure.fVerb == SkPathVerb::kCubic);
        REPORTER_ASSERT(reporter, SkScalarNearlyEqual(vmeasure.fDistance, 76.004692f));
        REPORTER_ASSERT(reporter, vmeasure.fPts.size() == 4);
        REPORTER_ASSERT(reporter, vmeasure.fPts[0] == SkPoint::Make(40, 40));
        REPORTER_ASSERT(reporter, vmeasure.fPts[1] == SkPoint::Make(50, 40));
        REPORTER_ASSERT(reporter, vmeasure.fPts[2] == SkPoint::Make(50, 50));
        REPORTER_ASSERT(reporter, vmeasure.fPts[3] == SkPoint::Make(40, 50));
    }

    ++viter;
    {
        REPORTER_ASSERT(reporter, viter != cmeasure->end());
        const auto vmeasure = *viter;
        REPORTER_ASSERT(reporter, vmeasure.fVerb == SkPathVerb::kConic);
        REPORTER_ASSERT(reporter, SkScalarNearlyEqual(vmeasure.fDistance, 92.428185f));
        REPORTER_ASSERT(reporter, vmeasure.fPts.size() == 4);
        REPORTER_ASSERT(reporter, vmeasure.fPts[0] == SkPoint::Make(40, 50));
        REPORTER_ASSERT(reporter, vmeasure.fPts[1] == SkPoint::Make(1.2f, 0));
        REPORTER_ASSERT(reporter, vmeasure.fPts[2] == SkPoint::Make(50, 50));
        REPORTER_ASSERT(reporter, vmeasure.fPts[3] == SkPoint::Make(50, 60));

        // The last verb distance should also match the contour length.
        REPORTER_ASSERT(reporter, SkScalarNearlyEqual(vmeasure.fDistance, cmeasure->length()));
    }

    ++viter;
    {
        REPORTER_ASSERT(reporter, viter == cmeasure->end());
    }

    // Exercise the range iterator form.
    float current_distance = 0;
    size_t verb_count = 0;
    for (const auto vmeasure : *cmeasure) {
        REPORTER_ASSERT(reporter, vmeasure.fDistance > current_distance);
        current_distance = vmeasure.fDistance;
        verb_count++;
    }
    REPORTER_ASSERT(reporter, verb_count == 5);
}
