//===- ExternalASTMerger.cpp - Merging External AST Interface ---*- C++ -*-===//
//
//                     The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
//
//  This file implements the ExternalASTMerger, which vends a combination of
//  ASTs from several different ASTContext/FileManager pairs
//
//===----------------------------------------------------------------------===//

#include "clang/AST/ASTContext.h"
#include "clang/AST/Decl.h"
#include "clang/AST/DeclObjC.h"
#include "clang/AST/ExternalASTMerger.h"

using namespace clang;

namespace {

template <typename T> struct Source {
  T t;
  Source(T t) : t(t) {}
  operator T() { return t; }
  template <typename U = T> U &get() { return t; }
  template <typename U = T> const U &get() const { return t; }
  template <typename U> operator Source<U>() { return Source<U>(t); }
};

typedef std::pair<Source<NamedDecl *>, ASTImporter *> Candidate;

class LazyASTImporter : public ASTImporter {
public:
  LazyASTImporter(ASTContext &ToContext, FileManager &ToFileManager,
                  ASTContext &FromContext, FileManager &FromFileManager)
      : ASTImporter(ToContext, ToFileManager, FromContext, FromFileManager,
                    /*MinimalImport=*/true) {}
  Decl *Imported(Decl *From, Decl *To) override {
    if (auto ToTag = dyn_cast<TagDecl>(To)) {
      ToTag->setHasExternalLexicalStorage();
      ToTag->setMustBuildLookupTable();
    } else if (auto ToNamespace = dyn_cast<NamespaceDecl>(To)) {
      ToNamespace->setHasExternalVisibleStorage();
    }
    return ASTImporter::Imported(From, To);
  }
};

Source<const DeclContext *>
LookupSameContext(Source<TranslationUnitDecl *> SourceTU, const DeclContext *DC,
                  ASTImporter &ReverseImporter) {
  if (DC->isTranslationUnit()) {
    return SourceTU;
  }
  Source<const DeclContext *> SourceParentDC =
      LookupSameContext(SourceTU, DC->getParent(), ReverseImporter);
  if (!SourceParentDC) {
    // If we couldn't find the parent DC in this TranslationUnit, give up.
    return nullptr;
  }
  auto ND = cast<NamedDecl>(DC);
  DeclarationName Name = ND->getDeclName();
  Source<DeclarationName> SourceName = ReverseImporter.Import(Name);
  DeclContext::lookup_result SearchResult =
      SourceParentDC.get()->lookup(SourceName.get());
  size_t SearchResultSize = SearchResult.size();
  // Handle multiple candidates once we have a test for it.
  // This may turn up when we import template specializations correctly.
  assert(SearchResultSize < 2);
  if (SearchResultSize == 0) {
    // couldn't find the name, so we have to give up
    return nullptr;
  } else {
    NamedDecl *SearchResultDecl = SearchResult[0];
    return dyn_cast<DeclContext>(SearchResultDecl);
  }
}

bool IsForwardDeclaration(Decl *D) {
  assert(!isa<ObjCInterfaceDecl>(D)); // TODO handle this case
  if (auto TD = dyn_cast<TagDecl>(D)) {
    return !TD->isThisDeclarationADefinition();
  } else if (auto FD = dyn_cast<FunctionDecl>(D)) {
    return !FD->isThisDeclarationADefinition();
  } else {
    return false;
  }
}

template <typename CallbackType>
void ForEachMatchingDC(
    const DeclContext *DC,
    llvm::ArrayRef<ExternalASTMerger::ImporterPair> Importers,
    CallbackType Callback) {
  for (const ExternalASTMerger::ImporterPair &IP : Importers) {
    Source<TranslationUnitDecl *> SourceTU =
        IP.Forward->getFromContext().getTranslationUnitDecl();
    if (auto SourceDC = LookupSameContext(SourceTU, DC, *IP.Reverse))
      Callback(IP, SourceDC);
  }
}

bool HasDeclOfSameType(llvm::ArrayRef<Candidate> Decls, const Candidate &C) {
  return llvm::any_of(Decls, [&](const Candidate &D) {
    return C.first.get()->getKind() == D.first.get()->getKind();
  });
}
} // end namespace

ExternalASTMerger::ExternalASTMerger(const ImporterEndpoint &Target,
                                     llvm::ArrayRef<ImporterEndpoint> Sources) {
  for (const ImporterEndpoint &S : Sources) {
    Importers.push_back(
        {llvm::make_unique<LazyASTImporter>(Target.AST, Target.FM, S.AST, S.FM),
         llvm::make_unique<ASTImporter>(S.AST, S.FM, Target.AST, Target.FM,
                                        /*MinimalImport=*/true)});
  }
}

bool ExternalASTMerger::FindExternalVisibleDeclsByName(const DeclContext *DC,
                                                       DeclarationName Name) {
  llvm::SmallVector<NamedDecl *, 1> Decls;
  llvm::SmallVector<Candidate, 4> CompleteDecls;
  llvm::SmallVector<Candidate, 4> ForwardDecls;

  auto FilterFoundDecl = [&CompleteDecls, &ForwardDecls](const Candidate &C) {
    if (IsForwardDeclaration(C.first.get())) {
      if (!HasDeclOfSameType(ForwardDecls, C)) {
        ForwardDecls.push_back(C);
      }
    } else {
      CompleteDecls.push_back(C);
    }
  };

  ForEachMatchingDC(
      DC, Importers,
      [&](const ImporterPair &IP, Source<const DeclContext *> SourceDC) {
        DeclarationName FromName = IP.Reverse->Import(Name);
        DeclContextLookupResult Result = SourceDC.get()->lookup(FromName);
        for (NamedDecl *FromD : Result) {
          FilterFoundDecl(std::make_pair(FromD, IP.Forward.get()));
        }
      });

  llvm::ArrayRef<Candidate> DeclsToReport =
      CompleteDecls.empty() ? ForwardDecls : CompleteDecls;

  if (DeclsToReport.empty()) {
    return false;
  }

  Decls.reserve(DeclsToReport.size());
  for (const Candidate &C : DeclsToReport) {
    NamedDecl *d = cast<NamedDecl>(C.second->Import(C.first.get()));
    assert(d);
    Decls.push_back(d);
  }
  SetExternalVisibleDeclsForName(DC, Name, Decls);
  return true;
}

void ExternalASTMerger::FindExternalLexicalDecls(
    const DeclContext *DC, llvm::function_ref<bool(Decl::Kind)> IsKindWeWant,
    SmallVectorImpl<Decl *> &Result) {
  ForEachMatchingDC(
      DC, Importers,
      [&](const ImporterPair &IP, Source<const DeclContext *> SourceDC) {
        for (const Decl *SourceDecl : SourceDC.get()->decls()) {
          if (IsKindWeWant(SourceDecl->getKind())) {
            Decl *ImportedDecl =
                IP.Forward->Import(const_cast<Decl *>(SourceDecl));
            assert(ImportedDecl->getDeclContext() == DC);
            (void)ImportedDecl;
          }
        }
      });
}

