//===-- SemanticSelectionTests.cpp  ----------------*- C++ -*--------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

#include "Annotations.h"
#include "ClangdServer.h"
#include "Protocol.h"
#include "SemanticSelection.h"
#include "SyncAPI.h"
#include "TestFS.h"
#include "TestTU.h"
#include "llvm/ADT/ArrayRef.h"
#include "llvm/Support/Error.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include <vector>

namespace clang {
namespace clangd {
namespace {

using ::testing::ElementsAre;
using ::testing::ElementsAreArray;
using ::testing::UnorderedElementsAreArray;

// front() is SR.range, back() is outermost range.
std::vector<Range> gatherRanges(const SelectionRange &SR) {
  std::vector<Range> Ranges;
  for (const SelectionRange *S = &SR; S; S = S->parent.get())
    Ranges.push_back(S->range);
  return Ranges;
}

std::vector<Range>
gatherFoldingRanges(llvm::ArrayRef<FoldingRange> FoldingRanges) {
  std::vector<Range> Ranges;
  Range NextRange;
  for (const auto &R : FoldingRanges) {
    NextRange.start.line = R.startLine;
    NextRange.start.character = R.startCharacter;
    NextRange.end.line = R.endLine;
    NextRange.end.character = R.endCharacter;
    Ranges.push_back(NextRange);
  }
  return Ranges;
}

TEST(SemanticSelection, All) {
  const char *Tests[] = {
      R"cpp( // Single statement in a function body.
        [[void func() [[{
          [[[[int v = [[1^00]]]];]]
        }]]]]
      )cpp",
      R"cpp( // Expression
        [[void func() [[{
          int a = 1;
          // int v = (10 + 2) * (a + a);
          [[[[int v = [[[[([[[[10^]] + 2]])]] * (a + a)]]]];]]
        }]]]]
      )cpp",
      R"cpp( // Function call.
        int add(int x, int y) { return x + y; }
        [[void callee() [[{
          // int res = add(11, 22);
          [[[[int res = [[add([[1^1]], 22)]]]];]]
        }]]]]
      )cpp",
      R"cpp( // Tricky macros.
        #define MUL ) * (
        [[void func() [[{
          // int var = (4 + 15 MUL 6 + 10);
          [[[[int var = [[[[([[4 + [[1^5]]]] MUL]] 6 + 10)]]]];]]
        }]]]]
       )cpp",
      R"cpp( // Cursor inside a macro.
        #define HASH(x) ((x) % 10)
        [[void func() [[{
          [[[[int a = [[HASH([[[[2^3]] + 34]])]]]];]]
        }]]]]
       )cpp",
      R"cpp( // Cursor on a macro.
        #define HASH(x) ((x) % 10)
        [[void func() [[{
          [[[[int a = [[HA^SH(23)]]]];]]
        }]]]]
       )cpp",
      R"cpp( // Multiple declaration.
        [[void func() [[{
          [[[[int var1, var^2]], var3;]]
        }]]]]
       )cpp",
      R"cpp( // Before comment.
        [[void func() [[{
          int var1 = 1;
          [[[[int var2 = [[[[var1]]^ /*some comment*/ + 41]]]];]]
        }]]]]
       )cpp",
      // Empty file.
      "[[^]]",
      // FIXME: We should get the whole DeclStmt as a range.
      R"cpp( // Single statement in TU.
        [[int v = [[1^00]]]];
      )cpp",
      R"cpp( // Cursor at end of VarDecl.
        [[int v = [[100]]^]];
      )cpp",
      // FIXME: No node found associated to the position.
      R"cpp( // Cursor in between spaces.
        void func() {
          int v = 100 + [[^]]  100;
        }
      )cpp",
      // Structs.
      R"cpp(
        struct AAA { struct BBB { static int ccc(); };};
        [[void func() [[{
          // int x = AAA::BBB::ccc();
          [[[[int x = [[[[AAA::BBB::c^cc]]()]]]];]]
        }]]]]
      )cpp",
      R"cpp(
        struct AAA { struct BBB { static int ccc(); };};
        [[void func() [[{
          // int x = AAA::BBB::ccc();
          [[[[int x = [[[[[[[[[[AA^A]]::]]BBB::]]ccc]]()]]]];]]
        }]]]]
      )cpp",
      R"cpp( // Inside struct.
        struct A { static int a(); };
        [[struct B {
          [[static int b() [[{
            [[return [[[[1^1]] + 2]]]];
          }]]]]
        }]];
      )cpp",
      // Namespaces.
      R"cpp(
        [[namespace nsa {
          [[namespace nsb {
            static int ccc();
            [[void func() [[{
              // int x = nsa::nsb::ccc();
              [[[[int x = [[[[nsa::nsb::cc^c]]()]]]];]]
            }]]]]
          }]]
        }]]
      )cpp",

  };

  for (const char *Test : Tests) {
    auto T = Annotations(Test);
    auto AST = TestTU::withCode(T.code()).build();
    EXPECT_THAT(gatherRanges(llvm::cantFail(getSemanticRanges(AST, T.point()))),
                ElementsAreArray(T.ranges()))
        << Test;
  }
}

TEST(SemanticSelection, RunViaClangdServer) {
  MockFS FS;
  MockCompilationDatabase CDB;
  ClangdServer Server(CDB, FS, ClangdServer::optsForTest());

  auto FooH = testPath("foo.h");
  FS.Files[FooH] = R"cpp(
    int foo(int x);
    #define HASH(x) ((x) % 10)
  )cpp";

  auto FooCpp = testPath("Foo.cpp");
  const char *SourceContents = R"cpp(
  #include "foo.h"
  [[void bar(int& inp) [[{
    // inp = HASH(foo(inp));
    [[inp = [[HASH([[foo([[in^p]])]])]]]];
  }]]]]
  $empty[[^]]
  )cpp";
  Annotations SourceAnnotations(SourceContents);
  FS.Files[FooCpp] = std::string(SourceAnnotations.code());
  Server.addDocument(FooCpp, SourceAnnotations.code());

  auto Ranges = runSemanticRanges(Server, FooCpp, SourceAnnotations.points());
  ASSERT_TRUE(bool(Ranges))
      << "getSemanticRange returned an error: " << Ranges.takeError();
  ASSERT_EQ(Ranges->size(), SourceAnnotations.points().size());
  EXPECT_THAT(gatherRanges(Ranges->front()),
              ElementsAreArray(SourceAnnotations.ranges()));
  EXPECT_THAT(gatherRanges(Ranges->back()),
              ElementsAre(SourceAnnotations.range("empty")));
}

TEST(FoldingRanges, PseudoParserWithoutLineFoldings) {
  const char *Tests[] = {
      R"cpp(
        #define FOO int foo() {\
          int Variable = 42; \
        }

        // Do not generate folding range for braces within macro expansion.
        FOO

        // Do not generate folding range within macro arguments.
        #define FUNCTOR(functor) functor
        void func() {[[
          FUNCTOR([](){});
        ]]}

        // Do not generate folding range with a brace coming from macro.
        #define LBRACE {
        void bar() LBRACE
          int X = 42;
        }
      )cpp",
      R"cpp(
        void func() {[[
          int Variable = 100;

          if (Variable > 5) {[[
            Variable += 42;
          ]]} else if (Variable++)
            ++Variable;
          else {[[
            Variable--;
          ]]}

          // Do not generate FoldingRange for empty CompoundStmts.
          for (;;) {}

          // If there are newlines between {}, we should generate one.
          for (;;) {[[

          ]]}
        ]]}
      )cpp",
      R"cpp(
        class Foo {[[
        public:
          Foo() {[[
            int X = 1;
          ]]}

        private:
          int getBar() {[[
            return 42;
          ]]}

          // Braces are located at the same line: no folding range here.
          void getFooBar() { }
        ]]};
      )cpp",
      R"cpp(
        // Range boundaries on escaped newlines.
        class Foo \
        \
        {[[  \
        public:
          Foo() {[[\
            int X = 1;
          ]]}   \
        ]]};
      )cpp",
      R"cpp(
        /*[[ Multi
          * line
          *  comment
          ]]*/
      )cpp",
      R"cpp(
        //[[ Comment
        // 1]]

        //[[ Comment
        // 2]]

        // No folding for single line comment.

        /*[[ comment 3
        ]]*/

        /*[[ comment 4
        ]]*/

        /*[[ foo */
        /* bar ]]*/

        /*[[ foo */
        // baz
        /* bar ]]*/

        /*[[ foo */
        /* bar*/
        // baz]]

        //[[ foo
        /* bar */]]
      )cpp",
      R"cpp(
        //Ignore non-conditional directives
        #define A 1

        void func() {[[
          int Variable = 100;

          #ifdef FOO[[
            Variable = 1;
            #if 1[[
                Variable = 4;
            ]]#endif
          ]]#else[[
            Variable = 2;
            //handle nested directives
            #if 1[[
              Variable = 3;
            ]]#endif
          ]]#endif


          ]]}
      )cpp",
      R"cpp(
        int Variable = 0;
        #if defined(WALDO)
            Variable = 1;
        #
      )cpp",
      R"cpp(
        int Variable = 0;
        #if defined(WALDO)[[
            Variable = 1;
        ]]#elif 1[[
            Variable = 2;
        ]]#else
            Variable = 3;
        #
      )cpp",
      R"cpp(
        #pragma region R1[[
        
        #pragma region R2[[
         constexpr int a = 2;
        ]]#pragma endregion
        
        ]]#pragma endregion
      )cpp",
      R"cpp(
        #pragma region[[
        ]]#pragma endregion

        #pragma /*comment1*/ region /*comment2*/name[[
        ]]#pragma endregion
      )cpp",
  };
  for (const char *Test : Tests) {
    auto T = Annotations(Test);
    EXPECT_THAT(gatherFoldingRanges(llvm::cantFail(getFoldingRanges(
                    T.code().str(), /*LineFoldingsOnly=*/false))),
                UnorderedElementsAreArray(T.ranges()))
        << Test;
  }
}

TEST(FoldingRanges, PseudoParserLineFoldingsOnly) {
  const char *Tests[] = {
      R"cpp(
        void func(int a) {[[
            a++;]]
        }
      )cpp",
      R"cpp(
        // Always exclude last line for brackets.
        void func(int a) {[[
          if(a == 1) {[[
            a++;]]
          } else if (a == 2){[[
            a--;]]
          } else {  // No folding for 2 line bracketed ranges.
          }]]
        }
      )cpp",
      R"cpp(
        /*[[ comment
        * comment]]
        */

        /* No folding for this comment.
        */

        // No folding for this comment.

        //[[ 2 single line comment.
        // 2 single line comment.]]

        //[[ >=2 line comments.
        // >=2 line comments.
        // >=2 line comments.]]

        //[[ foo\
        bar\
        baz]]

        /*[[ foo */
        /* bar */]]
        /* baz */

        /*[[ foo */
        /* bar]]
        * This does not fold me */

        //[[ foo
        /* bar */]]
      )cpp",
      R"cpp(
        #pragma region abc[[
        constexpr int a = 2;
        ]]
        #pragma endregion
      )cpp",
      // FIXME: Support folding template arguments.
      // R"cpp(
      // template <[[typename foo, class bar]]> struct baz {};
      // )cpp",

  };
  auto StripColumns = [](const std::vector<Range> &Ranges) {
    std::vector<Range> Res;
    for (Range R : Ranges) {
      R.start.character = R.end.character = 0;
      Res.push_back(R);
    }
    return Res;
  };
  for (const char *Test : Tests) {
    auto T = Annotations(Test);
    EXPECT_THAT(
        StripColumns(gatherFoldingRanges(llvm::cantFail(
            getFoldingRanges(T.code().str(), /*LineFoldingsOnly=*/true)))),
        UnorderedElementsAreArray(StripColumns(T.ranges())))
        << Test;
  }
}
} // namespace
} // namespace clangd
} // namespace clang
