#include "util.hpp"
#include "utf8.hpp"
#include <ctime>
#include <algorithm>
#include "upper2lower"

int util::printedLength(std::string_view s)
{
  return splitAsUtf8(s).size();
}

std::string_view util::getFilenameFromPath(std::string_view s)
{
  int indexOfSlash = s.find_last_of('/');
  return {s.data()+indexOfSlash+1, s.size()-1-indexOfSlash};
}

bool util::isSeparator(utf8char c)
{
  return c == ' ' || isIllegal(c);
}

bool util::isIllegal(utf8char c)
{
  return c == '\n' || c == '\t';
}

std::vector<std::string> util::split(std::string_view remaining, char delimiter)
{
  std::vector<std::string> result;

  for (auto firstDelimiterIndex = remaining.find_first_of(delimiter); firstDelimiterIndex != std::string_view::npos; firstDelimiterIndex = remaining.find_first_of(delimiter))
  {
    if (remaining[0] != delimiter)
      result.emplace_back(remaining.data(), firstDelimiterIndex);
    remaining = std::string_view(remaining.data()+firstDelimiterIndex+1, remaining.size()-1-firstDelimiterIndex);
  }

  if (remaining.size() > 0)
    result.emplace_back(remaining);

  return result;
}

util::utf8string util::splitAsUtf8(std::string_view s)
{
  utf8string result;
  const char * beginPtr = s.data();
  const char * currentPtr = beginPtr;
  const char * endPtr = s.data()+s.size();

  while (currentPtr < endPtr)
  {
    try {utf8::next(currentPtr, endPtr);}
    catch (std::exception &)
    {
      break;
    }
    if (currentPtr - beginPtr > 4 || currentPtr - beginPtr == 0)
      myThrow(fmt::format("Invalid utf8 character at index {}", beginPtr-s.data()));

    utf8char c;

    for (int i = 0; i < currentPtr - beginPtr; i++)
      c[i] = beginPtr[i];

    beginPtr = currentPtr;
    result.push_back(c);
  }

  return result;
}

std::string util::shrink(const std::string & s, int printedSize)
{
  static const std::string filler = "…";

  if (printedLength(s) <= printedSize)
    return s;

  auto splited = splitAsUtf8(s);

  std::string result;
  std::string begin, end;
  int nbLoop = 0;

  while (printedLength(begin)+printedLength(end)+printedLength(filler) <= printedSize)
  {
    result = begin + filler + end;

    if (nbLoop % 2)
      end = fmt::format("{}{}", splited[splited.size()-1-(nbLoop/2)], end);
    else
      begin = fmt::format("{}{}", begin, splited[nbLoop/2]);

    ++nbLoop;
  }

  return result;
}

void util::warning(std::string_view message, const std::experimental::source_location & location)
{
  fmt::print(stderr, "WARNING ({}) : {}\n", location, message);
}

void util::error(std::string_view message, const std::experimental::source_location & location)
{
  fmt::print(stderr, "ERROR ({}) : {}\n", location, message);
  exit(1);
}

void util::error(const std::exception & e, const std::experimental::source_location & location)
{
  error(e.what(), location);
}

void util::myThrow(std::string_view message, const std::experimental::source_location & location)
{
  throw std::invalid_argument(fmt::format("from ({}) {}", location, message));
}

std::string util::int2HumanStr(int number)
{
  std::string nb = std::to_string(number);
  std::string result;

  for (unsigned int i = 0; i < nb.size(); i++)
  {
    result.push_back(nb[i]);
    if (((nb.size()-i-1) % 3 == 0) && i < nb.size()-1)
      result.push_back(' ');
  }

  return result;
}

bool util::doIfNameMatch(const std::regex & reg, std::string_view name, const std::function<void(const std::smatch &)> & f)
{
  std::smatch sm;
  std::string sname(name);
  std::regex_match(sname, sm, reg);

  if (sm.empty())
    return false;

  f(sm);

  return true;
}

std::string util::strip(const std::string & s)
{
  std::string striped;

  if (s.empty())
    return striped;

  std::size_t first = 0;
  while (first < s.size() and (s[first] == ' ' or s[first] == '\t'))
    ++first;

  std::size_t last = s.size()-1;
  while (last > first and (s[last] == ' ' or s[last] == '\t' or s[last] == '\n'))
    --last;

  return std::string(s.begin()+first, s.begin()+last+1);
}

std::vector<std::filesystem::path> util::findFilesByExtension(std::filesystem::path directory, std::string extension)
{
  std::vector<std::filesystem::path> files;

  for (auto entry : std::filesystem::directory_iterator(directory))
    if (entry.is_regular_file())
    {
      auto path = entry.path();
      if (path.extension() == extension)
        files.push_back(path);
    }

  return files;
}

std::string util::getTime()
{
  std::time_t rawtime;
  char buffer[80];

  std::time(&rawtime);

  std::strftime(buffer, sizeof(buffer), "%H:%M:%S", std::localtime(&rawtime));
  return std::string(buffer);
}

bool util::choiceWithProbability(float probability)
{
  int maxVal = 100000;
  int threshold = maxVal * probability;

  return (std::rand() % maxVal) < threshold;
}

bool util::isUppercase(utf8char c)
{
  return upper2lower.count(c);
}

std::string util::lower(const std::string & s)
{
  auto splited = util::splitAsUtf8(s);
  for (auto & c : splited)
  {
    auto it = upper2lower.find(c);
    if (it != upper2lower.end())
      c = it->second;
  }

  return fmt::format("{}", splited);
}