#include "../../lib/Format/Macros.h"
#include "TestLexer.h"
#include "clang/Basic/FileManager.h"

#include "gtest/gtest.h"

namespace clang {
namespace format {

namespace {

class MacroExpanderTest : public ::testing::Test {
public:
  MacroExpanderTest() : Lex(Allocator, Buffers) {}
  std::unique_ptr<MacroExpander>
  create(const std::vector<std::string> &MacroDefinitions) {
    return std::make_unique<MacroExpander>(MacroDefinitions,
                                           Lex.SourceMgr.get(), Lex.Style,
                                           Lex.Allocator, Lex.IdentTable);
  }

  std::string expand(MacroExpander &Macros, llvm::StringRef Name,
                     const std::vector<std::string> &Args = {}) {
    EXPECT_TRUE(Macros.defined(Name));
    return text(Macros.expand(Lex.id(Name), lexArgs(Args)));
  }

  llvm::SmallVector<TokenList, 1>
  lexArgs(const std::vector<std::string> &Args) {
    llvm::SmallVector<TokenList, 1> Result;
    for (const auto &Arg : Args) {
      Result.push_back(uneof(Lex.lex(Arg)));
    }
    return Result;
  }

  struct MacroAttributes {
    clang::tok::TokenKind Kind;
    MacroRole Role;
    unsigned Start;
    unsigned End;
    llvm::SmallVector<FormatToken *, 1> ExpandedFrom;
  };

  void expectAttributes(const TokenList &Tokens,
                        const std::vector<MacroAttributes> &Attributes,
                        const std::string &File, unsigned Line) {
    EXPECT_EQ(Tokens.size(), Attributes.size()) << text(Tokens);
    for (size_t I = 0, E = Tokens.size(); I != E; ++I) {
      if (I >= Attributes.size())
        continue;
      std::string Context =
          ("for token " + llvm::Twine(I) + ": " + Tokens[I]->Tok.getName() +
           " / " + Tokens[I]->TokenText)
              .str();
      EXPECT_TRUE(Tokens[I]->is(Attributes[I].Kind))
          << Context << " in " << text(Tokens) << " at " << File << ":" << Line;
      EXPECT_EQ(Tokens[I]->MacroCtx->Role, Attributes[I].Role)
          << Context << " in " << text(Tokens) << " at " << File << ":" << Line;
      EXPECT_EQ(Tokens[I]->MacroCtx->StartOfExpansion, Attributes[I].Start)
          << Context << " in " << text(Tokens) << " at " << File << ":" << Line;
      EXPECT_EQ(Tokens[I]->MacroCtx->EndOfExpansion, Attributes[I].End)
          << Context << " in " << text(Tokens) << " at " << File << ":" << Line;
      EXPECT_EQ(Tokens[I]->MacroCtx->ExpandedFrom, Attributes[I].ExpandedFrom)
          << Context << " in " << text(Tokens) << " at " << File << ":" << Line;
    }
  }

protected:
  llvm::SpecificBumpPtrAllocator<FormatToken> Allocator;
  std::vector<std::unique_ptr<llvm::MemoryBuffer>> Buffers;
  TestLexer Lex;
};

#define EXPECT_ATTRIBUTES(Tokens, Attributes)                                  \
  expectAttributes(Tokens, Attributes, __FILE__, __LINE__)

TEST_F(MacroExpanderTest, SkipsDefinitionOnError) {
  auto Macros =
      create({"A(", "B(,", "C(a,", "D(a a", "E(a, a", "F(,)", "G(a;"});
  for (const auto *Name : {"A", "B", "C", "D", "E", "F", "G"}) {
    EXPECT_FALSE(Macros->defined(Name)) << "for Name " << Name;
  }
}

TEST_F(MacroExpanderTest, ExpandsWithoutArguments) {
  auto Macros = create({
      "A",
      "B=b",
      "C=c + c",
      "D()",
  });
  EXPECT_TRUE(Macros->objectLike("A"));
  EXPECT_TRUE(Macros->objectLike("B"));
  EXPECT_TRUE(Macros->objectLike("C"));
  EXPECT_TRUE(!Macros->objectLike("D"));
  EXPECT_EQ("", expand(*Macros, "A"));
  EXPECT_EQ("b", expand(*Macros, "B"));
  EXPECT_EQ("c+c", expand(*Macros, "C"));
  EXPECT_EQ("", expand(*Macros, "D"));
}

TEST_F(MacroExpanderTest, ExpandsWithArguments) {
  auto Macros = create({
      "A(x)",
      "B(x, y)=x + y",
  });
  EXPECT_EQ("", expand(*Macros, "A", {"a"}));
  EXPECT_EQ("b1+b2+b3", expand(*Macros, "B", {"b1", "b2 + b3"}));
  EXPECT_EQ("x+", expand(*Macros, "B", {"x"}));
}

TEST_F(MacroExpanderTest, AttributizesTokens) {
  auto Macros = create({
      "A(x, y)={ x + y; }",
      "B(x, y)=x + 3 + y",
  });
  auto *A = Lex.id("A");
  auto AArgs = lexArgs({"a1 * a2", "a3 * a4"});
  auto Result = Macros->expand(A, AArgs);
  EXPECT_EQ(11U, Result.size()) << text(Result) << " / " << Result;
  EXPECT_EQ("{a1*a2+a3*a4;}", text(Result));
  std::vector<MacroAttributes> Attributes = {
      {tok::l_brace, MR_Hidden, 1, 0, {A}},
      {tok::identifier, MR_ExpandedArg, 0, 0, {A}},
      {tok::star, MR_ExpandedArg, 0, 0, {A}},
      {tok::identifier, MR_ExpandedArg, 0, 0, {A}},
      {tok::plus, MR_Hidden, 0, 0, {A}},
      {tok::identifier, MR_ExpandedArg, 0, 0, {A}},
      {tok::star, MR_ExpandedArg, 0, 0, {A}},
      {tok::identifier, MR_ExpandedArg, 0, 0, {A}},
      {tok::semi, MR_Hidden, 0, 0, {A}},
      {tok::r_brace, MR_Hidden, 0, 1, {A}},
      {tok::eof, MR_Hidden, 0, 0, {A}},
  };
  EXPECT_ATTRIBUTES(Result, Attributes);

  auto *B = Lex.id("B");
  auto BArgs = lexArgs({"b1", "b2"});
  Result = Macros->expand(B, BArgs);
  EXPECT_EQ(6U, Result.size()) << text(Result) << " / " << Result;
  EXPECT_EQ("b1+3+b2", text(Result));
  Attributes = {
      {tok::identifier, MR_ExpandedArg, 1, 0, {B}},
      {tok::plus, MR_Hidden, 0, 0, {B}},
      {tok::numeric_constant, MR_Hidden, 0, 0, {B}},
      {tok::plus, MR_Hidden, 0, 0, {B}},
      {tok::identifier, MR_ExpandedArg, 0, 1, {B}},
      {tok::eof, MR_Hidden, 0, 0, {B}},
  };
  EXPECT_ATTRIBUTES(Result, Attributes);
}

TEST_F(MacroExpanderTest, RecursiveExpansion) {
  auto Macros = create({
      "A(x)=x",
      "B(x)=x",
      "C(x)=x",
  });

  auto *A = Lex.id("A");
  auto *B = Lex.id("B");
  auto *C = Lex.id("C");

  auto Args = lexArgs({"id"});
  auto CResult = uneof(Macros->expand(C, Args));
  auto BResult = uneof(Macros->expand(B, CResult));
  auto AResult = uneof(Macros->expand(A, BResult));

  std::vector<MacroAttributes> Attributes = {
      {tok::identifier, MR_ExpandedArg, 3, 3, {C, B, A}},
  };
  EXPECT_ATTRIBUTES(AResult, Attributes);
}

TEST_F(MacroExpanderTest, SingleExpansion) {
  auto Macros = create({"A(x)=x+x"});
  auto *A = Lex.id("A");
  auto Args = lexArgs({"id"});
  auto Result = uneof(Macros->expand(A, Args));
  std::vector<MacroAttributes> Attributes = {
      {tok::identifier, MR_ExpandedArg, 1, 0, {A}},
      {tok::plus, MR_Hidden, 0, 0, {A}},
      {tok::identifier, MR_Hidden, 0, 1, {A}},
  };
  EXPECT_ATTRIBUTES(Result, Attributes);
}

TEST_F(MacroExpanderTest, UnderstandsCppTokens) {
  auto Macros = create({"A(T,name)=T name = 0;"});
  auto *A = Lex.id("A");
  auto Args = lexArgs({"const int", "x"});
  auto Result = uneof(Macros->expand(A, Args));
  std::vector<MacroAttributes> Attributes = {
      {tok::kw_const, MR_ExpandedArg, 1, 0, {A}},
      {tok::kw_int, MR_ExpandedArg, 0, 0, {A}},
      {tok::identifier, MR_ExpandedArg, 0, 0, {A}},
      {tok::equal, MR_Hidden, 0, 0, {A}},
      {tok::numeric_constant, MR_Hidden, 0, 0, {A}},
      {tok::semi, MR_Hidden, 0, 1, {A}},
  };
  EXPECT_ATTRIBUTES(Result, Attributes);
}

} // namespace
} // namespace format
} // namespace clang
