/**
 * @file   convert.h
 * @brief  Declaration of miscellaneous convertion functions.
 * @author Andreas Aardal Hanssen
 * @date   2002-2005
 */

#ifndef convert_h_included
#define convert_h_included

#include "address.h"
#include "depot.h"

#include <cstring>
#include <iomanip>
#include <iostream>
#include <string>
#include <string_view>
#include <vector>

#include <sys/stat.h>

namespace Binc {

  inline std::string toHex(std::string_view s)
  {
    std::string_view hexchars = "0123456789abcdef";
    std::string tmp;
    tmp.reserve(s.length() * 2);

    for (char i : s) {
      auto c = static_cast<unsigned char>(i);
      tmp += hexchars[(c & 0xf0) >> 4];
      tmp += hexchars[c & 0x0f];
    }

    return tmp;
  }

  inline std::string fromHex(const std::string &s)
  {
    const char hexchars[] = "0123456789abcdef";
    std::string tmp;
    for (auto i = s.cbegin(); i != s.cend() && i + 1 != s.cend(); i += 2) {
      int n;
      unsigned char c = *i;
      unsigned char d = *(i + 1);

      const char *t;
      if ((t = strchr(hexchars, c)) == nullptr) return "out of range";
      n = (t - hexchars) << 4;

      if ((t = strchr(hexchars, d)) == nullptr) return "out of range";
      n += (t - hexchars);

      if (n >= 0 && n <= 255)
        tmp += (char)n;
      else
        return "out of range";
    }

    return tmp;
  }

  inline std::string toImapString(const std::string &s_in)
  {
    for (const auto i : s_in) {
      auto c = static_cast<unsigned char>(i);
      if (c <= 31 || c >= 127 || c == '\"' || c == '\\')
        return "{" + std::to_string(s_in.length()) + "}\r\n" + s_in;
    }

    return "\"" + s_in + "\"";
  }

  inline void uppercase(std::string &input)
  {
    for (auto &i : input)
      i = toupper(i);
  }

  inline void lowercase(std::string &input)
  {
    for (char &i : input)
      i = tolower(i);
  }

  inline void chomp(std::string &s_in, std::string_view chars = " \t\r\n")
  {
    int n = s_in.length();
    while (n > 1 && chars.find(s_in[n - 1]) != std::string::npos)
      s_in.resize(n-- - 1);
  }

  inline void trim(std::string &s_in, std::string_view chars = " \t\r\n")
  {
    while (s_in != "" && chars.find(s_in[0]) != std::string::npos)
      s_in = s_in.substr(1);
    chomp(s_in, chars);
  }

  inline void trim(std::string_view &s_in, std::string_view chars = " \t\r\n")
  {
    size_t n = 0;
    while (n < s_in.length() && chars.find(s_in[n]) != std::string_view::npos) {
      n++;
    }
    s_in = s_in.substr(n);

    if (s_in.empty()) return;

    n = s_in.length();
    while (n > 0 && chars.find(s_in[--n]) != std::string_view::npos) {
    }
    s_in = s_in.substr(0, n + 1);
  }

  inline const std::string unfold(const std::string &a, bool removecomment = true)
  {
    std::string tmp;

    bool incomment = false;
    bool inquotes = false;

    for (const char i : a) {
      unsigned char c = (unsigned char)i;
      if (!inquotes && removecomment) {
        if (c == '(') {
          incomment = true;
          tmp += " ";
        } else if (c == ')') {
          incomment = false;
        } else if (c != 0x0a && c != 0x0d) {
          tmp += i;
        }
      } else if (c != 0x0a && c != 0x0d) {
        tmp += i;
      }

      if (!incomment) {
        if (i == '\"') inquotes = !inquotes;
      }
    }

    trim(tmp);
    return tmp;
  }

  inline void split(const std::string &s_in,
                    const std::string &delim,
                    std::vector<std::string> &dest,
                    bool skipempty = true)
  {
    std::string token;
    for (const char i : s_in) {
      if (delim.find(i) != std::string::npos) {
        if (!skipempty || token != "") dest.push_back(token);
        token = "";
      } else {
        token += i;
      }
    }

    if (token != "") dest.push_back(token);
  }

  inline void splitAddr(const std::string &s_in,
                        std::vector<std::string> &dest,
                        bool skipempty = true)
  {
    static const std::string delim = ",";
    std::string token;
    bool inquote = false;
    for (const char i : s_in) {
      if (inquote && i == '\"')
        inquote = false;
      else if (!inquote && i == '\"')
        inquote = true;

      if (!inquote && delim.find(i) != std::string::npos) {
        if (!skipempty || token != "") dest.push_back(token);
        token = "";
      } else {
        token += i;
      }
    }
    if (token != "") dest.push_back(token);
  }

  inline std::string toCanonMailbox(const std::string &s_in)
  {
    if (s_in.find("..") != std::string::npos) return "";

    if (s_in.length() >= 5) {
      std::string a = s_in.substr(0, 5);
      uppercase(a);
      return a == "INBOX" ? a + (s_in.length() > 5 ? s_in.substr(5) : "") : s_in;
    }

    return s_in;
  }

  inline std::string toRegex(const std::string &s_in, char delimiter)
  {
    std::string regex = "^";
    for (const char i : s_in) {
      if (i == '.' || i == '[' || i == ']' || i == '{' || i == '}' || i == '(' || i == ')'
          || i == '^' || i == '$' || i == '?' || i == '+' || i == '\\')
      {
        regex += "\\";
        regex += i;
      } else if (i == '*') {
        regex += ".*?";
      } else if (i == '%') {
        regex += "(\\";
        regex += delimiter;
        regex += "){0,1}";
        regex += "[^\\";
        regex += delimiter;
        regex += "]*?";
      } else {
        regex += i;
      }
    }

    if (regex[regex.length() - 1] == '?')
      regex[regex.length() - 1] = '$';
    else
      regex += "$";

    return regex;
  }

  class BincStream {
  private:
    std::string nstr;

  public:
    BincStream &operator<<(std::ostream &(*)(std::ostream &));
    BincStream &operator<<(const std::string &t);
    BincStream &operator<<(unsigned long t);
    BincStream &operator<<(unsigned int t);
    BincStream &operator<<(int t);
    BincStream &operator<<(char t);

    std::string popString(size_t size);

    char popChar();
    void unpopChar(char c);
    void unpopStr(const std::string &s);

    const std::string &str() const;

    size_t getSize() const;

    void clear();

    BincStream();
    ~BincStream();
  };
}

#endif