#ifndef MPMO_H
#define MPMO_H

#include <string>
#include <iostream>
#include <cstring>
#include <functional>
#include <cstdio>
#include <vector>
#include "mpmo_color.h"

std::string argvToString(int argc, const char **argv);
std::string argvToString(std::vector<std::string> argv);

namespace MpMO
{
/* ---- CAUTION ---- */
/* In this entire namespace the warnings -Wformat-nonliteral and -Wformat-security are disabled for all printf statements
 * this can cause security issues IF the users have access to the format strings used in this files.
 * in CDO this is not the case and as such it was decided to use this file as it is and to disable the warnings.
 *
 * */

extern int DebugLevel;
extern bool silentMode;
void enableSilentMode(bool enable);
extern bool warningsEnabled;
void enableWarnings(bool enable);
extern bool verbose;
void enableVerbose(bool enable);
extern bool exitOnError;
extern bool pedantic;
void enablePedantic(bool enable);
constexpr int padding_width = 30;

template <typename T>
T
Argument(T value) noexcept
{
  return value;
}

template <typename T>
T const *
Argument(std::basic_string<T> const &value) noexcept
{
  return value.c_str();
}

template <typename... Args>
void
Print(const std::string &format, Args const &... args) noexcept
{
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wformat-nonliteral"
#pragma GCC diagnostic ignored "-Wformat-security"
  if (!silentMode) printf((format + "\n").c_str(), Argument(args)...);
#pragma GCC diagnostic pop
}

template <typename... Args>
void
PrintCerr(const std::string &format, Args const &... args) noexcept
{
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wformat-nonliteral"
#pragma GCC diagnostic ignored "-Wformat-security"
  fprintf(stderr, (format + "\n").c_str(), Argument(args)...);
#pragma GCC diagnostic pop
}

static inline std::string
get_padding(const char *p_func)
{
  size_t len = strlen(p_func);
  return std::string(padding_width - len, ' ');
}

void Debug_(const char *p_func, const char *context, int p_debugScope, std::function<void()> p_function);
void Debug_(const char *p_func, const char *context, std::function<void()> p_function);

template <typename... Args>
void
Debug_(const char *p_func, const char *context, int p_debugScope, const std::string &format, Args const &... args)
{
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wformat-nonliteral"
#pragma GCC diagnostic ignored "-Wformat-security"
  if (p_debugScope)
    fprintf(stderr, (Cyan(context) + ": " + std::string(p_func) + get_padding(p_func) + format + "\n").c_str(), Argument(args)...);
#pragma GCC diagnostic pop
}

template <typename... Args>
void
Debug_(const char *p_func, const char *context, const std::string &format, Args const &... args)
{
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wformat-nonliteral"
#pragma GCC diagnostic ignored "-Wformat-security"
  if (DebugLevel)
    fprintf(stderr, (Cyan(context) + ": " + std::string(p_func) + get_padding(p_func) + format + "\n").c_str(), Argument(args)...);
#pragma GCC diagnostic pop
}

template <typename... Args>
void
Error_(const char *caller, const std::string &format, Args const &... args) noexcept
{
  PrintCerr(Red("Error:") + "(%s)" + format, caller, Argument(args)...);
  if (exitOnError) exit(EXIT_FAILURE);
}

void Verbose_(std::function<void()> p_function) noexcept;

template <typename... Args>
void
Verbose_(const std::string &format, Args const &... args) noexcept
{
  if (verbose) PrintCerr(format, Argument(args)...);
}

template <typename... Args>
void
Warning_(const char *caller, const std::string &format, Args const &... args) noexcept
{
  (void) caller;  // quell warning if WITH_CALLER_NAME is not defined
  if (warningsEnabled)
    {
      if (pedantic)
        {
          PrintCerr(Red("Warning: ") + format, Argument(args)...);
          if (exitOnError) exit(EXIT_FAILURE);
        }
      else
        {
          PrintCerr(Yellow("Warning: ") + format, Argument(args)...);
        }
    }
}

template <typename... Args>
void
SysError_(const char *func, const std::string &format, Args const &... args) noexcept
{
  int saved_errno = errno;
  PrintCerr(Red("SysError: %s ") + format, func, Argument(args)...);
  if (saved_errno)
    {
      errno = saved_errno;
      perror("System error message");
    }
  exit(EXIT_FAILURE);
}

}  // namespace MpMO

#define Verbose(...) Verbose_(__VA_ARGS__)
#ifndef NO_DEBUG
#define Debug(...) MpMO::Debug_(__func__, cdo::getContext(), __VA_ARGS__)
#else
#define Debug(...)
#endif

#endif
