#include <vcsn/misc/escape.hh>

#include <cctype>
#include <cstring> // strchr
#include <iomanip>
#include <iostream>
#include <sstream>

namespace vcsn
{
  // Accept int to catch EOF too.
  std::ostream&
  str_escape(std::ostream& os, const int c, const char* special)
  {
    std::ios_base::fmtflags flags = os.flags(std::ios_base::hex);
    char fill = os.fill('0');
    switch (c)
      {
      case -1:   os << "<end-of-file>"; break;
      case '\\': os << "\\\\";          break;
      case '\n': os << "\\n";           break;
      default:
        if (0 <= c && c <= 0177 && std::isprint(c))
          {
            if (special && strchr(special, c))
              os << '\\';
            os << uint8_t(c);
          }
        else
          // Turn into an unsigned, otherwise if c has its highest bit
          // set, it will be interpreted as a negative char, which
          // will be mapped to a negative int.  So for instance c =
          // 255 is mapped to \xFFFFFFF instead of \xFF.
          //
          // This happens only when char is "signed char".  Which is
          // common.
          os << "\\x" << std::setw(2) << int(uint8_t(c));
        break;
      }
    os.fill(fill);
    os.flags(flags);
    return os;
  }

  std::string
  str_escape(const int c, const char* special)
  {
    std::ostringstream o;
    str_escape(o, c, special);
    return o.str();
  }

  std::ostream&
  str_escape(std::ostream& o, const std::string& str, const char* special)
  {
    for (auto c: str)
      str_escape(o, c, special);
    return o;
  }

  std::string
  str_escape(const std::string& s, const char* special)
  {
    std::ostringstream o;
    str_escape(o, s, special);
    return o.str();
  }
}
