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

#include "cmAlgorithms.h"
#include "cmDocumentationFormatter.h"
#include "cmState.h"
#include "cmSystemTools.h"

#if defined(CMAKE_BUILD_WITH_CMAKE)
#  include "cmsys/SystemInformation.hxx"
#endif

#include <sstream>

cmake::MessageType cmMessenger::ConvertMessageType(cmake::MessageType t) const
{
  bool warningsAsErrors;

  if (t == cmake::AUTHOR_WARNING || t == cmake::AUTHOR_ERROR) {
    warningsAsErrors = this->GetDevWarningsAsErrors();
    if (warningsAsErrors && t == cmake::AUTHOR_WARNING) {
      t = cmake::AUTHOR_ERROR;
    } else if (!warningsAsErrors && t == cmake::AUTHOR_ERROR) {
      t = cmake::AUTHOR_WARNING;
    }
  } else if (t == cmake::DEPRECATION_WARNING ||
             t == cmake::DEPRECATION_ERROR) {
    warningsAsErrors = this->GetDeprecatedWarningsAsErrors();
    if (warningsAsErrors && t == cmake::DEPRECATION_WARNING) {
      t = cmake::DEPRECATION_ERROR;
    } else if (!warningsAsErrors && t == cmake::DEPRECATION_ERROR) {
      t = cmake::DEPRECATION_WARNING;
    }
  }

  return t;
}

bool cmMessenger::IsMessageTypeVisible(cmake::MessageType t) const
{
  bool isVisible = true;

  if (t == cmake::DEPRECATION_ERROR) {
    if (!this->GetDeprecatedWarningsAsErrors()) {
      isVisible = false;
    }
  } else if (t == cmake::DEPRECATION_WARNING) {
    if (this->GetSuppressDeprecatedWarnings()) {
      isVisible = false;
    }
  } else if (t == cmake::AUTHOR_ERROR) {
    if (!this->GetDevWarningsAsErrors()) {
      isVisible = false;
    }
  } else if (t == cmake::AUTHOR_WARNING) {
    if (this->GetSuppressDevWarnings()) {
      isVisible = false;
    }
  }

  return isVisible;
}

static bool printMessagePreamble(cmake::MessageType t, std::ostream& msg)
{
  // Construct the message header.
  if (t == cmake::FATAL_ERROR) {
    msg << "CMake Error";
  } else if (t == cmake::INTERNAL_ERROR) {
    msg << "CMake Internal Error (please report a bug)";
  } else if (t == cmake::LOG) {
    msg << "CMake Debug Log";
  } else if (t == cmake::DEPRECATION_ERROR) {
    msg << "CMake Deprecation Error";
  } else if (t == cmake::DEPRECATION_WARNING) {
    msg << "CMake Deprecation Warning";
  } else if (t == cmake::AUTHOR_WARNING) {
    msg << "CMake Warning (dev)";
  } else if (t == cmake::AUTHOR_ERROR) {
    msg << "CMake Error (dev)";
  } else {
    msg << "CMake Warning";
  }
  return true;
}

void printMessageText(std::ostream& msg, std::string const& text)
{
  msg << ":\n";
  cmDocumentationFormatter formatter;
  formatter.SetIndent("  ");
  formatter.PrintFormatted(msg, text.c_str());
}

void displayMessage(cmake::MessageType t, std::ostringstream& msg)
{
  // Add a note about warning suppression.
  if (t == cmake::AUTHOR_WARNING) {
    msg << "This warning is for project developers.  Use -Wno-dev to suppress "
           "it.";
  } else if (t == cmake::AUTHOR_ERROR) {
    msg << "This error is for project developers. Use -Wno-error=dev to "
           "suppress "
           "it.";
  }

  // Add a terminating blank line.
  msg << "\n";

#if defined(CMAKE_BUILD_WITH_CMAKE)
  // Add a C++ stack trace to internal errors.
  if (t == cmake::INTERNAL_ERROR) {
    std::string stack = cmsys::SystemInformation::GetProgramStack(0, 0);
    if (!stack.empty()) {
      if (cmHasLiteralPrefix(stack, "WARNING:")) {
        stack = "Note:" + stack.substr(8);
      }
      msg << stack << "\n";
    }
  }
#endif

  // Output the message.
  if (t == cmake::FATAL_ERROR || t == cmake::INTERNAL_ERROR ||
      t == cmake::DEPRECATION_ERROR || t == cmake::AUTHOR_ERROR) {
    cmSystemTools::SetErrorOccured();
    cmSystemTools::Message(msg.str().c_str(), "Error");
  } else {
    cmSystemTools::Message(msg.str().c_str(), "Warning");
  }
}

cmMessenger::cmMessenger(cmState* state)
  : State(state)
{
}

void cmMessenger::IssueMessage(cmake::MessageType t, const std::string& text,
                               const cmListFileBacktrace& backtrace) const
{
  bool force = false;
  if (!force) {
    // override the message type, if needed, for warnings and errors
    cmake::MessageType override = this->ConvertMessageType(t);
    if (override != t) {
      t = override;
      force = true;
    }
  }

  if (!force && !this->IsMessageTypeVisible(t)) {
    return;
  }
  this->DisplayMessage(t, text, backtrace);
}

void cmMessenger::DisplayMessage(cmake::MessageType t, const std::string& text,
                                 const cmListFileBacktrace& backtrace) const
{
  std::ostringstream msg;
  if (!printMessagePreamble(t, msg)) {
    return;
  }

  // Add the immediate context.
  backtrace.PrintTitle(msg);

  printMessageText(msg, text);

  // Add the rest of the context.
  backtrace.PrintCallStack(msg);

  displayMessage(t, msg);
}

bool cmMessenger::GetSuppressDevWarnings() const
{
  const char* cacheEntryValue =
    this->State->GetCacheEntryValue("CMAKE_SUPPRESS_DEVELOPER_WARNINGS");
  return cmSystemTools::IsOn(cacheEntryValue);
}

bool cmMessenger::GetSuppressDeprecatedWarnings() const
{
  const char* cacheEntryValue =
    this->State->GetCacheEntryValue("CMAKE_WARN_DEPRECATED");
  return cacheEntryValue && cmSystemTools::IsOff(cacheEntryValue);
}

bool cmMessenger::GetDevWarningsAsErrors() const
{
  const char* cacheEntryValue =
    this->State->GetCacheEntryValue("CMAKE_SUPPRESS_DEVELOPER_ERRORS");
  return cacheEntryValue && cmSystemTools::IsOff(cacheEntryValue);
}

bool cmMessenger::GetDeprecatedWarningsAsErrors() const
{
  const char* cacheEntryValue =
    this->State->GetCacheEntryValue("CMAKE_ERROR_DEPRECATED");
  return cmSystemTools::IsOn(cacheEntryValue);
}
