#ifndef LLVM_CLANG_UNITTESTS_AST_IMPORTER_FIXTURES_H
#define LLVM_CLANG_UNITTESTS_AST_IMPORTER_FIXTURES_H
#include "gmock/gmock.h"
#include "clang/AST/ASTImporter.h"
#include "clang/AST/ASTImporterSharedState.h"
#include "clang/Frontend/ASTUnit.h"
#include "clang/Testing/CommandLineArgs.h"
#include "llvm/Support/Error.h"
#include "llvm/Support/ErrorHandling.h"
#include "DeclMatcher.h"
#include "MatchVerifier.h"
#include <sstream>
namespace clang {
class ASTImporter;
class ASTImporterSharedState;
class ASTUnit;
namespace ast_matchers {
const StringRef DeclToImportID = "declToImport";
const StringRef DeclToVerifyID = "declToVerify";
void createVirtualFileIfNeeded(ASTUnit *ToAST, StringRef FileName,
                               std::unique_ptr<llvm::MemoryBuffer> &&Buffer);
void createVirtualFileIfNeeded(ASTUnit *ToAST, StringRef FileName,
                               StringRef Code);
class CompilerOptionSpecificTest : public ::testing::Test {
protected:
    virtual std::vector<std::string> getExtraArgs() const { return {}; }
      std::vector<std::string>
  getCommandLineArgsForLanguage(TestLanguage Lang) const {
    std::vector<std::string> Args = getCommandLineArgsForTesting(Lang);
    std::vector<std::string> ExtraArgs = getExtraArgs();
    for (const auto &Arg : ExtraArgs) {
      Args.push_back(Arg);
    }
    return Args;
  }
};
const auto DefaultTestArrayForRunOptions =
    std::array<std::vector<std::string>, 4>{
        {std::vector<std::string>(),
         std::vector<std::string>{"-fdelayed-template-parsing"},
         std::vector<std::string>{"-fms-compatibility"},
         std::vector<std::string>{"-fdelayed-template-parsing",
                                  "-fms-compatibility"}}};
const auto DefaultTestValuesForRunOptions =
    ::testing::ValuesIn(DefaultTestArrayForRunOptions);
class ASTImporterTestBase : public CompilerOptionSpecificTest {
  const char *const InputFileName = "input.cc";
  const char *const OutputFileName = "output.cc";
public:
    typedef std::function<ASTImporter *(
      ASTContext &, FileManager &, ASTContext &, FileManager &, bool,
      const std::shared_ptr<ASTImporterSharedState> &SharedState)>
      ImporterConstructor;
    ASTImporter::ODRHandlingType ODRHandling;
    ImporterConstructor Creator;
private:
    std::string ToCode;
      struct TU {
        std::string Code;
    std::string FileName;
    std::unique_ptr<ASTUnit> Unit;
    TranslationUnitDecl *TUDecl = nullptr;
    std::unique_ptr<ASTImporter> Importer;
    ImporterConstructor Creator;
    ASTImporter::ODRHandlingType ODRHandling;
    TU(StringRef Code, StringRef FileName, std::vector<std::string> Args,
       ImporterConstructor C = ImporterConstructor(),
       ASTImporter::ODRHandlingType ODRHandling =
           ASTImporter::ODRHandlingType::Conservative);
    ~TU();
    void
    lazyInitImporter(const std::shared_ptr<ASTImporterSharedState> &SharedState,
                     ASTUnit *ToAST);
    Decl *import(const std::shared_ptr<ASTImporterSharedState> &SharedState,
                 ASTUnit *ToAST, Decl *FromDecl);
    llvm::Expected<Decl *>
    importOrError(const std::shared_ptr<ASTImporterSharedState> &SharedState,
                  ASTUnit *ToAST, Decl *FromDecl);
    QualType import(const std::shared_ptr<ASTImporterSharedState> &SharedState,
                    ASTUnit *ToAST, QualType FromType);
  };
                std::list<TU> FromTUs;
    void lazyInitSharedState(TranslationUnitDecl *ToTU);
  void lazyInitToAST(TestLanguage ToLang, StringRef ToSrcCode,
                     StringRef FileName);
protected:
  std::shared_ptr<ASTImporterSharedState> SharedStatePtr;
public:
    std::unique_ptr<ASTUnit> ToAST;
    TU *findFromTU(Decl *From);
        std::tuple<Decl *, Decl *>
  getImportedDecl(StringRef FromSrcCode, TestLanguage FromLang,
                  StringRef ToSrcCode, TestLanguage ToLang,
                  StringRef Identifier = DeclToImportID);
        TranslationUnitDecl *getTuDecl(StringRef SrcCode, TestLanguage Lang,
                                 StringRef FileName = "input.cc");
    TranslationUnitDecl *getToTuDecl(StringRef ToSrcCode, TestLanguage ToLang);
        Decl *Import(Decl *From, TestLanguage ToLang);
  template <class DeclT> DeclT *Import(DeclT *From, TestLanguage Lang) {
    return cast_or_null<DeclT>(Import(cast<Decl>(From), Lang));
  }
      llvm::Expected<Decl *> importOrError(Decl *From, TestLanguage ToLang);
  QualType ImportType(QualType FromType, Decl *TUDecl, TestLanguage ToLang);
  ASTImporterTestBase()
      : ODRHandling(ASTImporter::ODRHandlingType::Conservative) {}
  ~ASTImporterTestBase();
};
class ASTImporterOptionSpecificTestBase
    : public ASTImporterTestBase,
      public ::testing::WithParamInterface<std::vector<std::string>> {
protected:
  std::vector<std::string> getExtraArgs() const override { return GetParam(); }
};
class TestImportBase
    : public CompilerOptionSpecificTest,
      public ::testing::WithParamInterface<std::vector<std::string>> {
  template <typename NodeType>
  llvm::Expected<NodeType> importNode(ASTUnit *From, ASTUnit *To,
                                      ASTImporter &Importer, NodeType Node) {
    ASTContext &ToCtx = To->getASTContext();
                StringRef FromFileName = From->getMainFileName();
    createVirtualFileIfNeeded(To, FromFileName,
                              From->getBufferForFile(FromFileName));
    auto Imported = Importer.Import(Node);
    if (Imported) {
                  SmallString<1024> ImportChecker;
      llvm::raw_svector_ostream ToNothing(ImportChecker);
      ToCtx.getTranslationUnitDecl()->print(ToNothing);
                  (*Imported)->dump(ToNothing);
    }
    return Imported;
  }
  template <typename NodeType>
  testing::AssertionResult
  testImport(const std::string &FromCode,
             const std::vector<std::string> &FromArgs,
             const std::string &ToCode, const std::vector<std::string> &ToArgs,
             MatchVerifier<NodeType> &Verifier,
             const internal::BindableMatcher<NodeType> &SearchMatcher,
             const internal::BindableMatcher<NodeType> &VerificationMatcher) {
    const char *const InputFileName = "input.cc";
    const char *const OutputFileName = "output.cc";
    std::unique_ptr<ASTUnit> FromAST = tooling::buildASTFromCodeWithArgs(
                                 FromCode, FromArgs, InputFileName),
                             ToAST = tooling::buildASTFromCodeWithArgs(
                                 ToCode, ToArgs, OutputFileName);
    ASTContext &FromCtx = FromAST->getASTContext(),
               &ToCtx = ToAST->getASTContext();
    ASTImporter Importer(ToCtx, ToAST->getFileManager(), FromCtx,
                         FromAST->getFileManager(), false);
    auto FoundNodes = match(SearchMatcher, FromCtx);
    if (FoundNodes.size() != 1)
      return testing::AssertionFailure()
             << "Multiple potential nodes were found!";
    auto ToImport = selectFirst<NodeType>(DeclToImportID, FoundNodes);
    if (!ToImport)
      return testing::AssertionFailure() << "Node type mismatch!";
            internal::BindableMatcher<NodeType> WrapperMatcher(VerificationMatcher);
    EXPECT_TRUE(Verifier.match(ToImport, WrapperMatcher));
    auto Imported = importNode(FromAST.get(), ToAST.get(), Importer, ToImport);
    if (!Imported) {
      std::string ErrorText;
      handleAllErrors(Imported.takeError(),
                      [&ErrorText](const ASTImportError &Err) {
                        ErrorText = Err.message();
                      });
      return testing::AssertionFailure()
             << "Import failed, error: \"" << ErrorText << "\"!";
    }
    return Verifier.match(*Imported, WrapperMatcher);
  }
  template <typename NodeType>
  testing::AssertionResult
  testImport(const std::string &FromCode,
             const std::vector<std::string> &FromArgs,
             const std::string &ToCode, const std::vector<std::string> &ToArgs,
             MatchVerifier<NodeType> &Verifier,
             const internal::BindableMatcher<NodeType> &VerificationMatcher) {
    return testImport(
        FromCode, FromArgs, ToCode, ToArgs, Verifier,
        translationUnitDecl(
            has(namedDecl(hasName(DeclToImportID)).bind(DeclToImportID))),
        VerificationMatcher);
  }
protected:
  std::vector<std::string> getExtraArgs() const override { return GetParam(); }
public:
        template <typename NodeType, typename MatcherType>
  void testImport(const std::string &FromCode, TestLanguage FromLang,
                  const std::string &ToCode, TestLanguage ToLang,
                  MatchVerifier<NodeType> &Verifier,
                  const MatcherType &AMatcher) {
    std::vector<std::string> FromArgs = getCommandLineArgsForLanguage(FromLang);
    std::vector<std::string> ToArgs = getCommandLineArgsForLanguage(ToLang);
    EXPECT_TRUE(
        testImport(FromCode, FromArgs, ToCode, ToArgs, Verifier, AMatcher));
  }
  struct ImportAction {
    StringRef FromFilename;
    StringRef ToFilename;
        internal::BindableMatcher<Decl> ImportPredicate;
    ImportAction(StringRef FromFilename, StringRef ToFilename,
                 DeclarationMatcher ImportPredicate)
        : FromFilename(FromFilename), ToFilename(ToFilename),
          ImportPredicate(ImportPredicate) {}
    ImportAction(StringRef FromFilename, StringRef ToFilename,
                 const std::string &DeclName)
        : FromFilename(FromFilename), ToFilename(ToFilename),
          ImportPredicate(namedDecl(hasName(DeclName))) {}
  };
  using SingleASTUnit = std::unique_ptr<ASTUnit>;
  using AllASTUnits = llvm::StringMap<SingleASTUnit>;
  struct CodeEntry {
    std::string CodeSample;
    TestLanguage Lang;
  };
  using CodeFiles = llvm::StringMap<CodeEntry>;
    SingleASTUnit createASTUnit(StringRef FileName, const CodeEntry &CE) const {
    std::vector<std::string> Args = getCommandLineArgsForLanguage(CE.Lang);
    auto AST = tooling::buildASTFromCodeWithArgs(CE.CodeSample, Args, FileName);
    EXPECT_TRUE(AST.get());
    return AST;
  }
                              void testImportSequence(const CodeFiles &CodeSamples,
                          const std::vector<ImportAction> &ImportActions,
                          StringRef FileForFinalCheck,
                          internal::BindableMatcher<Decl> FinalSelectPredicate,
                          internal::BindableMatcher<Decl> VerificationMatcher) {
    AllASTUnits AllASTs;
    using ImporterKey = std::pair<const ASTUnit *, const ASTUnit *>;
    llvm::DenseMap<ImporterKey, std::unique_ptr<ASTImporter>> Importers;
    auto GenASTsIfNeeded = [this, &AllASTs, &CodeSamples](StringRef Filename) {
      if (!AllASTs.count(Filename)) {
        auto Found = CodeSamples.find(Filename);
        assert(Found != CodeSamples.end() && "Wrong file for import!");
        AllASTs[Filename] = createASTUnit(Filename, Found->getValue());
      }
    };
    for (const ImportAction &Action : ImportActions) {
      StringRef FromFile = Action.FromFilename, ToFile = Action.ToFilename;
      GenASTsIfNeeded(FromFile);
      GenASTsIfNeeded(ToFile);
      ASTUnit *From = AllASTs[FromFile].get();
      ASTUnit *To = AllASTs[ToFile].get();
            std::unique_ptr<ASTImporter> &ImporterRef = Importers[{From, To}];
      if (!ImporterRef)
        ImporterRef.reset(new ASTImporter(
            To->getASTContext(), To->getFileManager(), From->getASTContext(),
            From->getFileManager(), false));
            auto FoundDecl = match(Action.ImportPredicate.bind(DeclToImportID),
                             From->getASTContext());
      EXPECT_TRUE(FoundDecl.size() == 1);
      const Decl *ToImport = selectFirst<Decl>(DeclToImportID, FoundDecl);
      auto Imported = importNode(From, To, *ImporterRef, ToImport);
      EXPECT_TRUE(static_cast<bool>(Imported));
      if (!Imported)
        llvm::consumeError(Imported.takeError());
    }
        auto FoundDecl = match(FinalSelectPredicate.bind(DeclToVerifyID),
                           AllASTs[FileForFinalCheck]->getASTContext());
    EXPECT_TRUE(FoundDecl.size() == 1);
    const Decl *ToVerify = selectFirst<Decl>(DeclToVerifyID, FoundDecl);
    MatchVerifier<Decl> Verifier;
    EXPECT_TRUE(Verifier.match(
        ToVerify, internal::BindableMatcher<Decl>(VerificationMatcher)));
  }
};
template <typename T> RecordDecl *getRecordDecl(T *D) {
  auto *ET = cast<ElaboratedType>(D->getType().getTypePtr());
  return cast<RecordType>(ET->getNamedType().getTypePtr())->getDecl();
}
template <class T>
::testing::AssertionResult isSuccess(llvm::Expected<T> &ValOrErr) {
  if (ValOrErr)
    return ::testing::AssertionSuccess() << "Expected<> contains no error.";
  else
    return ::testing::AssertionFailure()
           << "Expected<> contains error: " << toString(ValOrErr.takeError());
}
template <class T>
::testing::AssertionResult isImportError(llvm::Expected<T> &ValOrErr,
                                         ASTImportError::ErrorKind Kind) {
  if (ValOrErr) {
    return ::testing::AssertionFailure() << "Expected<> is expected to contain "
                                            "error but does contain value \""
                                         << (*ValOrErr) << "\"";
  } else {
    std::ostringstream OS;
    bool Result = false;
    auto Err = llvm::handleErrors(
        ValOrErr.takeError(), [&OS, &Result, Kind](clang::ASTImportError &IE) {
          if (IE.Error == Kind) {
            Result = true;
            OS << "Expected<> contains an ImportError " << IE.toString();
          } else {
            OS << "Expected<> contains an ImportError " << IE.toString()
               << " instead of kind " << Kind;
          }
        });
    if (Err) {
      OS << "Expected<> contains unexpected error: "
         << toString(std::move(Err));
    }
    if (Result)
      return ::testing::AssertionSuccess() << OS.str();
    else
      return ::testing::AssertionFailure() << OS.str();
  }
}
} } 
#endif