//===-- 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 "Matchers.h"
#include "Protocol.h"
#include "SemanticSelection.h"
#include "SourceCode.h"
#include "SyncAPI.h"
#include "TestFS.h"
#include "TestTU.h"
#include "clang/Basic/SourceLocation.h"
#include "clang/Basic/SourceManager.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, All) {
  const char *Tests[] = {
      R"cpp(
        [[int global_variable]];

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

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

          [[void getFooBar() { }]]
        }]];
      )cpp",
  };
  for (const char *Test : Tests) {
    auto T = Annotations(Test);
    auto AST = TestTU::withCode(T.code()).build();
    EXPECT_THAT(gatherFoldingRanges(llvm::cantFail(getFoldingRanges(AST))),
                UnorderedElementsAreArray(T.ranges()))
        << Test;
  }
}

} // namespace
} // namespace clangd
} // namespace clang
