//===-- IncludeFixerContext.cpp - Include fixer context ---------*- 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 "IncludeFixerContext.h"
#include <algorithm>

namespace clang {
namespace include_fixer {

namespace {

// Splits a multiply qualified names (e.g. a::b::c).
llvm::SmallVector<llvm::StringRef, 8>
SplitQualifiers(llvm::StringRef StringQualifiers) {
  llvm::SmallVector<llvm::StringRef, 8> Qualifiers;
  StringQualifiers.split(Qualifiers, "::");
  return Qualifiers;
}

std::string createQualifiedNameForReplacement(
    llvm::StringRef RawSymbolName,
    llvm::StringRef SymbolScopedQualifiersName,
    const find_all_symbols::SymbolInfo &MatchedSymbol) {
  // No need to add missing qualifiers if SymbolIdentifier has a global scope
  // operator "::".
  if (RawSymbolName.startswith("::"))
    return std::string(RawSymbolName);

  std::string QualifiedName = MatchedSymbol.getQualifiedName();

  // For nested classes, the qualified name constructed from database misses
  // some stripped qualifiers, because when we search a symbol in database,
  // we strip qualifiers from the end until we find a result. So append the
  // missing stripped qualifiers here.
  //
  // Get stripped qualifiers.
  auto SymbolQualifiers = SplitQualifiers(RawSymbolName);
  std::string StrippedQualifiers;
  while (!SymbolQualifiers.empty() &&
         !llvm::StringRef(QualifiedName).endswith(SymbolQualifiers.back())) {
    StrippedQualifiers =
        "::" + SymbolQualifiers.back().str() + StrippedQualifiers;
    SymbolQualifiers.pop_back();
  }
  // Append the missing stripped qualifiers.
  std::string FullyQualifiedName = QualifiedName + StrippedQualifiers;

  // Try to find and skip the common prefix qualifiers.
  auto FullySymbolQualifiers = SplitQualifiers(FullyQualifiedName);
  auto ScopedQualifiers = SplitQualifiers(SymbolScopedQualifiersName);
  auto FullySymbolQualifiersIter = FullySymbolQualifiers.begin();
  auto SymbolScopedQualifiersIter = ScopedQualifiers.begin();
  while (FullySymbolQualifiersIter != FullySymbolQualifiers.end() &&
         SymbolScopedQualifiersIter != ScopedQualifiers.end()) {
    if (*FullySymbolQualifiersIter != *SymbolScopedQualifiersIter)
      break;
    ++FullySymbolQualifiersIter;
    ++SymbolScopedQualifiersIter;
  }
  std::string Result;
  for (; FullySymbolQualifiersIter != FullySymbolQualifiers.end();
       ++FullySymbolQualifiersIter) {
    if (!Result.empty())
      Result += "::";
    Result += *FullySymbolQualifiersIter;
  }
  return Result;
}

} // anonymous namespace

IncludeFixerContext::IncludeFixerContext(
    StringRef FilePath, std::vector<QuerySymbolInfo> QuerySymbols,
    std::vector<find_all_symbols::SymbolInfo> Symbols)
    : FilePath(FilePath), QuerySymbolInfos(std::move(QuerySymbols)),
      MatchedSymbols(std::move(Symbols)) {
  // Remove replicated QuerySymbolInfos with the same range.
  //
  // QuerySymbolInfos may contain replicated elements. Because CorrectTypo
  // callback doesn't always work as we expected. In somecases, it will be
  // triggered at the same position or unidentified symbol multiple times.
  std::sort(QuerySymbolInfos.begin(), QuerySymbolInfos.end(),
            [&](const QuerySymbolInfo &A, const QuerySymbolInfo &B) {
              return std::make_pair(A.Range.getOffset(), A.Range.getLength()) <
                     std::make_pair(B.Range.getOffset(), B.Range.getLength());
            });
  QuerySymbolInfos.erase(
      std::unique(QuerySymbolInfos.begin(), QuerySymbolInfos.end(),
                  [](const QuerySymbolInfo &A, const QuerySymbolInfo &B) {
                    return A.Range == B.Range;
                  }),
      QuerySymbolInfos.end());
  for (const auto &Symbol : MatchedSymbols) {
    HeaderInfos.push_back(
        {Symbol.getFilePath().str(),
         createQualifiedNameForReplacement(
             QuerySymbolInfos.front().RawIdentifier,
             QuerySymbolInfos.front().ScopedQualifiers, Symbol)});
  }
  // Deduplicate header infos.
  HeaderInfos.erase(std::unique(HeaderInfos.begin(), HeaderInfos.end(),
                                [](const HeaderInfo &A, const HeaderInfo &B) {
                                  return A.Header == B.Header &&
                                         A.QualifiedName == B.QualifiedName;
                                }),
                    HeaderInfos.end());
}

} // include_fixer
} // clang
