#include <memory>
#include <string>
#include <vector>
#include <algorithm>
#include <sstream>
#include <fstream>
#include <chrono>
#include <ctime>


#if defined(_WIN32)
#  define OS_PLATFORM  Windows
#elif defined(DARWIN)
#  define OS_PLATFORM  macOS
#else
#  define OS_PLATFORM  Linux
#endif


#ifndef _MSC_VER

namespace std
{
	template<typename T, typename ...Args>
	std::unique_ptr<T> make_unique( Args&& ...args )
	{
	    return std::unique_ptr<T>( new T( std::forward<Args>(args)... ) );
	}
}

inline int fopen_s(FILE** a_file, const char* a_fileName, const char* a_mode)
{
	*a_file = fopen(a_fileName, a_mode);
	return (*a_file != nullptr);
}

#endif


static std::string get_date_string(const char* fmtStr)
{
  char some_buffer[64];
  auto t = std::chrono::system_clock::now();
  auto as_time_t = std::chrono::system_clock::to_time_t(t);
  struct tm tm;
  //if (::gmtime_r(&as_time_t, &tm))
  if (::localtime_r(&as_time_t, &tm))
    // if (std::strftime(some_buffer, sizeof(some_buffer), "%F", &tm))
    if (std::strftime(some_buffer, sizeof(some_buffer), fmtStr, &tm))
      return std::string{some_buffer};
  throw std::runtime_error("Failed to get current date as string");
}


//! Reads in file contents of filename to dat
inline bool slurp(const char* filename, std::vector<uint8_t>& dat)
{
  std::ifstream file(filename, std::ifstream::ate | std::ifstream::binary);
  if (!file)
    return false;
  dat.resize(file.tellg());
  dat.assign((std::istreambuf_iterator<char>(file.seekg(0, std::ios::beg))), std::istreambuf_iterator<char>());
  return true;
}


inline static std::vector<std::string> split(const std::string &s, char delim)
{
	std::vector<std::string> elems;
	std::stringstream ss(s);
	std::string item;
	while (std::getline(ss, item, delim)) {
		elems.push_back(item);
	}
	return elems;
}


inline static std::string join(const std::vector<std::string>& words, std::string delim)
{
	std::string str;
  if (!words.empty())
  {
    for (const auto& word : words)
      str += word + delim;
    // remove trailing delim
    str.replace(str.rfind(delim), delim.length(), "");
  }
  return str;
}


inline static std::string trim(std::string str)
{
	str.erase(0, str.find_first_not_of(" \t\r\n"));
	str.erase(str.find_last_not_of(" \t\r\n") + 1);
	return str;
}


inline static bool endsWith(const std::string& first, const std::string& other)
{
  return (other.size() <= first.size()) && other == (first.substr(first.size() - other.size()));
}


inline static std::string str2lower(const std::string& str)
{
	std::string ret = str;
	std::transform(ret.begin(), ret.end(), ret.begin(), ::tolower);
	return ret;
}


// TODO: perhaps rename this function, it converts the string "#010203" to the int 0x010203
inline static unsigned int str2col(std::string str)
{
	if (str[0] == '#')
		str = &str[1];
	//return std::stoul(str, nullptr, 16);
	return strtoul(str.c_str(), nullptr, 16);
}


float static inline str2float(const std::string& str)
{
	return (float)atof(str.c_str());
}


// TODO: this doesn't work for any T, should work for int, perhaps need to add an std::enable_if
//       the oss version appears to be broken for uint8_t. I think it doesn't treat it as an integer type
//       but instead as a char type and does something different.
//       The hexChars version might be better
template<typename T>
inline static std::string int2hex(const T& value)
{
  std::string ret;
  char hexChars[17] = "0123456789ABCDEF";
  uint64_t val = (uint64_t)(value);
  for (int i = 0; i < sizeof(T)*2; i++)
  {
    ret = hexChars[val&0xF] + ret;
    val >>= 4;
  }
  return ret;
  // This looks good:  https://gist.github.com/miguelmota/4fc9b46cf21111af5fa613555c14de92
  /*
  std::ostringstream oss;
  oss << std::setfill('0') << std::setw(sizeof(T)*2) << std::hex << value;
  return oss.str();
  */
}


// https://stackoverflow.com/a/38885161  - this is best solution from performance point of view
template<template <typename...> class MAP, class KEY, class VALUE>
inline static std::vector<KEY> map2keys(const MAP<KEY, VALUE>& map)
{
  std::vector<KEY> result;
  result.reserve(map.size());
  for (const auto& it : map)
    result.emplace_back(it.first);
  return result;
}


#define ARRAY_SIZE(arr)  (sizeof(arr)/sizeof(arr[0]))


#define GENERATE_ENUM_SERIALIZATION_FUNCTIONS(EnumType, lastVal, defaultVal, ...) \
  const char* EnumType##Strings[] = { __VA_ARGS__ }; \
  static_assert((lastVal + 1) == ARRAY_SIZE(EnumType##Strings), "Please update the serialization for " #EnumType); \
  EnumType stringTo##EnumType(const std::string& str) \
  { \
    for (int i = 0; i < ARRAY_SIZE(EnumType##Strings); ++i) \
      if (str == EnumType##Strings[i]) \
        return (EnumType)i; \
    return defaultVal; \
  } \
  std::string stringFrom##EnumType(EnumType e) \
  { \
    if ((int)e >= 0 && (int)e < ARRAY_SIZE(EnumType##Strings)) \
      return EnumType##Strings[(int)e]; \
    return ""; \
  }


static float stringToFloat(const std::string& str)
{
	return (float)atof(str.c_str());
}


static std::string stringFromFloat(float val)
{
  std::string str = std::to_string(val);
  str = str.erase(str.find_last_not_of('0') + 1, std::string::npos);
  str = str.erase(str.find_last_not_of('.') + 1, std::string::npos);
  return str;
  //return std::to_string(val);
}


static int stringToInt(const std::string& str)
{
	return (int)atoi(str.c_str());
}


static std::string stringFromInt(int val)
{
  return std::to_string(val);
}


static std::string stringToString(const std::string& str)
{
	return str;
}


static std::string stringFromString(const std::string& str)
{
  return str;
}


static int stringToColor(const std::string& str)
{
  return (int)strtol(str.c_str() + 1, (char **)NULL, 16);
}


static std::string stringFromColor(int col)
{
  return "#" + int2hex(col);
}

