//===--- SymbolCollector.cpp -------------------------------------*- C++-*-===//
//
//                     The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//

#include "SymbolCollector.h"
#include "clang/AST/ASTContext.h"
#include "clang/AST/Decl.h"
#include "clang/AST/DeclCXX.h"
#include "clang/Basic/SourceManager.h"
#include "clang/Index/IndexSymbol.h"
#include "clang/Index/USRGeneration.h"
#include "llvm/Support/MemoryBuffer.h"
#include "llvm/Support/Path.h"

namespace clang {
namespace clangd {

namespace {
// Make the Path absolute using the current working directory of the given
// SourceManager if the Path is not an absolute path.
//
// The Path can be a path relative to the build directory, or retrieved from
// the SourceManager.
std::string makeAbsolutePath(const SourceManager &SM, StringRef Path) {
  llvm::SmallString<128> AbsolutePath(Path);
  if (std::error_code EC =
          SM.getFileManager().getVirtualFileSystem()->makeAbsolute(
              AbsolutePath))
    llvm::errs() << "Warning: could not make absolute file: '" << EC.message()
                 << '\n';
  // Handle the symbolic link path case where the current working directory
  // (getCurrentWorkingDirectory) is a symlink./ We always want to the real
  // file path (instead of the symlink path) for the  C++ symbols.
  //
  // Consider the following example:
  //
  //   src dir: /project/src/foo.h
  //   current working directory (symlink): /tmp/build -> /project/src/
  //
  // The file path of Symbol is "/project/src/foo.h" instead of
  // "/tmp/build/foo.h"
  const DirectoryEntry *Dir = SM.getFileManager().getDirectory(
      llvm::sys::path::parent_path(AbsolutePath.str()));
  if (Dir) {
    StringRef DirName = SM.getFileManager().getCanonicalName(Dir);
    SmallString<128> AbsoluteFilename;
    llvm::sys::path::append(AbsoluteFilename, DirName,
                            llvm::sys::path::filename(AbsolutePath.str()));
    return AbsoluteFilename.str();
  }
  return AbsolutePath.str();
}

// Split a qualified symbol name into scope and unqualified name, e.g. given
// "a::b::c", return {"a::b", "c"}. Scope is empty if it doesn't exist.
std::pair<llvm::StringRef, llvm::StringRef>
splitQualifiedName(llvm::StringRef QName) {
  assert(!QName.startswith("::") && "Qualified names should not start with ::");
  size_t Pos = QName.rfind("::");
  if (Pos == llvm::StringRef::npos)
    return {StringRef(), QName};
  return {QName.substr(0, Pos), QName.substr(Pos + 2)};
}

} // namespace

// Always return true to continue indexing.
bool SymbolCollector::handleDeclOccurence(
    const Decl *D, index::SymbolRoleSet Roles,
    ArrayRef<index::SymbolRelation> Relations, FileID FID, unsigned Offset,
    index::IndexDataConsumer::ASTNodeInfo ASTNode) {
  // FIXME: collect all symbol references.
  if (!(Roles & static_cast<unsigned>(index::SymbolRole::Declaration) ||
        Roles & static_cast<unsigned>(index::SymbolRole::Definition)))
    return true;

  if (const NamedDecl *ND = llvm::dyn_cast<NamedDecl>(D)) {
    // FIXME: Should we include the internal linkage symbols?
    if (!ND->hasExternalFormalLinkage() || ND->isInAnonymousNamespace())
      return true;

    llvm::SmallString<128> USR;
    if (index::generateUSRForDecl(ND, USR))
      return true;

    auto ID = SymbolID(USR);
    if (Symbols.find(ID) != nullptr)
      return true;

    auto &SM = ND->getASTContext().getSourceManager();
    std::string FilePath =
        makeAbsolutePath(SM, SM.getFilename(D->getLocation()));
    SymbolLocation Location = {FilePath, SM.getFileOffset(D->getLocStart()),
                               SM.getFileOffset(D->getLocEnd())};
    std::string QName = ND->getQualifiedNameAsString();
    auto ScopeAndName = splitQualifiedName(QName);

    Symbol S;
    S.ID = std::move(ID);
    S.Scope = ScopeAndName.first;
    S.Name = ScopeAndName.second;
    S.SymInfo = index::getSymbolInfo(D);
    S.CanonicalDeclaration = Location;
    Symbols.insert(S);
  }

  return true;
}

} // namespace clangd
} // namespace clang
