#include "Test/Tester.h"
#include "Feature/FoldingRange.h"

namespace clice::testing {

namespace {

suite<"FoldingRange"> folding_range = [] {
    Tester tester;
    feature::FoldingRanges ranges;

    auto run = [&](llvm::StringRef code) {
        tester.clear();
        tester.add_main("main.cpp", code);
        tester.compile_with_pch();
        ranges = feature::folding_ranges(*tester.unit);
    };

    auto expect_folding = [&](std::uint32_t index,
                              llvm::StringRef begin,
                              llvm::StringRef end,
                              feature::FoldingRangeKind kind,
                              std::source_location loc = std::source_location::current()) {
        auto& folding = ranges[index];
        auto begin_point = tester.point(begin, "main.cpp");
        auto end_point = tester.point(end, "main.cpp");
        expect(that % folding.range.begin == begin_point, loc);
        expect(that % folding.range.end == end_point, loc);

        /// FIXME: folding.kind is not implemented
        /// expect(that % folding.kind.value() == kind.value(), loc);
    };

    using enum feature::FoldingRangeKind::Kind;

    test("Namespace") = [&] {
        run(R"cpp(
namespace single_line { }

namespace with_nodes $(1){
    struct inner $(3){
        int x;
    }$(4);
}$(2)

namespace strange
                 $(5){

                 }$(6)

#define NS_BEGIN namespace ns {
#define NS_END }

$(7)NS_BEGIN
NS_END$(8)
            )cpp");

        expect(that % ranges.size() == 4);
        expect_folding(0, "1", "2", Namespace);
        expect_folding(1, "3", "4", Namespace);
        expect_folding(2, "5", "6", Namespace);
        expect_folding(3, "7", "8", Namespace);
    };

    test("Enum") = [&] {
        run(R"cpp(
enum e1 $(1){
    A,
    B,
    C
}$(2);

enum class e2 $(3){
    A,
    B,
    C
}$(4);

enum e3 { D };

)cpp");

        expect(that % ranges.size() == 2);
        expect_folding(0, "1", "2", Enum);
        expect_folding(1, "3", "4", Enum);
    };

    test("Record") = [&] {
        run(R"cpp(
struct s1 $(1){
    int x;
    float y;
}$(2);

struct s2 {};

struct s3;

union u1 $(3){
    int x;
    float y;
}$(4);

struct u2 $(5){
    struct s4 $(7){

    }$(8);
}$(6);

void foo() $(9){
    struct s5 $(11){

    }$(12);
}$(10)
)cpp");

        expect(that % ranges.size() == 6);
        expect_folding(0, "1", "2", Struct);
        expect_folding(1, "3", "4", Union);
        expect_folding(2, "5", "6", Struct);
        expect_folding(3, "7", "8", Struct);
        expect_folding(4, "9", "10", FunctionBody);
        expect_folding(5, "11", "12", Struct);
    };

    test("Method") = [&] {
        run(R"cpp(
struct s2 $(1){
    int x;
    float y;

    s2() = default;
}$(2);

struct s3;

struct s3 $(3){
    void method() $(5){
        int x = 0;
    }$(6)

    void parameter() $(7){

    }$(8)

    void skip() {};
}$(4);
)cpp");

        expect(that % ranges.size() == 4);
        expect_folding(0, "1", "2", Struct);
        expect_folding(1, "3", "4", Struct);
        expect_folding(2, "5", "6", FunctionBody);
        expect_folding(3, "7", "8", FunctionBody);
    };

    test("Lambda") = [&] {
        run(R"cpp(
auto z = $(1)[
    x = 0, y = 1
]$(2) () $(3){

}$(4);

static int array[4];

auto s = $(5)[
    x=0,
    y = 1,
    z = array[
    0],
    k = -1
]$(6) () $(7){
    return;
}$(8);

auto l1 = [] () {};

auto l2 = [] () $(9){

}$(10);

auto l3 = [] () $(11){
    return 0;
}$(12);

auto l4 = [] $(13)(
    int x1,
    int x2
)$(14) {};
)cpp");

        expect(that % ranges.size() == 7);
        expect_folding(0, "1", "2", LambdaCapture);
        expect_folding(1, "3", "4", FunctionBody);
        expect_folding(2, "5", "6", LambdaCapture);
        expect_folding(3, "7", "8", FunctionBody);
        expect_folding(4, "9", "10", FunctionBody);
        expect_folding(5, "11", "12", FunctionBody);
        expect_folding(6, "13", "14", FunctionBody);
    };

    test("Function") = [&] {
        run(R"cpp(
void e() {};

void f $(1)(


)$(2) $(3){

}$(4)

void g $(5)(
    int x,
    int y = 2
)$(6) $(7){
    int z;
}$(8)

void h() $(9){
    int x = 0;
}$(10)

void i(  ) {   };

void j $(11)(
    int p1,
    int p2,
    ...
)$(12);

void k() $(13){

}$(14)
)cpp");

        expect(that % ranges.size() == 7);
        expect_folding(0, "1", "2", FunctionParams);
        expect_folding(1, "3", "4", FunctionBody);
        expect_folding(2, "5", "6", FunctionParams);
        expect_folding(3, "7", "8", FunctionBody);
        expect_folding(4, "9", "10", FunctionBody);
        expect_folding(5, "11", "12", FunctionParams);
        expect_folding(6, "13", "14", FunctionBody);
    };

    test("FunctionCall") = [&] {
        run(R"cpp(
int f(int p1, int p2, int p3, int p4, int p5, int p6) { return p1 + p2; }

int main() $(1){
    int x = f(1, 2, 3, 4, 5, 6);

    int y = f $(2)(
        1, 2, 3,
        4, 5, 6
    )$(3);

    return f $(4)(
        1, 2, 3,
        4, 5, 6
    )$(5);
}$(6)
)cpp");

        expect(that % ranges.size() == 3);
        expect_folding(0, "1", "6", FunctionBody);
        expect_folding(1, "2", "3", FunctionCall);
        expect_folding(2, "4", "5", FunctionCall);
    };

    test("CompoundStmt") = [&] {
        run(R"cpp(
int main() $(1){

    $(3){
        $(5){
            //
        }$(6)

        $(7){
            //
        }$(8)

        //
    }$(4)

    return 0;
}$(2)

)cpp");
    };

    test("InitializeList") = [&] {
        run(R"cpp(
struct L { int xs[4]; };

L l1 = $(1){
    1, 2, 3, 4
}$(2);

L l2 = $(3){
//
//
}$(4);

)cpp");

        expect(that % ranges.size() == 2);
        expect_folding(0, "1", "2", Initializer);
        expect_folding(1, "3", "4", Initializer);
    };

    test("AccessSpecifier") = [&] {
        run(R"cpp(
class c1 $(1){
public$(3):
private$(4):
protected$(5):
}$(2);

class c2 $(6){
public$(8):
    int x;

private$(9):
    float y;

protected$(10):
    double z;
}$(7);

#define PUBLIC public:
#define PRIVATE private:
#define PROTECTED protected:

class c3 $(11){
$(13)PUBLIC
    int a;
$(15)PRIVATE$(14)
    int b;
$(17)PROTECTED$(16)
    int c;
}$(12);
)cpp");

        expect_folding(0, "1", "2", Class);
        expect_folding(1, "3", "4", AccessSpecifier);
        expect_folding(2, "4", "5", AccessSpecifier);
        expect_folding(3, "5", "2", AccessSpecifier);

        expect_folding(4, "6", "7", Class);
        expect_folding(5, "8", "9", AccessSpecifier);
        expect_folding(6, "9", "10", AccessSpecifier);
        expect_folding(7, "10", "7", AccessSpecifier);

        expect_folding(8, "11", "12", Class);
        expect_folding(9, "13", "14", AccessSpecifier);
        expect_folding(10, "15", "16", AccessSpecifier);
        expect_folding(11, "17", "12", AccessSpecifier);
    };

    test("Directive") = [&] {
        run(R"cpp(
#ifdef M1

#else

    #ifdef M2


    #endif

#endif
)cpp");
    };

    test("PragmaRegion") = [&] {
        run(R"cpp(
$(1)#pragma region level1
    $(2)#pragma region level2
        $(3)#pragma region level3

        #$(4)pragma endregion level3


    #$(5)pragma endregion level2


#$(6)pragma endregion level1

#pragma endregion   // mismatch region, skipped
#pragma region  // mismatch region, skipped
)cpp");

        /// FIXME: PCH make pragma region not work
        /// expect(that % ranges.size() == 3);
        /// expect_folding(0, "1", "6", Region);
        /// expect_folding(1, "2", "5", Region);
        /// expect_folding(2, "3", "4", Region);
    };
};

}  // namespace

}  // namespace clice::testing
