/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
   file LICENSE.rst or https://cmake.org/licensing for details.  */
#include "cmDiagnostics.h"

#include <cassert>
#include <map>
#include <string>
#include <utility>

#include <cmext/string_view>

#include "cmStringAlgorithms.h"

namespace {

#if __cplusplus >= 201703L
constexpr unsigned validateDiagnosticsSubtree(unsigned parent, unsigned index)
{
  // Ensure that all diagnostics, starting from the specified index, have the
  // specified parent as an ancestor. Return the first index that violates
  // this condition.
  while (index < cmDiagnostics::CategoryCount &&
         cmDiagnostics::CategoryInfo[index].Parent == parent) {
    unsigned const child = index;
    // For each diagnostic, 'consume' its children (if any).
    index = validateDiagnosticsSubtree(child, ++index);
  }
  return index;
}

static_assert(validateDiagnosticsSubtree(cmDiagnostics::CMD_NONE, 1) ==
                cmDiagnostics::CategoryCount,
              "Diagnostics are not properly ordered"
              " (hint: LHS is the index of the first misordered diagnostic)");
#endif

cm::optional<cmDiagnosticCategory> stringToCategory(cm::string_view input)
{
  using Map = std::map<cm::string_view, cmDiagnosticCategory>;
  static Map const mapping = {
#define CATEGORY_MAP(C) { #C ""_s, cmDiagnostics::C },
    CM_FOR_EACH_DIAGNOSTIC_CATEGORY(CATEGORY_MAP)
#undef CATEGORY_MAP
  };

  assert(!input.empty());
  if (input.size() >= 4 && cmHasLiteralPrefix(input, "CMD_")) {
    auto const i = mapping.find(input);
    if (i != mapping.end()) {
      return i->second;
    }
  }

  return cm::nullopt;
}
}

#if __cplusplus < 201703L
// Prior to C++17, the compiler is unhappy if this member doesn't have explicit
// storage... and clang-tidy is unhappy if it does.
// NOLINTNEXTLINE(*-redundant-declaration)
constexpr cmDiagnostics::DiagnosticCategoryInformation
  cmDiagnostics::CategoryInfo[cmDiagnostics::CategoryCount];
#endif

cm::string_view cmDiagnostics::GetActionString(DiagnosticAction action)
{
  switch (action) {
    case Ignore:
      return "IGNORE"_s;
    case Warn:
      return "WARN"_s;
    case SendError:
      return "SEND_ERROR"_s;
    case FatalError:
      return "FATAL_ERROR"_s;
    default:
      return {};
  }
}

cm::string_view cmDiagnostics::GetCategoryString(DiagnosticCategory category)
{
  static cm::string_view const names[CategoryCount] = {
    {}, // CMD_NONE
#define CATEGORY_NAME(C) #C ""_s,
    CM_FOR_EACH_DIAGNOSTIC_CATEGORY(CATEGORY_NAME)
#undef CATEGORY_MAP
  };

  if (category < CategoryCount) {
    return names[category];
  }
  return {};
}

cm::optional<cmDiagnosticAction> cmDiagnostics::GetDiagnosticAction(
  cm::string_view name)
{
  if (name == "IGNORE"_s) {
    return DiagnosticAction::Ignore;
  }
  if (name == "WARN"_s) {
    return DiagnosticAction::Warn;
  }
  if (name == "SEND_ERROR"_s) {
    return DiagnosticAction::SendError;
  }
  if (name == "FATAL_ERROR"_s) {
    return DiagnosticAction::FatalError;
  }

  return cm::nullopt;
}

cm::optional<cmDiagnosticCategory> cmDiagnostics::GetDiagnosticCategory(
  cm::string_view name)
{
  return stringToCategory(name);
}
