/*
 * 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/SkTypes.h"

#if !defined(SK_BUILD_FOR_GOOGLE3)

#include "include/core/SkPath.h"
#include "include/core/SkPathBuilder.h"
#include "include/core/SkRect.h"
#include "include/pathops/SkPathOps.h"
#include "include/private/base/SkTo.h"
#include "modules/sksg/include/SkSGDraw.h"
#include "modules/sksg/include/SkSGGroup.h"
#include "modules/sksg/include/SkSGInvalidationController.h"
#include "modules/sksg/include/SkSGMerge.h"
#include "modules/sksg/include/SkSGPaint.h"
#include "modules/sksg/include/SkSGPath.h"
#include "modules/sksg/include/SkSGRect.h"
#include "modules/sksg/include/SkSGRenderEffect.h"
#include "modules/sksg/include/SkSGTransform.h"
#include "src/core/SkPathPriv.h"
#include "src/core/SkRectPriv.h"

#include "tests/Test.h"

#include <vector>

static void check_inval(skiatest::Reporter* reporter, const sk_sp<sksg::Node>& root,
                        const SkRect& expected_bounds,
                        const SkRect& expected_inval_bounds,
                        const std::vector<SkRect>* expected_damage) {
    sksg::InvalidationController ic;
    const auto bbox = root->revalidate(&ic, SkMatrix::I());

    if ((false)) {
        SkDebugf("** bbox: [%f %f %f %f], ibbox: [%f %f %f %f]\n",
                 bbox.fLeft, bbox.fTop, bbox.fRight, bbox.fBottom,
                 ic.bounds().left(), ic.bounds().top(), ic.bounds().right(), ic.bounds().bottom());
    }

    REPORTER_ASSERT(reporter, bbox == expected_bounds);
    REPORTER_ASSERT(reporter, ic.bounds() == expected_inval_bounds);

    if (expected_damage) {
        const auto damage_count = SkTo<size_t>(ic.end() - ic.begin());
        REPORTER_ASSERT(reporter, expected_damage->size() == damage_count);
        for (size_t i = 0; i < std::min(expected_damage->size(), damage_count); ++i) {
            const auto r1 = (*expected_damage)[i],
                       r2 = ic.begin()[i];
            if ((false)) {
                SkDebugf("*** expected inval: [%f %f %f %f], actual: [%f %f %f %f]\n",
                         r1.left(), r1.top(), r1.right(), r1.bottom(),
                         r2.left(), r2.top(), r2.right(), r2.bottom());
            }
            REPORTER_ASSERT(reporter, r1 == r2);
        }
    }
}

struct HitTest {
    const SkPoint           pt;
    sk_sp<sksg::RenderNode> node;
};

static void check_hittest(skiatest::Reporter* reporter, const sk_sp<sksg::RenderNode>& root,
                          const std::vector<HitTest>& tests) {
    for (const auto& tst : tests) {
        const auto* node = root->nodeAt(tst.pt);
        if (node != tst.node.get()) {
            SkDebugf("*** nodeAt(%f, %f) - expected %p, got %p\n",
                     tst.pt.x(), tst.pt.y(), tst.node.get(), node);
        }
        REPORTER_ASSERT(reporter, tst.node.get() == node);
    }
}

static void inval_test1(skiatest::Reporter* reporter) {
    auto color  = sksg::Color::Make(0xff000000);
    auto r1     = sksg::Rect::Make(SkRect::MakeWH(100, 100)),
         r2     = sksg::Rect::Make(SkRect::MakeWH(100, 100));
    auto grp    = sksg::Group::Make();
    auto matrix = sksg::Matrix<SkMatrix>::Make(SkMatrix::I());
    auto root   = sksg::TransformEffect::Make(grp, matrix);
    auto d1     = sksg::Draw::Make(r1, color),
         d2     = sksg::Draw::Make(r2, color);

    grp->addChild(d1);
    grp->addChild(d2);

    {
        // Initial revalidation.
        check_inval(reporter, root,
                    SkRect::MakeWH(100, 100),
                    SkRectPriv::MakeLargeS32(),
                    nullptr);

        check_hittest(reporter, root, {
                          {{  -1,   0 }, nullptr },
                          {{   0,  -1 }, nullptr },
                          {{ 100,   0 }, nullptr },
                          {{   0, 100 }, nullptr },
                          {{   0,   0 },      d2 },
                          {{  99,  99 },      d2 },
                      });
    }

    {
        // Move r2 to (200 100).
        r2->setL(200); r2->setT(100); r2->setR(300); r2->setB(200);
        std::vector<SkRect> damage = { {0, 0, 100, 100}, { 200, 100, 300, 200} };
        check_inval(reporter, root,
                    SkRect::MakeWH(300, 200),
                    SkRect::MakeWH(300, 200),
                    &damage);

        check_hittest(reporter, root, {
                          {{  -1,   0 }, nullptr },
                          {{   0,  -1 }, nullptr },
                          {{ 100,   0 }, nullptr },
                          {{   0, 100 }, nullptr },
                          {{   0,   0 },      d1 },
                          {{  99,  99 },      d1 },

                          {{ 199, 100 }, nullptr },
                          {{ 200,  99 }, nullptr },
                          {{ 300, 100 }, nullptr },
                          {{ 200, 200 }, nullptr },
                          {{ 200, 100 },      d2 },
                          {{ 299, 199 },      d2 },
                      });
    }

    {
        // Update the common color.
        color->setColor(0xffff0000);
        std::vector<SkRect> damage = { {0, 0, 100, 100}, { 200, 100, 300, 200} };
        check_inval(reporter, root,
                    SkRect::MakeWH(300, 200),
                    SkRect::MakeWH(300, 200),
                    &damage);
    }

    {
        // Shrink r1.
        r1->setR(50);
        std::vector<SkRect> damage = { {0, 0, 100, 100}, { 0, 0, 50, 100} };
        check_inval(reporter, root,
                    SkRect::MakeWH(300, 200),
                    SkRect::MakeWH(100, 100),
                    &damage);

        check_hittest(reporter, root, {
                          {{  -1,   0 }, nullptr },
                          {{   0,  -1 }, nullptr },
                          {{  50,   0 }, nullptr },
                          {{   0, 100 }, nullptr },
                          {{   0,   0 },      d1 },
                          {{  49,  99 },      d1 },

                          {{ 199, 100 }, nullptr },
                          {{ 200,  99 }, nullptr },
                          {{ 300, 100 }, nullptr },
                          {{ 200, 200 }, nullptr },
                          {{ 200, 100 },      d2 },
                          {{ 299, 199 },      d2 },
                      });
    }

    {
        // Update transform.
        matrix->setMatrix(SkMatrix::Scale(2, 2));
        std::vector<SkRect> damage = { {0, 0, 300, 200}, { 0, 0, 600, 400} };
        check_inval(reporter, root,
                    SkRect::MakeWH(600, 400),
                    SkRect::MakeWH(600, 400),
                    &damage);

        check_hittest(reporter, root, {
                          {{  -1,   0 }, nullptr },
                          {{   0,  -1 }, nullptr },
                          {{  25,   0 }, nullptr },
                          {{   0,  50 }, nullptr },
                          {{   0,   0 },      d1 },
                          {{  24,  49 },      d1 },

                          {{  99,  50 }, nullptr },
                          {{ 100,  49 }, nullptr },
                          {{ 150,  50 }, nullptr },
                          {{ 100, 100 }, nullptr },
                          {{ 100,  50 },      d2 },
                          {{ 149,  99 },      d2 },
                      });
    }

    {
        // Shrink r2 under transform.
        r2->setR(250);
        std::vector<SkRect> damage = { {400, 200, 600, 400}, { 400, 200, 500, 400} };
        check_inval(reporter, root,
                    SkRect::MakeWH(500, 400),
                    SkRect::MakeLTRB(400, 200, 600, 400),
                    &damage);

        check_hittest(reporter, root, {
                          {{  -1,   0 }, nullptr },
                          {{   0,  -1 }, nullptr },
                          {{  25,   0 }, nullptr },
                          {{   0,  50 }, nullptr },
                          {{   0,   0 },      d1 },
                          {{  24,  49 },      d1 },

                          {{  99,  50 }, nullptr },
                          {{ 100,  49 }, nullptr },
                          {{ 125,  50 }, nullptr },
                          {{ 100, 100 }, nullptr },
                          {{ 100,  50 },      d2 },
                          {{ 124,  99 },      d2 },
                      });
    }
}

static void inval_test2(skiatest::Reporter* reporter) {
    auto color = sksg::Color::Make(0xff000000);
    auto rect  = sksg::Rect::Make(SkRect::MakeWH(100, 100));
    auto m1    = sksg::Matrix<SkMatrix>::Make(SkMatrix::I()),
         m2    = sksg::Matrix<SkMatrix>::Make(SkMatrix::I());
    auto t1    = sksg::TransformEffect::Make(sksg::Draw::Make(rect, color),
                                             sksg::Transform::MakeConcat(m1, m2)),
         t2    = sksg::TransformEffect::Make(sksg::Draw::Make(rect, color), m1);
    auto root  = sksg::Group::Make();
    root->addChild(t1);
    root->addChild(t2);

    {
        // Initial revalidation.
        check_inval(reporter, root,
                    SkRect::MakeWH(100, 100),
                    SkRectPriv::MakeLargeS32(),
                    nullptr);
    }

    {
        // Update the shared color.
        color->setColor(0xffff0000);
        std::vector<SkRect> damage = { {0, 0, 100, 100}, { 0, 0, 100, 100} };
        check_inval(reporter, root,
                    SkRect::MakeWH(100, 100),
                    SkRect::MakeWH(100, 100),
                    &damage);
    }

    {
        // Update m2.
        m2->setMatrix(SkMatrix::Scale(2, 2));
        std::vector<SkRect> damage = { {0, 0, 100, 100}, { 0, 0, 200, 200} };
        check_inval(reporter, root,
                    SkRect::MakeWH(200, 200),
                    SkRect::MakeWH(200, 200),
                    &damage);
    }

    {
        // Update shared m1.
        m1->setMatrix(SkMatrix::Translate(100, 100));
        std::vector<SkRect> damage = { {   0,   0, 200, 200},   // draw1 prev bounds
                                       { 100, 100, 300, 300},   // draw1 new bounds
                                       {   0,   0, 100, 100},   // draw2 prev bounds
                                       { 100, 100, 200, 200} }; // draw2 new bounds
        check_inval(reporter, root,
                    SkRect::MakeLTRB(100, 100, 300, 300),
                    SkRect::MakeLTRB(  0,   0, 300, 300),
                    &damage);
    }

    {
        // Update shared rect.
        rect->setR(50);
        std::vector<SkRect> damage = { { 100, 100, 300, 300},   // draw1 prev bounds
                                       { 100, 100, 200, 300},   // draw1 new bounds
                                       { 100, 100, 200, 200},   // draw2 prev bounds
                                       { 100, 100, 150, 200} }; // draw2 new bounds
        check_inval(reporter, root,
                    SkRect::MakeLTRB(100, 100, 200, 300),
                    SkRect::MakeLTRB(100, 100, 300, 300),
                    &damage);
    }
}

static void inval_test3(skiatest::Reporter* reporter) {
    auto color1 = sksg::Color::Make(0xff000000),
         color2 = sksg::Color::Make(0xff000000);
    auto group  = sksg::Group::Make();

    group->addChild(sksg::Draw::Make(sksg::Rect::Make(SkRect::MakeWH(100, 100)),
                                     color1));
    group->addChild(sksg::Draw::Make(sksg::Rect::Make(SkRect::MakeXYWH(200, 0, 100, 100)),
                                     color2));
    auto filter = sksg::DropShadowImageFilter::Make();
    filter->setOffset({50, 75});
    auto root = sksg::ImageFilterEffect::Make(group, filter);

    {
        // Initial revalidation.
        check_inval(reporter, root,
                    SkRect::MakeXYWH(0, 0, 350, 175),
                    SkRectPriv::MakeLargeS32(),
                    nullptr);
    }

    {
        // Shadow-only.
        filter->setMode(sksg::DropShadowImageFilter::Mode::kShadowOnly);
        std::vector<SkRect> damage = { {0, 0, 350, 175}, { 50, 75, 350, 175} };
        check_inval(reporter, root,
                    SkRect::MakeLTRB(50, 75, 350, 175),
                    SkRect::MakeLTRB(0, 0, 350, 175),
                    &damage);
    }

    {
        // Content change -> single/full filter bounds inval.
        color1->setColor(0xffff0000);
        std::vector<SkRect> damage = { { 50, 75, 350, 175} };
        check_inval(reporter, root,
                    SkRect::MakeLTRB(50, 75, 350, 175),
                    SkRect::MakeLTRB(50, 75, 350, 175),
                    &damage);
    }

    {
        // Visibility change -> full inval.
        group->setVisible(false);
        std::vector<SkRect> damage = { { 50, 75, 350, 175} };
        check_inval(reporter, root,
                    SkRect::MakeLTRB(50, 75, 350, 175),
                    SkRect::MakeLTRB(50, 75, 350, 175),
                    &damage);
    }
}

static void inval_group_remove(skiatest::Reporter* reporter) {
    auto draw = sksg::Draw::Make(sksg::Rect::Make(SkRect::MakeWH(100, 100)),
                                 sksg::Color::Make(SK_ColorBLACK));
    auto grp = sksg::Group::Make();

    // Readding the child should not trigger asserts.
    grp->addChild(draw);
    grp->removeChild(draw);
    grp->addChild(draw);
}

DEF_TEST(SGInvalidation, reporter) {
    inval_test1(reporter);
    inval_test2(reporter);
    inval_test3(reporter);
    inval_group_remove(reporter);
}

// Helper to stringify a verb.
static const char* verb_to_string(SkPath::Verb verb) {
    switch (verb) {
        case SkPath::kMove_Verb:
            return "Move";
        case SkPath::kLine_Verb:
            return "Line";
        case SkPath::kQuad_Verb:
            return "Quad";
        case SkPath::kConic_Verb:
            return "Conic";
        case SkPath::kCubic_Verb:
            return "Cubic";
        case SkPath::kClose_Verb:
            return "Close";
        case SkPath::kDone_Verb:
            return "Done";
    }
    SkUNREACHABLE;
}

static void assert_paths_equal(skiatest::Reporter* reporter, const SkPath& a, const SkPath& b) {
    if (a.getFillType() != b.getFillType()) {
        ERRORF(reporter,
               "Paths differ in FillType. Expected %d, got %d.",
               (int)a.getFillType(),
               (int)b.getFillType());
        a.dump();
        b.dump();
        return;
    }
    if (SkPathPriv::GetConvexity(a) != SkPathPriv::GetConvexity(b)) {
        ERRORF(reporter,
               "Paths differ in Convexity. Expected %d, got %d.",
               (int)SkPathPriv::GetConvexity(a),
               (int)SkPathPriv::GetConvexity(b));
        return;
    }

    SkPath::RawIter iterA(a);
    SkPath::RawIter iterB(b);

    SkPoint ptsA[4], ptsB[4];
    int verbIndex = 0;

    for (;;) {
        SkPath::Verb verbA = iterA.next(ptsA);
        SkPath::Verb verbB = iterB.next(ptsB);

        if (verbA != verbB) {
            ERRORF(reporter,
                   "Paths differ at verb index %d. Expected %s, got %s.",
                   verbIndex,
                   verb_to_string(verbA),
                   verb_to_string(verbB));
            a.dump();
            b.dump();
            return;
        }

        if (verbA == SkPath::kDone_Verb) {
            break;
        }

        const int numPts = SkPathPriv::PtsInIter(verbA);
        for (int i = 0; i < numPts; ++i) {
            if (ptsA[i] != ptsB[i]) {
                ERRORF(reporter,
                       "Paths differ at verb index %d (%s), point %d. "
                       "Expected (%f, %f), got (%f, %f).",
                       verbIndex,
                       verb_to_string(verbA),
                       i,
                       ptsA[i].fX,
                       ptsA[i].fY,
                       ptsB[i].fX,
                       ptsB[i].fY);
                a.dump();
                b.dump();
                return;
            }
        }

        if (verbA == SkPath::kConic_Verb) {
            const float weightA = iterA.conicWeight();
            const float weightB = iterB.conicWeight();
            if (weightA != weightB) {
                ERRORF(reporter,
                       "Paths differ at verb index %d (Conic), weight. "
                       "Expected %f, got %f.",
                       verbIndex,
                       weightA,
                       weightB);
                a.dump();
                b.dump();
                return;
            }
        }

        verbIndex++;
    }
}

static void test_merge(skiatest::Reporter* reporter,
                       const char* name,
                       std::vector<sksg::Merge::Rec>&& recs,
                       const SkPath& expected) {
    skiatest::ReporterContext rc(reporter, name);
    auto merge = sksg::Merge::Make(std::move(recs));
    sksg::InvalidationController ic;
    merge->revalidate(&ic, SkMatrix::I());
    assert_paths_equal(reporter, merge->asPath(), expected);
}

DEF_TEST(SGMerge, reporter) {
    const auto square = sksg::Rect::Make(SkRect::MakeXYWH(0, 0, 100, 100));
    const auto rect = sksg::Path::Make(SkPath::Rect(SkRect::MakeXYWH(50, 50, 100, 150)));
    const auto window = sksg::Path::Make(SkPath::Rect(SkRect::MakeXYWH(20, 30, 5, 60)));

    {
        const SkPath expected = SkPathBuilder()
                                        .moveTo(0, 0)
                                        .lineTo(100, 0)
                                        .lineTo(100, 100)
                                        .lineTo(0, 100)
                                        .close()
                                        .moveTo(50, 50)
                                        .lineTo(150, 50)
                                        .lineTo(150, 200)
                                        .lineTo(50, 200)
                                        .close()
                                        .detach();
        test_merge(reporter,
                   "Merge combines contours",
                   {{square, sksg::Merge::Mode::kMerge}, {rect, sksg::Merge::Mode::kMerge}},
                   expected);
    }

    {
        const SkPath expected = SkPathBuilder()
                                        .setFillType(SkPathFillType::kEvenOdd)
                                        .moveTo(100, 0)
                                        .lineTo(0, 0)
                                        .lineTo(0, 100)
                                        .lineTo(50, 100)
                                        .lineTo(50, 200)
                                        .lineTo(150, 200)
                                        .lineTo(150, 50)
                                        .lineTo(100, 50)
                                        .lineTo(100, 0)
                                        .close()
                                        .detach();
        test_merge(reporter,
                   "Union uses pathops",
                   {{square, sksg::Merge::Mode::kUnion}, {rect, sksg::Merge::Mode::kUnion}},
                   expected);
    }

    {
        const SkPath expected = SkPathBuilder()
                                        .setFillType(SkPathFillType::kEvenOdd)
                                        .moveTo(50, 50)
                                        .lineTo(100, 50)
                                        .lineTo(100, 100)
                                        .lineTo(50, 100)
                                        .close()
                                        .detach();
        test_merge(reporter,
                   "Intersect uses pathops",
                   {{square, sksg::Merge::Mode::kUnion},  // first op is ignored
                    {rect, sksg::Merge::Mode::kIntersect}},
                   expected);
    }

    {
        const SkPath expected = SkPathBuilder()
                                        .setFillType(SkPathFillType::kEvenOdd)
                                        .moveTo(100, 0)
                                        .lineTo(0, 0)
                                        .lineTo(0, 100)
                                        .lineTo(100, 100)
                                        .lineTo(100, 0)
                                        .close()
                                        .moveTo(25, 30)
                                        .lineTo(20, 30)
                                        .lineTo(20, 90)
                                        .lineTo(25, 90)
                                        .lineTo(25, 30)
                                        .close()
                                        .detach();
        test_merge(reporter,
                   "Difference uses pathops",
                   {{square, sksg::Merge::Mode::kUnion},  // first op is ignored
                    {window, sksg::Merge::Mode::kDifference}},
                   expected);
    }
}

#endif // !defined(SK_BUILD_FOR_GOOGLE3)
