#include "utils.h"

#include <regex>
#include <string>
#include <vector>
#include <exception>
#include <chrono>

/**
 * Clean up the trailing ../ in __FILE__
 *
 * This is used by the logging macro.
 *
 * @param filepath
 * @return const char*
 */
const char *trim_path(const char *filepath) {
  if (strncmp(filepath, "../", 3) == 0) {
    filepath += 3;
  }
  return filepath;
}

#include <fstream>

bool file_exists(const std::string &name) {
  std::ifstream f(name.c_str());
  return f.good();
}

bool replace(std::string &str, const std::string &from, const std::string &to) {
  size_t start_pos = str.find(from);
  if (start_pos == std::string::npos) return false;
  do {
    str.replace(start_pos, from.length(), to);
  } while ((start_pos = str.find(from)) != std::string::npos);
  return true;
}

bool replace(std::string &str, const char *from, const char *to) {
  size_t start_pos = str.find(from);
  if (start_pos == std::string::npos) return false;
  do {
    str.replace(start_pos, strlen(from), to);
  } while ((start_pos = str.find(from)) != std::string::npos);
  return true;
}

void ansi_clean(std::string &str) {
  static std::regex ansi_cleaner("\x1b\[[0-9;]*[A-Zmh]",
                                 std::regex_constants::ECMAScript);
  str = std::regex_replace(str, ansi_cleaner, "");
}

void high_ascii(std::string &str) {
  // the + replaces all of them into one.  I want each high ascii replaced with
  // #.
  static std::regex high_cleaner("[\x80-\xff]",
                                 std::regex_constants::ECMAScript);
  str = std::regex_replace(str, high_cleaner, "#");
}

std::smatch ansi_newline(const std::string &str) {
  static std::regex ansi_nl("\x1b\[[0-9;]*[JK]",
                            std::regex_constants::ECMAScript);
  std::smatch m;
  std::regex_search(str, m, ansi_nl);
  return m;
}

std::string repr(const std::string &source) {
  std::string output = source;

  replace(output, "\n", "\\n");
  replace(output, "\r", "\\r");
  replace(output, "\b", "\\b");
  replace(output, "\x1b", "\\[");
  high_ascii(output);
  return output;
}

std::string clean_string(const std::string &source) {
  std::string clean = source;
  /*
    replace(clean, "\n", "\\n");
    replace(clean, "\r", "\\r");
    replace(clean, "\b", "\\b");
    replace(clean, "\x1b", "\\[");
  */
  replace(clean, "\n", "");
  replace(clean, "\r", "");

  // ANSI too
  ansi_clean(clean);
  // BUGZ_LOG(error) << "cleaned: " << clean;
  high_ascii(clean);

  // replace(clean, "\x1b", "^");

  return clean;
}

std::vector<std::string> split(const std::string &line) {
  static std::regex rx_split("[^\\s]+");
  std::vector<std::string> results;

  for (auto it = std::sregex_iterator(line.begin(), line.end(), rx_split);
       it != std::sregex_iterator(); ++it) {
    results.push_back(it->str());
  }
  return results;
}

std::vector<std::string> split(const std::string &line, const std::string &by) {
  std::string work = line;
  std::vector<std::string> results;
  size_t pos;
  while ((pos = work.find(by)) != std::string::npos) {
    results.push_back(work.substr(0, pos));
    work.erase(0, pos + by.length());
  }
  if (!work.empty()) results.push_back(work);
  return results;
}

bool in(const std::string &line, const std::string &has) {
  return (line.find(has) != std::string::npos);
}

bool startswith(const std::string &line, const std::string &has) {
  return (line.substr(0, has.length()) == has);
}

bool endswith(const std::string &line, const std::string &has) {
  if (line.length() < has.length()) return false;
  return (line.substr(line.length() - has.length()) == has);
}

/**
 * Trim leading and trailing spaces from str.
 * 
 * @param str 
 */
void trim(std::string &str) {
  while (str.substr(0, 1) == " ") str.erase(0, 1);
  while (str.substr(str.length() - 1) == " ") str.erase(str.length() - 1);
}

bool at_command_prompt(const std::string &prompt) {
  if (startswith(prompt, "Command ["))
    if (endswith(prompt, "] (?=Help)? : ")) return true;
  return false;
}

bool at_computer_prompt(const std::string &prompt) {
  if (startswith(prompt, "Computer command ["))
    if (endswith(prompt, "] (?=Help)? ")) return true;
  return false;
}

bool at_planet_prompt(const std::string &prompt) {
  if (startswith(prompt, "Planet command (?=Help)"))
    if (endswith(prompt, " [D] ")) return true;
  return false;
}

bool density_clear(int sector, int density, int navhaz) {
  if (sector == 0) return false;
  // if(anomoly) return false;
  /*
  http://wiki.classictw.com/index.php?title=Gypsy%27s_Big_Dummy%27s_Guide_to_TradeWars_Text#Trader_Information
  Density Readings:
    0 = Empty Sector or Ferrengi Dreadanought
    1 = Marker Beacon
    2 = Limpet Type 2 Tracking Mine
    5 = Fighter (per Fighter)
   10 = Armid Type 1 Mine
   21 = Navigation Hazard (Per 1 Percent)
   21 = Destroyed Ship     (Due to 1 Percent Nav-Haz)
   38 = Unmanned Ship
   40 = Manned Ship, Alien or Ferrengi Assault Trader
   50 = Destroyed Starport (After 25 Percent Nav-Haz Clears)
  100 = Starport or Ferrengi Battle Cruiser
  210 = Destroyed Planet   (Due to 10 Percent Nav-Haz)
  462 = Federation Starship under Admiral Nelson
  489 = Federation Starship under Captain Zyrain
  500 = Planet
  512 = Federation Starship under Admiral Clausewitz
  575 = Destroyed Port (Before 25% Nav-Haz Clears)
  */
  int dense = density;
  if ((navhaz != 0) && (navhaz <= 5)) {
    // Adjust density by upto 5% navhaz, exlude greather than 5%
    dense -= navhaz * 21;
  }
  if (navhaz > 5) return false;
  switch (dense) {
    case 0:
    case 1:
    case 100:
    case 101:
      return true;
  }
  // Special case for Sector 001.
  if ((sector == 1) && (dense == 601)) return true;
  return false;
}

#include <algorithm>
#include <cctype>

void str_toupper(std::string &str) {
  std::transform(str.begin(), str.end(), str.begin(), ::toupper);
}

void str_tolower(std::string &str) {
  std::transform(str.begin(), str.end(), str.begin(), ::tolower);
}

void remove_telnet_commands(std::string &text) {
  size_t pos;

  while ((pos = text.find('\xff')) != std::string::npos) {
    text.erase(pos, pos + 3);
  }
}

int sstoi(const std::string &text, int failure) {
  int result;
  try {
    result = stoi(text);
  } catch (const std::invalid_argument &e) {
    // BUGZ_LOG(fatal) << e.what();
    return failure;
  } catch (const std::out_of_range &e) {
    // BUGZ_LOG(fatal) << e.what();
    return failure;
  }
  return result;
}

time_t time_t_now(void) {
  return std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());
}

// json support functions

bool json_bool(json j) {
  if (j.is_boolean()) {
    return j.get<bool>();
  }
  if (j.is_number_integer()) {
    return j.get<int>() == 1;
  }
  if (j.is_string()) {
    std::string temp = j.get<std::string>();
    char c = toupper(temp[0]);
    return ((c == 'Y') || (c == 'T'));
  }
  std::string error = "json_bool from ";
  error += j.type_name();  
  throw std::range_error(error);
}

std::string json_str(json j) {
  if (j.is_string()) return j.get<std::string>();
  if (j.is_number_integer()) {
    return std::to_string(j.get<int>());
  }
  std::string error = "json_str from ";
  error += j.type_name();
  throw std::range_error(error);
}

int json_int(json j) {
  if (j.is_number_integer()) return j.get<int>();
  if (j.is_string()) return sstoi(j.get<std::string>());
  std::string error = "json_int from ";
  error += j.type_name();
  throw std::range_error(error);
}