#include "./str_utils.h"
#include <sys/random.h>

bool isHexDigit(u_char c)
{
  return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F');
}

int fromHexDigit(u_char c)
{
  if (c >= '0' && c <= '9') return (int)(c - '0');
  if (c >= 'a' && c <= 'f') return (int)(c - 'a') + 10;
  if (c >= 'A' && c <= 'F') return (int)(c - 'A') + 10;
  return 0;
}

u_char toHexDigit(int x)
{
  if (x >= 0 && x <= 9) return '0' + x;
  if (x >= 10 && x <= 15) return 'a' + (x - 10);
  return '?';
}

u_char toOctalDigit(int x)
{
  if (x >= 0 && x <= 7) return '0' + x;
  return '?';
}

// isHexDigit inlined in str_utils.h


string indentString(string const &s, string const &indent)
{
  string ret;
  ret.reserve(s.size() * 2);
  ret += indent;

  for (size_t i = 0; i < s.size(); i++) {
    ret += s[i];
    if (i + 1 < s.size() && s[i] == '\n') {
      ret += indent;
    }
  }
  return ret;
}

string quoteStringJs(string const &s)
{
  string ret;
  ret += 0x22;
  for (u_char c : s) {
    if (c == (u_char)0x22) {
      ret += 0x5c;
      ret += 0x22;
    }
    else if (c == (u_char)0x5c) {
      ret += 0x5c;
      ret += 0x5c;
    }
    else if (c == (u_char)0x0a) {
      ret += 0x5c;
      ret += 'n';
    }
    else if (c < 0x20) {
      // Only ascii control characters are turned into \uxxxx escapes.
      // Multibyte characters just get passed through, which is legal.
      ret += 0x5c;
      ret += 'u';
      ret += '0';
      ret += '0';
      ret += toHexDigit((c >> 4) & 0x0f);
      ret += toHexDigit((c >> 0) & 0x0f);
    }
    else {
      ret += c;
    }
  }
  ret += 0x22;
  return ret;
}


string quoteStringC(string const &s)
{
  string ret;
  ret += 0x22;
  for (auto vi : s) {
    u_char c = vi;
    if (c == (u_char)0x22) {
      ret += 0x5c;
      ret += 0x22;
    }
    else if (c == (u_char)0x5c) {
      ret += 0x5c;
      ret += 0x5c;
    }
    else if (c == (u_char)0x0a) {
      ret += 0x5c;
      ret += 'n';
    }
    else if (c < 0x20) {
      ret += 0x5c;
      ret += toOctalDigit((c>>6) & 0x07);
      ret += toOctalDigit((c>>3) & 0x07);
      ret += toOctalDigit((c>>0) & 0x07);
    }
    else {
      ret += c;
    }
  }
  ret += 0x22;
  return ret;
}


string myRealpath(string const &path)
{
  const char *fullFnAlloc = realpath(path.c_str(), nullptr);
  if (!fullFnAlloc) return path;
  string fullFn(fullFnAlloc);
  free((void *)fullFnAlloc);
  return fullFn;
}

string myDirname(string const &path)
{
  auto slashPos = path.rfind('/');
  if (slashPos == string::npos) return path;
  return path.substr(0, slashPos);
}

string firstPathComponent(string const &path)
{
  auto slashPos = path.find('/');
  if (slashPos == string::npos) return ""s;
  return path.substr(0, slashPos);
}

string notFirstPathComponent(string const &path)
{
  auto slashPos = path.find('/');
  if (slashPos == string::npos) return path;
  return path.substr(slashPos+1);
}


string dotJoin(string const &a, string const &b)
{
  if (a.empty()) return b;
  if (b.empty()) return a;
  return a + "." + b;
}


/*
  Create a readable string of the given length from the data, which should be
  the output of a good hash function.
  Should be collision-resistant up to a small fraction of 58^(len/2) items
*/
string base58Hash(U8 *raw, size_t rawLen, size_t strLen)
{
  char const *base58 = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";

  string ret(strLen, '_');
  size_t mdi1 = 0, mdi2 = 1;
  for (size_t i = 0; i < strLen; i++) {
    if (0) L() << "  " << mdi1 << ", " << mdi2;
    ret[i] = base58[(
      (U32)raw[mdi1] + (U32)raw[mdi2] * 256
    ) % 58u];
    mdi1 += 2;
    while (mdi1 >= rawLen) mdi1 -= rawLen - 1;
    mdi2 += 2;
    while (mdi2 >= rawLen) mdi2 -= rawLen - 2;
  }
  return ret;
}

#ifdef HAVE_APR1
/*
  Create a readable string of the given length with a secure (SHA-1) hash of the data.
  Should be collision-resistant up to a small fraction of 58^(len/2) items
*/
string secureHashToIdentSuffix(string const &data, int len)
{
  const size_t MD_LEN = APR_SHA1_DIGESTSIZE;
  u_char md[MD_LEN];

  apr_sha1_ctx_t ctx;
  apr_sha1_init(&ctx);
  apr_sha1_update(&ctx, data.data(), data.size());
  apr_sha1_final(md, &ctx);

  return base58Hash(md, MD_LEN, len);
}
#endif


void writeFile(int fd, string const &contents, string const &errmsgfn)
{
  ssize_t nw = write(fd, contents.data(), contents.size());
  if (nw < 0) throw runtime_error("writeFile: "s + errmsgfn + ": "s + strerror(errno));
  if (size_t(nw) < contents.size()) throw runtime_error("writeFile: "s + errmsgfn + ": short write");
}

void writeFile(string const &fn, string const &contents)
{
  int fd = open(fn.c_str(), O_WRONLY|O_TRUNC|O_CREAT, 0777);
  if (fd < 0) throw runtime_error("writeFile: "s + fn + ": "s + strerror(errno));
  writeFile(fd, contents, fn);
  if (close(fd) < 0) throw runtime_error("writeFile: "s + fn + ": "s + strerror(errno));
}


string readFile(int fd, string const &errmsgfn)
{
  string ret;
  size_t retsize = 0;
  while (1) {
    ret.resize(retsize + 4096);
    ssize_t nr = read(fd, &ret[retsize], ret.size() - retsize);
    if (nr < 0) {
      throw runtime_error("readFile: "s + errmsgfn + ": "s + strerror(errno));
    }
    if (nr == 0) break;
    retsize += nr;
  }
  ret.resize(retsize);
  return ret;
}

string readFile(string const &fn)
{
  int fd = open(fn.c_str(), O_RDONLY, 0);
  if (fd < 0) throw runtime_error("readFile: Opening "s + fn + ": " + strerror(errno));
  auto ret = readFile(fd, fn);
  if (close(fd) < 0) throw runtime_error("CreadFile: losing "s + fn + ": " + strerror(errno));
  return ret;
}



bool writeFileIfChanged(string const &fn, string const &contents)
{
  int rfd = open(fn.c_str(), O_RDONLY, 0);
  if (rfd != -1) {
    string oldContents = readFile(rfd, fn);
    close(rfd);
    if (contents == oldContents) return false;
  }

  string tmpfn = fn + ".tmpXXXXXX";
  int wfd = mkstemp(tmpfn.data());

  if (wfd < 0) throw runtime_error("Opening "s + tmpfn + ": "s + strerror(errno));
  writeFile(wfd, contents, fn);
  if (close(wfd) < 0) throw runtime_error("Closing "s + tmpfn + ": "s + strerror(errno));

  if (rename(tmpfn.c_str(), fn.c_str()) < 0) throw runtime_error("Renaming "s + tmpfn + " to "s + fn + ": "s + strerror(errno));

  L() << "Wrote " << fn;
  return true;
}

string getRandTok(size_t len)
{
  char randChars[] = "abcdefghjkmnpqrstuvwxyz";

  vector<u_int> randBuf(len);
#if defined(__APPLE__) || defined(__EMSCRIPTEN__)
  if (getentropy(randBuf.data(), randBuf.size() * sizeof(u_int)) < 0) {
    throw runtime_error("getentropy: "s + strerror(errno));
  }
#else
  if (getrandom(randBuf.data(), randBuf.size() * sizeof(u_int), 0) < 0) {
    throw runtime_error("getrandom: "s + strerror(errno));
  }
#endif

  string ret(len, ' ');
  for (size_t i=0; i < len; i++) {
    ret[i] = randChars[randBuf[i] % 23u];
  }
  return ret;
}

string getTimeTok(struct tm *t)
{
  return stringprintf("%d%02d%02d_%02d%02d%02d_",
    t->tm_year + 1900,
    t->tm_mon + 1,
    t->tm_mday,
    t->tm_hour,
    t->tm_min,
    t->tm_sec) + getRandTok(3);
}

string getTimeStamp(struct tm *t)
{
  return stringprintf("%d-%02d-%02d %02d:%02d:%02d",
    t->tm_year + 1900,
    t->tm_mon + 1,
    t->tm_mday,
    t->tm_hour,
    t->tm_min,
    t->tm_sec);
}



bool needsShellEscape(string const &a)
{
  if (a.empty()) return true;
  for (auto &it : a) {
    if (!isalnum(it) && it != '_' && it != '-' && it != '.' && it != '/' && it != ':') return true;
  }
  return false;
}

string shellEscape(string const &a)
{
  if (!needsShellEscape(a)) return a;
  string ret;
  ret.reserve(a.size() * 2);
  ret += '"';
  for (auto &it : a) {
    if (it == '"' || it == '\'' || it == '\\') {
      ret += '\\';
      ret += it;
    }
    else if (it == '\n') {
      ret += '\\';
      ret += 'n';
    }
    else if (it < 32 || it >= 127) {
      ret += '\\';
      ret += '0' + ((((int)(u_char)it)>>6)&7);
      ret += '0' + ((((int)(u_char)it)>>3)&7);
      ret += '0' + ((((int)(u_char)it)>>0)&7);
    }
    else {
      ret += it;
    }
  }
  ret += '"';
  return ret;
}

string shellEscape(vector<string> const &args)
{
  string ret;
  for (auto &it : args) {
    if (!ret.empty()) ret += " ";
    ret += shellEscape(it);
  }
  return ret;
}

string cppEscape(string_view const &s)
{
  string ret;

  ret += 0x22;
  for (auto c : s) {
    if (c == (u_char)0x22) {
      ret += 0x5c;
      ret += 0x22;
    }
    else if (c == (u_char)0x5c) {
      ret += 0x5c;
      ret += 0x5c;
    }
    else if (c == (u_char)0x0a) {
      ret += 0x5c;
      ret += 'n';
    }
    else if (c < 0x20) {
      // Only ascii control characters are turned into \uxxxx escapes.
      // Multibyte characters just get passed through, which is legal.
      ret += 0x5c;
      ret += 'u';
      ret += '0';
      ret += '0';
      ret += toHexDigit((c >> 4) & 0x0f);
      ret += toHexDigit((c >> 0) & 0x0f);
    }
    else {
      ret += c;
    }
  }
  ret += 0x22;
  return ret;
}

string relativePath(string from, string to)
{
  if (from == to) {
    return "";
  }

  from = myRealpath(from);
  to = myRealpath(to);

  if (from == to) {
    return "";
  }

  int fromStart = 1;
  int fromEnd = from.size();
  while (fromStart < fromEnd && from[fromStart] == '/') fromStart++;
  int fromLen = fromEnd - fromStart;

  int toStart = 1;
  int toEnd = to.size();
  while (toStart < toEnd && to[toStart] == '/') toStart++;
  int toLen = toEnd - toStart;

  int len = min(fromLen, toLen);

  int lastCommonSep = -1;

  for (int i=0; i <= len; i++) {
    if (i == len) {
      if (toLen > len) {
        if (to[toStart + i] == '/') {
          return to.substr(toStart + i + 1);
        }
        else if (i == 0) {
          return to.substr(toStart + i);
        }
      }
      else if (fromLen > len) {
        if (from[fromStart + i] == '/') {
          lastCommonSep = i;
        }
        else if (i == 0) {
          lastCommonSep = 0;
        }
      }
      break;
    }
    if (from[fromStart + i] != to[toStart + i]) break;
    if (from[fromStart + i] == '/') {
      lastCommonSep = i;
    }
  }

  string out;
  for (int i = fromStart + lastCommonSep + 1; i <= fromEnd; i++) {
    if (i == fromEnd || from[i] == '/') {
      out += "..";
    }
    else {
      out += "/..";
    }
  }

  if (!out.empty()) {
    out += to.substr(toStart + lastCommonSep);
  }
  else {
    toStart += lastCommonSep;
    if (to[toStart] == '/') toStart++;
    out += to.substr(toStart);
  }
  return out;
}


void forEachLine(string const &buf, std::function<void(string const &s)> onLine)
{
  auto curIt = buf.begin();
  auto endIt = buf.end();
  while (curIt < endIt) {
    auto eolIt = find(curIt, endIt, (u_char)10);
    if (eolIt == endIt) {
      return;
    }

    onLine(string(curIt, eolIt));
    curIt = eolIt + 1;
  }
}


vector<string> splitChar(string const &buf, char sep)
{
  vector<string> ret;

  auto curIt = buf.begin();
  auto endIt = buf.end();
  while (curIt < endIt) {
    auto eolIt = find(curIt, endIt, sep);
    if (eolIt == endIt) {
      ret.emplace_back(curIt, eolIt);
      break;
    }
    ret.emplace_back(curIt, eolIt);
    curIt = eolIt + 1;
    if (curIt == endIt) {
      ret.push_back("");
      break;
    }
  }
  return ret;
}

vector<string> splitSpaces(string const &buf)
{
  return splitChar(buf, ' ');
}

vector<string> splitString(string const &buf, string const &sep)
{
  vector<string> ret;

  size_t curIt = 0;
  while (curIt < buf.size()) {
    auto eolIt = buf.find(sep, curIt);
    if (eolIt == string::npos) {
      ret.push_back(buf.substr(curIt));
      break;
    }
    ret.push_back(buf.substr(curIt, eolIt-curIt));
    curIt = eolIt + sep.size();
    if (curIt == buf.size()) {
      ret.push_back("");
      break;
    }
  }
  return ret;
}

tuple<string, string> splitAtDotNumber(string const &s, int dotCount)
{
  string seqName, subName;
  ssize_t start = 0;
  for (int doti = 0; doti < dotCount; doti++) {
    auto dotPos = s.find('.', start);
    if (dotPos == string::npos) {
      return make_tuple(s, ""s);
    }
    start = dotPos + 1;
  }

  return make_tuple(s.substr(0, start > 0 ? start - 1 : 0), s.substr(start));
}


string withoutBlankLines(string const &buf)
{
  string ret;
  ret.reserve(buf.size());
  bool bol = true;
  for (auto &c : buf) {
    if (c == '\n' && bol) {
    }
    else {
      ret += c;
      bol = (c == '\n');
    }
  }
  return ret;
}


// ----------------------------------------------------------------------


string joinChar(vector<string> const &tokens, char sep)
{
  string ret;
  bool needSep = false;
  for (auto &it : tokens) {
    if (needSep) ret += sep;
    needSep = true;
    ret += it;
  }
  return ret;
}

string joinString(vector<string> const &tokens, string const &sep)
{
  string ret;
  bool needSep = false;
  for (auto &it : tokens) {
    if (needSep) ret += sep;
    needSep = true;
    ret += it;
  }
  return ret;
}

string joinSpaces(vector<string> const &tokens)
{
  return joinChar(tokens, ' ');
}


bool startsWith(string const &s, string const &prefix)
{
  if (s.size() < prefix.size()) return false;
  if (s.compare(0, prefix.size(), prefix, 0, prefix.size()) == 0) return true;
  return false;
}

bool endsWith(string const &s, string const &suffix)
{
  if (s.size() < suffix.size()) return false;
  if (s.compare(s.size()-suffix.size(), suffix.size(), suffix, 0, suffix.size()) == 0) return true;
  return false;
}

string withNewSuffix(string const &s, string const &oldSuffix, string const &newSuffix)
{
  if (s.size() >= oldSuffix.size() && s.compare(s.size() - oldSuffix.size(), oldSuffix.size(), oldSuffix, 0, oldSuffix.size()) == 0) {
    return s.substr(0, s.size() - oldSuffix.size()) + newSuffix;
  }
  return "";
}

template<> string repr(char const *it)
{
  return string(it);
}

template<> string repr(bool const &it)
{
  return to_string(it);
}

template<> string repr(int const &it)
{
  return to_string(it);
}

template<> string repr(float const &it)
{
  char buf[64];
  snprintf(buf, sizeof(buf), "%.7g", it);
  return string(buf);
}

template<> string repr(double const &it)
{
  char buf[64];
  snprintf(buf, sizeof(buf), "%.15g", it);
  return string(buf);
}

template<> string repr(size_t const &it)
{
  return to_string(it);
}

string repr_g(double value)
{
  char buf[100];
  snprintf(buf, sizeof(buf), "%g", value);
  return buf;
}

string repr_0_1g(double value)
{
  char buf[100];
  snprintf(buf, sizeof(buf), "%0.1g", value);
  return buf;
}

string repr_0_2g(double value)
{
  char buf[100];
  snprintf(buf, sizeof(buf), "%0.2g", value);
  return buf;
}

string repr_0_3g(double value)
{
  char buf[100];
  snprintf(buf, sizeof(buf), "%0.3g", value);
  return buf;
}

string repr_0_6f(double value)
{
  char buf[100];
  snprintf(buf, sizeof(buf), "%0.6f", value);
  return buf;
}

string repr_0_3f(double value)
{
  char buf[100];
  snprintf(buf, sizeof(buf), "%0.3f", value);
  return buf;
}

string repr_0_1f(double value)
{
  char buf[100];
  snprintf(buf, sizeof(buf), "%0.1f", value);
  return buf;
}

string repr_02x(uint32_t value)
{
  char buf[100];
  snprintf(buf, sizeof(buf), "%02x", value);
  return buf;
}

string repr_04x(uint32_t value)
{
  char buf[100];
  snprintf(buf, sizeof(buf), "%04x", value);
  return buf;
}

string repr_08x(uint32_t value)
{
  char buf[100];
  snprintf(buf, sizeof(buf), "%08x", value);
  return buf;
}

string repr_016x(uint64_t value)
{
  char buf[100];
  snprintf(buf, sizeof(buf), "%16llx", (unsigned long long)value);
  return buf;
}

string repr_ptr(void * value)
{
  char buf[100];
  snprintf(buf, sizeof(buf), "%p", value);
  return buf;
}

string repr_filesize(size_t v)
{
  if (v < 4*1024) return to_string(v);
  if (v < 4*1024*1024) return to_string(v/1024) + "k";
  return to_string(v/1024/1024) + "M";
}

string repr_clock(long t)
{
  if (t < 0) return "-"s + repr_clock(-t);
#if defined(CLOCKS_PER_SEC)
  double tsec = (double)t/(double)CLOCKS_PER_SEC;
#else
  double tsec = t / 1000000.0;
#endif
  if (tsec < 1e-6) return repr_0_3f(1e9*tsec) + "ns";
  if (tsec < 1e-3) return repr_0_3f(1e6*tsec) + "us";
  if (tsec < 1.0) return repr_0_3f(1e3*tsec) + "ms";
  if (tsec < 10.0) return repr_0_1f(1e3*tsec) + "ms";
  return repr_0_3f(tsec) + "s";
}

string repr_clockper(long t, long iters)
{
  if (t < 0) return "-"s + repr_clockper(-t, iters);
#if defined(CLOCKS_PER_SEC)
  double tsec = (double)t/(double)CLOCKS_PER_SEC;
#else
  double tsec = t / 1000000.0;
#endif
  tsec /= iters;
  if (tsec < 1e-6) return repr_0_3f(1e9*tsec) + "ns" + " * " + repr(iters);
  if (tsec < 1e-3) return repr_0_3f(1e6*tsec) + "us" + " * " + repr(iters);
  if (tsec < 1.0) return repr_0_3f(1e3*tsec) + "ms" + " * " + repr(iters);
  if (tsec < 10.0) return repr_0_1f(1e3*tsec) + "ms" + " * " + repr(iters);
  return repr_0_3f(tsec) + "s" + " * " + repr(iters);
}

string repr_tscper(long t, long iters)
{
  if (t < 0) return "-"s + repr_tscper(-t, iters);
  double ticks = double(t) / double(iters);
  if (ticks > 1e7) return repr_0_1f(ticks/1e6) + "M ticks";
  if (ticks > 1e4) return repr_0_1f(ticks/1e3) + "k ticks";
  return repr_0_1f(ticks) + " ticks";
}


string repr_time(double t)
{
  if (t < 0) return "-"s + repr_time(-t);
  if (t < 0.00005) return repr_0_1f(t*1000000000.0) + "ns";
  if (t < 0.05) return repr_0_1f(t*1000000.0) + "us";
  return repr_0_3f(t) + "s";
}

string leftPadTo(string const &s, size_t width, char pad)
{
  if (s.size() >= width) return s;

  return string(width - s.size(), pad) + s;
}

string rightPadTo(string const &s, size_t width, char pad)
{
  if (s.size() >= width) return s;

  return s + string(width - s.size(), pad);
}


void console(string const &line)
{
  cerr << line + "\n";
}


string &NavPath::operator [](size_t i)
{
  if (i >= path.size()) {
    path.resize(i+1);
  }
  return path[i];
}

NavPath & NavPath::operator =(vector<string> const &_path)
{
  path = _path;
  return *this;
}

string NavPath::pop_front()
{
  if (path.size() < 1) return "";
  auto ret = path[0];
  path.erase(path.begin());
  return ret;
}


template<>
ostream &operator<<(ostream &s, vector<bool> const &it)
{
  s << "[";
  char const *sep = "";
  for (auto a : it) {
    s << sep << a;
    sep = " ";
  }
  s << "]";
  return s;
}


static mutex osMutex;

L::L(ostream &_outStream)
  : outStream(&_outStream),
    outLineq(nullptr)
{
  precision(3);
  //setf(ios::fixed, ios::floatfield);
}

L::L()
  : outStream(&cerr),
    outLineq(nullptr)
{
  precision(3);
  //setf(ios::fixed, ios::floatfield);
}

L::L(ostream &_outStream, lineq &_outLineq, U32 _color)
  : outStream(&_outStream),
    outLineq(&_outLineq),
    color(_color)
{
  precision(3);
}


L::~L()
{
  lock_guard lock(osMutex);
  auto s = str();
  if (!s.empty() && s[s.size()-1] == ' ') s.pop_back();
  if (!s.empty()) {
    if (outLineq) {
      outLineq->emplace_back(s, realtime(), color);
    }
    if (outStream) {
      if (s[s.size()-1] != '\n') s += '\n';
      *outStream << s;
    }
  }
}

