#include "string.h"

#include <stdarg.h>
#include <clocale>
#include <algorithm>
#include "types.h"
#include "error.h"

std::string strlower(std::string const& str) {
  std::string dest(str.size(), ' ');
  std::transform(str.begin(), str.end(), dest.begin(), std::tolower);
  return dest;
}

std::vector<std::string> split(std::string const& str, char sep) {
  std::vector<std::string> res;
  std::string cur;
  for (char c : str) {
    if (c == sep) {
      res.push_back(cur);
      cur.clear();
    } else {
      cur.push_back(c);
    }
  }
  res.push_back(cur);
  return res;
}

std::vector<std::string> split_multiple(std::string const& str, char const* sep) {
  std::vector<std::string> res;
  std::string cur;
  for (char c : str) {
    if (strchr(sep, c)) {
      res.push_back(cur);
      cur.clear();
    } else {
      cur.push_back(c);
    }
  }
  res.push_back(cur);
  return res;
}

std::vector<std::string> split(std::string const& str, char const* sep) {
  size_t sep_length = strlen(sep);
  std::vector<std::string> res;
  size_t pos = 0, next;
  while ((next = str.find(sep, pos)) != std::string::npos) {
    res.push_back(str.substr(pos, next - pos));
    pos = next + sep_length;
  }
  res.push_back(str.substr(pos));
  return res;
}

std::string join(std::vector<std::string> const& list, char sep) {
  std::string res;
  for (auto& str : list) {
    if (!res.empty()) res.push_back(' ');
    res.append(str);
  }
  return res;
}
std::string join(std::vector<std::string> const& list, std::string const& sep) {
  std::string res;
  for (auto& str : list) {
    if (!res.empty()) res.append(sep);
    res.append(str);
  }
  return res;
}
std::string fmtstring(char const* fmt, ...) {
  va_list ap;
  va_start(ap, fmt);
  std::string res = varfmtstring(fmt, ap);
  va_end(ap);
  return res;
}
std::string varfmtstring(char const* fmt, va_list list) {
  uint32 len = _vscprintf(fmt, list);
  std::string dst;
  dst.resize(len + 1);
  vsprintf(&dst[0], fmt, list);
  dst.resize(len);
  return dst;
}

std::wstring utf8_to_utf16(std::string const& str) {
  std::wstring dst;
  for (size_t i = 0; i < str.size();) {
    uint32 cp = (unsigned char) str[i++];
    size_t next = 0;
    if (cp <= 0x7F) {
      // do nothing
    } else if (cp <= 0xBF) {
      throw Exception("not a valid utf-8 string");
    } else if (cp <= 0xDF) {
      cp &= 0x1F;
      next = 1;
    } else if (cp <= 0xEF) {
      cp &= 0x0F;
      next = 2;
    } else if (cp <= 0xF7) {
      cp &= 0x07;
      next = 3;
    } else {
      throw Exception("not a valid utf-8 string");
    }
    while (next--) {
      if (i >= str.size() || str[i] < 0x80 || str[i] > 0xBF) {
        throw Exception("not a valid utf-8 string");
      }
      cp = (cp << 6) | (str[i++] & 0x3F);
    }
    if ((cp >= 0xD800 && cp <= 0xDFFF) || cp > 0x10FFFF) {
      throw Exception("not a valid utf-8 string");
    }

    if (cp <= 0xFFFF) {
      dst.push_back(cp);
    } else {
      cp -= 0x10000;
      dst.push_back((cp >> 10) + 0xD800);
      dst.push_back((cp & 0x3FF) + 0xDC00);
    }
  }
  return dst;
}

std::string utf16_to_utf8(std::wstring const& str) {
  std::string dst;
  for (size_t i = 0; i < str.size();) {
    uint32 cp = str[i++];
    if (cp >= 0xD800 && cp <= 0xDFFF) {
      if (cp >= 0xDC00) throw Exception("not a valid utf-16 string");
      if (i >= str.size() || str[i] < 0xDC00 || str[i] > 0xDFFF) throw Exception("not a valid utf-16 string");
      cp = 0x10000 + ((cp - 0xD800) << 10) + (str[i++] - 0xDC00);
    }
    if (cp >= 0x10FFFF) throw Exception("not a valid utf-16 string");
    if (cp <= 0x7F) {
      dst.push_back(cp);
    } else if (cp <= 0x7FF) {
      dst.push_back((cp >> 6) | 0xC0);
      dst.push_back((cp & 0x3F) | 0x80);
    } else if (cp <= 0xFFFF) {
      dst.push_back((cp >> 12) | 0xE0);
      dst.push_back(((cp >> 6) & 0x3F) | 0x80);
      dst.push_back((cp & 0x3F) | 0x80);
    } else {
      dst.push_back((cp >> 18) | 0xF0);
      dst.push_back(((cp >> 12) & 0x3F) | 0x80);
      dst.push_back(((cp >> 6) & 0x3F) | 0x80);
      dst.push_back((cp & 0x3F) | 0x80);
    }
  }
  return dst;
}

std::string trim(std::string const& str) {
  size_t left = 0, right = str.size();
  while (left < str.length() && isspace((unsigned char) str[left])) ++left;
  while (right > left && isspace((unsigned char) str[right - 1])) --right;
  return str.substr(left, right - left);
}
