#ifndef UTIL__H
#define UTIL__H

#include <string>
#include <vector>
#include <array>
#include <unordered_map>
#include <regex>
#include <filesystem>
#include <experimental/source_location>
#include <boost/flyweight.hpp>
#include <boost/circular_buffer.hpp>
#include "fmt/core.h"
#include "utf8.hpp"
#include "utf8string.hpp"

namespace util
{
void warning(std::string_view message, const std::experimental::source_location & location = std::experimental::source_location::current());
void error(std::string_view message, const std::experimental::source_location & location = std::experimental::source_location::current());
void error(const std::exception & e, const std::experimental::source_location & location = std::experimental::source_location::current());
void myThrow(std::string_view message, const std::experimental::source_location & location = std::experimental::source_location::current());

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

std::string_view getFilenameFromPath(std::string_view s);

std::vector<std::string> split(std::string_view s, char delimiter);

utf8string splitAsUtf8(std::string_view s);

std::string int2HumanStr(int number);

std::string shrink(const std::string & s, int printedSize);

std::string strip(const std::string & s);

int printedLength(std::string_view s);

bool isSeparator(utf8char c);

bool isIllegal(utf8char c);

bool isUppercase(utf8char c);

bool isUrl(const std::string & s);
bool isNumber(const std::string & s);

std::string getTime();

template <typename T>
bool isEmpty(const std::vector<T> & s)
{
  return s.empty();
}

template <typename T>
bool isEmpty(const std::basic_string<T> & s)
{
  return s.empty();
}

template <typename T>
std::size_t getSize(const std::vector<T> & s)
{
  return s.size();
}

template <typename T>
std::size_t getSize(const boost::flyweight<T> & s)
{
  return getSize(s.get());
}

template <typename T>
bool isEmpty(const boost::flyweight<T> & s)
{
  return isEmpty(s.get());
}

bool doIfNameMatch(const std::regex & reg, std::string_view name, const std::function<void(const std::smatch &)> & f);

bool choiceWithProbability(float probability);

std::string lower(const std::string & s);

void lower(utf8string & s);

utf8string lower(const utf8string & s);

void lower(utf8char & c);

std::string upper(const std::string & s);

void upper(utf8string & s);

utf8string upper(const utf8string & s);

void upper(utf8char & c);

template <typename T>
std::string join(const std::string & delim, const std::vector<T> elems)
{
  std::string result;

  for (unsigned int i = 0; i < elems.size(); i++)
    result = fmt::format("{}{}{}", result, elems[i], i == elems.size()-1 ? "" : delim);

  return result;
}

template <typename T>
std::string join(const std::string & delim, const boost::circular_buffer<T> elems)
{
  std::string result;

  for (unsigned int i = 0; i < elems.size(); i++)
    result = fmt::format("{}{}{}", result, elems[i], i == elems.size()-1 ? "" : delim);

  return result;
}

template <typename K, typename V>
std::map<V, K> inverseMap(const std::map<K, V> & model)
{
  std::map<V, K> res;
  for (auto & it : model)
    res[it.second] = it.first;

  return res;
}

};

template <>
struct fmt::formatter<std::experimental::source_location>
{
  constexpr auto parse(format_parse_context & ctx) { return ctx.begin(); }

  template <typename FormatContext>
  auto format(const std::experimental::source_location & d, FormatContext & ctx)
  {
    return format_to(ctx.out(), "{},l.{},'{}'", util::getFilenameFromPath(d.file_name()), d.line(), d.function_name());
  }
};

template <typename T>
struct fmt::formatter<boost::flyweight<T>>
{
  constexpr auto parse(format_parse_context & ctx) { return ctx.begin(); }

  template <typename FormatContext>
  auto format(const boost::flyweight<T> & s, FormatContext & ctx)
  {
    return format_to(ctx.out(), "{}", s.get());
  }
};

#endif