//          Copyright Johannes Kapfhammer 2019.
// Distributed under the Boost Software License, Version 1.0.
//    (See accompanying file LICENSE_1_0.txt or copy at
//          http://www.boost.org/LICENSE_1_0.txt)
//
// A debug macro: allows debugging with dbg(<expression>);
// initialize with dbg_init() to enable colorized output
//

#ifndef SOI_DBG
#define SOI_DBG

#include <cstddef>
#include <cstring>
#include <iostream>
#include <ostream>
#include <sstream>
#include <stdexcept>

#ifdef __GNUG__
#include <cstdlib>
#include <cxxabi.h>
#include <memory>
#endif

#if defined(__unix__) || (defined(__APPLE__) && defined(__MACH__))
#include <unistd.h>
#endif

#include "soi-pretty.hpp"

namespace soi {

namespace detail {

// ----------------------------------------------------------------------------
// pretty printing the type name

struct string_view {
  char const *data;
  std::size_t size;
};

template <class T> string_view get_name() {
  char const *p = __PRETTY_FUNCTION__;
  while (*p++ != '=')
    ;
  for (; *p == ' '; ++p)
    ;
  char const *p2 = p;
  int count = 1;
  for (;; ++p2) {
    switch (*p2) {
    case '[':
      ++count;
      break;
    case ']':
      --count;
      if (!count)
        return {p, std::size_t(p2 - p)};
    }
  }
  return {};
}

std::string replace_all(std::string str, const std::string &from,
                        const std::string &to) {
  size_t start_pos = 0;
  while ((start_pos = str.find(from, start_pos)) != std::string::npos) {
    str.replace(start_pos, from.length(), to);
    start_pos +=
        to.length(); // Handles case where 'to' is a substring of 'from'
  }
  return str;
}

inline std::string sanitize_type(std::string s) {
  auto f = [&](const char *a, const char *b) {
    s = replace_all(std::move(s), a, b);
  };
  f("std::", "");
  f("__debug::", "");
  f("__cxx11::", "");
  f("basic_string<char> ", "string");
  f("basic_string<char>", "string");
  f("long int", "int");
  return s;
}

template <class T> std::string sanitized_type_name() {
  auto t = get_name<T>();
  return sanitize_type(std::string(t.data, t.size));
}

inline std::string extract_method_name(std::string const& pretty_function) {
    size_t colons = pretty_function.find("::");
    size_t begin = pretty_function.substr(0,colons).rfind(" ") + 1;
    size_t end = pretty_function.rfind("(") - begin;
    return pretty_function.substr(begin,end);
}


// ----------------------------------------------------------------------------
// colorized output

bool tty_supports_colors() {
#if defined(__unix__) || (defined(__APPLE__) && defined(__MACH__))
  return isatty(STDERR_FILENO);
#else
  return true;
#endif
}

int has_environment_color_overwrite() {
  if (const char *color_enabled = std::getenv("SOI_COLOR")) {
    if (!std::strcmp(color_enabled, "1"))
      return 1;
    if (!std::strcmp(color_enabled, "0"))
      return 0;
  }
  return -1;
}

bool are_colors_enabled() {
  int c = has_environment_color_overwrite();
  if (c == -1)
    return tty_supports_colors();
  return static_cast<bool>(c);
}

// ----------------------------------------------------------------------------
// init and static variables

static bool colors_enabled = false;
static const char *ANSI_DEBUG = "";
static const char *ANSI_EXPRESSION = "";
static const char *ANSI_VALUE = "";
static const char *ANSI_TYPE = "";
static const char *ANSI_MESSAGE = "";
static const char *ANSI_RESET = "";

void dbg_init(bool with_colors) {
  if (with_colors) {
    bool colors_enabled = true;
    ANSI_DEBUG = "\x1b[37m";
    ANSI_EXPRESSION = "\x1b[36m";
    ANSI_VALUE = "\x1b[01m";
    ANSI_TYPE = "\x1b[32m";
    ANSI_MESSAGE = "\x1b[31;01m";
    ANSI_RESET = "\x1b[0m";
  } else {
    bool colors_enabled = false;
    ANSI_DEBUG = "";
    ANSI_EXPRESSION = "";
    ANSI_VALUE = "";
    ANSI_TYPE = "";
    ANSI_MESSAGE = "";
    ANSI_RESET = "";
  }
}

void dbg_init() { dbg_init(are_colors_enabled()); }

// ----------------------------------------------------------------------------
// printer

template <typename T>
T &&dbg_print(T &&value, std::string const& type, char const *file, int line,
              std::string const& function_name, char const *expression) {
  const T &ref = value;
  std::stringstream
      value_buffer; // avoid nesting of dbg macros within print functions
  soi::print(value_buffer, ref);

  std::cerr << ANSI_DEBUG << "[" << file << ":" << line << " (" << function_name
            << ")] " << ANSI_RESET << ANSI_EXPRESSION
            << replace_all(c, "int64_t", "int")
            << ANSI_RESET << " = " << ANSI_VALUE << value_buffer.rdbuf()
            << ANSI_RESET << " (" << ANSI_TYPE << type << ANSI_RESET << ")"
            << '\n';

  return std::forward<T>(value);
}

template <unsigned int N>
auto dbg_print(const char (&msg)[N], std::string const &, char const *file,
               int line, std::string const& function_name, char const *expression)
    -> decltype(msg) {
  std::cerr << ANSI_DEBUG << "[" << file << ":" << line << " (" << function_name
            << ")] " << ANSI_RESET << ANSI_MESSAGE << msg << ANSI_RESET << '\n';
  return msg;
}

void dbg_print_status(char const *file, int line, char const *function_name) {
  std::cerr << ANSI_VALUE << "[" << file << ":" << line << " (" << function_name
            << ")]" << ANSI_RESET << '\n';
}

template <typename T> T &&identity(T &&t) { return std::forward<T>(t); }

} // end namespace detail

} // namespace soi

#ifdef SOI_RELEASE
#define dbg(...) dbg_macro::identity(__VA_ARGS__)
#else

#if defined(__clang__)

#define SOI_ARG16(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13,  \
                  _14, _15, ...)                                               \
  _15
#define SOI_IS_NONEMPTY(...)                                                   \
  SOI_ARG16(1, ##__VA_ARGS__, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0)
#else

#define SOI_IS_NONEMPTY(...) __VA_OPT__(1)
#define SOI_DBG_IMPL_ SOI_DBG_IMPL_0
#endif

#define SOI_DBG_IMPL_0()                                                       \
  soi::detail::dbg_print_status(__FILE__, __LINE__, __func__)
#define SOI_DBG_IMPL_1(...)                                                    \
  soi::detail::dbg_print(                                                      \
      (__VA_ARGS__),                                                           \
      soi::detail::sanitized_type_name<decltype(__VA_ARGS__)>(), __FILE__,     \
      __LINE__, soi::detail::extract_method_name(__PRETTY_FUNCTION__), #__VA_ARGS__)

#define SOI_CAT(a, ...) PRIMITIVE_CAT(a, __VA_ARGS__)
#define SOI_PRIMITIVE_CAT(a, ...) a##__VA_ARGS__

#define SOI_DBG_IMPL(is_nonempty, ...)                                         \
  SOI_PRIMITIVE_CAT(SOI_DBG_IMPL_, is_nonempty)(__VA_ARGS__)

#define dbg(...) SOI_DBG_IMPL(SOI_IS_NONEMPTY(__VA_ARGS__), __VA_ARGS__)
#endif

#endif // SOI_DBG