//===--- UseRangesCheck.cpp - clang-tidy ----------------------------------===//
//
// 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 "UseRangesCheck.h"
#include "clang/AST/Decl.h"
#include "clang/Basic/Diagnostic.h"
#include "clang/Basic/LLVM.h"
#include "llvm/ADT/ArrayRef.h"
#include "llvm/ADT/IntrusiveRefCntPtr.h"
#include "llvm/ADT/SmallString.h"
#include "llvm/ADT/SmallVector.h"
#include "llvm/ADT/StringRef.h"
#include <initializer_list>
#include <optional>
#include <string>

// FixItHint - Let the docs script know that this class does provide fixits

namespace clang::tidy::boost {

namespace {
/// Base replacer that handles the boost include path and namespace
class BoostReplacer : public UseRangesCheck::Replacer {
public:
  BoostReplacer(ArrayRef<UseRangesCheck::Signature> Signatures,
                bool IncludeSystem)
      : Signatures(Signatures), IncludeSystem(IncludeSystem) {}

  ArrayRef<UseRangesCheck::Signature> getReplacementSignatures() const final {
    return Signatures;
  }

  virtual std::pair<StringRef, StringRef>
  getBoostName(const NamedDecl &OriginalName) const = 0;

  virtual std::pair<StringRef, StringRef>
  getBoostHeader(const NamedDecl &OriginalName) const = 0;

  std::optional<std::string>
  getReplaceName(const NamedDecl &OriginalName) const final {
    auto [Namespace, Function] = getBoostName(OriginalName);
    return ("boost::" + Namespace + (Namespace.empty() ? "" : "::") + Function)
        .str();
  }

  std::optional<std::string>
  getHeaderInclusion(const NamedDecl &OriginalName) const final {
    auto [Path, HeaderName] = getBoostHeader(OriginalName);
    return ((IncludeSystem ? "<boost/" : "boost/") + Path +
            (Path.empty() ? "" : "/") + HeaderName +
            (IncludeSystem ? ".hpp>" : ".hpp"))
        .str();
  }

private:
  SmallVector<UseRangesCheck::Signature> Signatures;
  bool IncludeSystem;
};

/// Creates replaces where the header file lives in
/// `boost/algorithm/<FUNC_NAME>.hpp` and the function is named
/// `boost::range::<FUNC_NAME>`
class BoostRangeAlgorithmReplacer : public BoostReplacer {
public:
  using BoostReplacer::BoostReplacer;

  std::pair<StringRef, StringRef>
  getBoostName(const NamedDecl &OriginalName) const override {
    return {"range", OriginalName.getName()};
  }

  std::pair<StringRef, StringRef>
  getBoostHeader(const NamedDecl &OriginalName) const override {
    return {"range/algorithm", OriginalName.getName()};
  }
};

/// Creates replaces where the header file lives in
/// `boost/algorithm/<CUSTOM_HEADER>.hpp` and the function is named
/// `boost::range::<FUNC_NAME>`
class CustomBoostAlgorithmHeaderReplacer : public BoostRangeAlgorithmReplacer {
public:
  CustomBoostAlgorithmHeaderReplacer(
      StringRef HeaderName, ArrayRef<UseRangesCheck::Signature> Signatures,
      bool IncludeSystem)
      : BoostRangeAlgorithmReplacer(Signatures, IncludeSystem),
        HeaderName(HeaderName) {}

  std::pair<StringRef, StringRef>
  getBoostHeader(const NamedDecl & /*OriginalName*/) const override {
    return {"range/algorithm", HeaderName};
  }

private:
  StringRef HeaderName;
};

/// Creates replaces where the header file lives in
/// `boost/algorithm/<SUB_HEADER>.hpp` and the function is named
/// `boost::algorithm::<FUNC_NAME>`
class BoostAlgorithmReplacer : public BoostReplacer {
public:
  BoostAlgorithmReplacer(StringRef SubHeader,
                         ArrayRef<UseRangesCheck::Signature> Signatures,
                         bool IncludeSystem)
      : BoostReplacer(Signatures, IncludeSystem),
        SubHeader(("algorithm/" + SubHeader).str()) {}
  std::pair<StringRef, StringRef>
  getBoostName(const NamedDecl &OriginalName) const override {
    return {"algorithm", OriginalName.getName()};
  }

  std::pair<StringRef, StringRef>
  getBoostHeader(const NamedDecl &OriginalName) const override {
    return {SubHeader, OriginalName.getName()};
  }

private:
  std::string SubHeader;
};

/// Creates replaces where the header file lives in
/// `boost/algorithm/<SUB_HEADER>/<HEADER_NAME>.hpp` and the function is named
/// `boost::algorithm::<FUNC_NAME>`
class CustomBoostAlgorithmReplacer : public BoostReplacer {
public:
  CustomBoostAlgorithmReplacer(StringRef SubHeader, StringRef HeaderName,
                               ArrayRef<UseRangesCheck::Signature> Signatures,
                               bool IncludeSystem)
      : BoostReplacer(Signatures, IncludeSystem),
        SubHeader(("algorithm/" + SubHeader).str()), HeaderName(HeaderName) {}
  std::pair<StringRef, StringRef>
  getBoostName(const NamedDecl &OriginalName) const override {
    return {"algorithm", OriginalName.getName()};
  }

  std::pair<StringRef, StringRef>
  getBoostHeader(const NamedDecl & /*OriginalName*/) const override {
    return {SubHeader, HeaderName};
  }

private:
  std::string SubHeader;
  StringRef HeaderName;
};

/// A Replacer that is used for functions that just call a new overload
class MakeOverloadReplacer : public UseRangesCheck::Replacer {
public:
  explicit MakeOverloadReplacer(ArrayRef<UseRangesCheck::Signature> Signatures)
      : Signatures(Signatures) {}

  ArrayRef<UseRangesCheck::Signature>
  getReplacementSignatures() const override {
    return Signatures;
  }

  std::optional<std::string>
  getReplaceName(const NamedDecl & /* OriginalName */) const override {
    return std::nullopt;
  }

  std::optional<std::string>
  getHeaderInclusion(const NamedDecl & /* OriginalName */) const override {
    return std::nullopt;
  }

private:
  SmallVector<UseRangesCheck::Signature> Signatures;
};

/// A replacer that replaces functions with an equivalent named function in the
/// root boost namespace
class FixedBoostReplace : public BoostReplacer {
public:
  FixedBoostReplace(StringRef Header,
                    ArrayRef<UseRangesCheck::Signature> Signatures,
                    bool IncludeBoostSystem)
      : BoostReplacer(Signatures, IncludeBoostSystem), Header(Header) {}

  std::pair<StringRef, StringRef>
  getBoostName(const NamedDecl &OriginalName) const override {
    return {{}, OriginalName.getName()};
  }

  std::pair<StringRef, StringRef>
  getBoostHeader(const NamedDecl & /* OriginalName */) const override {
    return {{}, Header};
  }

private:
  StringRef Header;
};

} // namespace

utils::UseRangesCheck::ReplacerMap UseRangesCheck::getReplacerMap() const {

  ReplacerMap Results;
  static const Signature SingleSig = {{0}};
  static const Signature TwoSig = {{0}, {2}};
  const auto AddFrom =
      [&Results](llvm::IntrusiveRefCntPtr<UseRangesCheck::Replacer> Replacer,
                 std::initializer_list<StringRef> Names, StringRef Prefix) {
        llvm::SmallString<64> Buffer;
        for (const auto &Name : Names) {
          Buffer.assign({"::", Prefix, (Prefix.empty() ? "" : "::"), Name});
          Results.try_emplace(Buffer, Replacer);
        }
      };

  const auto AddFromStd =
      [&](llvm::IntrusiveRefCntPtr<UseRangesCheck::Replacer> Replacer,
          std::initializer_list<StringRef> Names) {
        AddFrom(Replacer, Names, "std");
      };

  const auto AddFromBoost =
      [&](llvm::IntrusiveRefCntPtr<UseRangesCheck::Replacer> Replacer,
          std::initializer_list<
              std::pair<StringRef, std::initializer_list<StringRef>>>
              NamespaceAndNames) {
        for (auto [Namespace, Names] : NamespaceAndNames)
          AddFrom(Replacer, Names,
                  SmallString<64>{"boost", (Namespace.empty() ? "" : "::"),
                                  Namespace});
      };

  AddFromStd(llvm::makeIntrusiveRefCnt<CustomBoostAlgorithmHeaderReplacer>(
                 "set_algorithm", TwoSig, IncludeBoostSystem),
             {"includes", "set_union", "set_intersection", "set_difference",
              "set_symmetric_difference"});

  AddFromStd(llvm::makeIntrusiveRefCnt<BoostRangeAlgorithmReplacer>(
                 SingleSig, IncludeBoostSystem),
             {"unique",         "lower_bound",   "stable_sort",
              "equal_range",    "remove_if",     "sort",
              "random_shuffle", "remove_copy",   "stable_partition",
              "remove_copy_if", "count",         "copy_backward",
              "reverse_copy",   "adjacent_find", "remove",
              "upper_bound",    "binary_search", "replace_copy_if",
              "for_each",       "generate",      "count_if",
              "min_element",    "reverse",       "replace_copy",
              "fill",           "unique_copy",   "transform",
              "copy",           "replace",       "find",
              "replace_if",     "find_if",       "partition",
              "max_element"});

  AddFromStd(llvm::makeIntrusiveRefCnt<BoostRangeAlgorithmReplacer>(
                 TwoSig, IncludeBoostSystem),
             {"find_end", "merge", "partial_sort_copy", "find_first_of",
              "search", "lexicographical_compare", "equal", "mismatch"});

  AddFromStd(llvm::makeIntrusiveRefCnt<CustomBoostAlgorithmHeaderReplacer>(
                 "permutation", SingleSig, IncludeBoostSystem),
             {"next_permutation", "prev_permutation"});

  AddFromStd(llvm::makeIntrusiveRefCnt<CustomBoostAlgorithmHeaderReplacer>(
                 "heap_algorithm", SingleSig, IncludeBoostSystem),
             {"push_heap", "pop_heap", "make_heap", "sort_heap"});

  AddFromStd(llvm::makeIntrusiveRefCnt<BoostAlgorithmReplacer>(
                 "cxx11", SingleSig, IncludeBoostSystem),
             {"copy_if", "is_permutation", "is_partitioned", "find_if_not",
              "partition_copy", "any_of", "iota", "all_of", "partition_point",
              "is_sorted", "none_of"});

  AddFromStd(llvm::makeIntrusiveRefCnt<CustomBoostAlgorithmReplacer>(
                 "cxx11", "is_sorted", SingleSig, IncludeBoostSystem),
             {"is_sorted_until"});

  AddFromStd(llvm::makeIntrusiveRefCnt<FixedBoostReplace>(
                 "range/numeric", SingleSig, IncludeBoostSystem),
             {"accumulate", "partial_sum", "adjacent_difference"});

  if (getLangOpts().CPlusPlus17)
    AddFromStd(llvm::makeIntrusiveRefCnt<BoostAlgorithmReplacer>(
                   "cxx17", SingleSig, IncludeBoostSystem),
               {"reduce"});

  AddFromBoost(llvm::makeIntrusiveRefCnt<MakeOverloadReplacer>(SingleSig),
               {{"algorithm",
                 {"reduce",
                  "find_backward",
                  "find_not_backward",
                  "find_if_backward",
                  "find_if_not_backward",
                  "hex",
                  "hex_lower",
                  "unhex",
                  "is_partitioned_until",
                  "is_palindrome",
                  "copy_if",
                  "copy_while",
                  "copy_until",
                  "copy_if_while",
                  "copy_if_until",
                  "is_permutation",
                  "is_partitioned",
                  "one_of",
                  "one_of_equal",
                  "find_if_not",
                  "partition_copy",
                  "any_of",
                  "any_of_equal",
                  "iota",
                  "all_of",
                  "all_of_equal",
                  "partition_point",
                  "is_sorted_until",
                  "is_sorted",
                  "is_increasing",
                  "is_decreasing",
                  "is_strictly_increasing",
                  "is_strictly_decreasing",
                  "none_of",
                  "none_of_equal",
                  "clamp_range"}}});

  AddFromBoost(
      llvm::makeIntrusiveRefCnt<MakeOverloadReplacer>(TwoSig),
      {{"algorithm", {"apply_permutation", "apply_reverse_permutation"}}});

  return Results;
}

UseRangesCheck::UseRangesCheck(StringRef Name, ClangTidyContext *Context)
    : utils::UseRangesCheck(Name, Context),
      IncludeBoostSystem(Options.get("IncludeBoostSystem", true)),
      UseReversePipe(Options.get("UseReversePipe", false)) {}

void UseRangesCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
  utils::UseRangesCheck::storeOptions(Opts);
  Options.store(Opts, "IncludeBoostSystem", IncludeBoostSystem);
  Options.store(Opts, "UseReversePipe", UseReversePipe);
}

DiagnosticBuilder UseRangesCheck::createDiag(const CallExpr &Call) {
  DiagnosticBuilder D =
      diag(Call.getBeginLoc(), "use a %0 version of this algorithm");
  D << (Call.getDirectCallee()->isInStdNamespace() ? "boost" : "ranged");
  return D;
}
ArrayRef<std::pair<StringRef, StringRef>>
UseRangesCheck::getFreeBeginEndMethods() const {
  static const std::pair<StringRef, StringRef> Refs[] = {
      {"::std::begin", "::std::end"},
      {"::std::cbegin", "::std::cend"},
      {"::boost::range_adl_barrier::begin", "::boost::range_adl_barrier::end"},
      {"::boost::range_adl_barrier::const_begin",
       "::boost::range_adl_barrier::const_end"},
  };
  return Refs;
}
std::optional<UseRangesCheck::ReverseIteratorDescriptor>
UseRangesCheck::getReverseDescriptor() const {
  static const std::pair<StringRef, StringRef> Refs[] = {
      {"::std::rbegin", "::std::rend"},
      {"::std::crbegin", "::std::crend"},
      {"::boost::rbegin", "::boost::rend"},
      {"::boost::const_rbegin", "::boost::const_rend"},
  };
  return ReverseIteratorDescriptor{
      UseReversePipe ? "boost::adaptors::reversed" : "boost::adaptors::reverse",
      IncludeBoostSystem ? "<boost/range/adaptor/reversed.hpp>"
                         : "boost/range/adaptor/reversed.hpp",
      Refs, UseReversePipe};
}
} // namespace clang::tidy::boost
