

#include "tstream.h"
#include "tpersist.h"
#include "tfilepath_io.h"
#include "tconvert.h"
#include "tsystem.h"
#include "tutil.h"

#if defined(LZ4_STATIC)
#include "lz4frame_static.h"
#else
#include "lz4frame.h"
#endif

#include <sstream>
#include <memory>

//===============================================================
namespace {

std::string escape(std::string v) {
  int i = 0;
  for (;;) {
// Removing escaping of apostrophe from Windows and OSX as it's not needed and
// causes problems
#if defined(LINUX) || defined(FREEBSD)
    i = v.find_first_of("\\\'\"", i);
#else
    i = v.find_first_of("\\\"", i);
#endif
    if (i == (int)std::string::npos) break;
    std::string h = "\\";
    h += v[i];
    v.insert(i, "\\");
    i = i + 2;
  }
  return v;
}

//===============================================================

void writeCompressedFile(TFilePath dst, const std::string &str) {}

//===================================================================

void readCompressedFile(std::string &str, TFilePath src) {
  TFileStatus status(src);
  if (!status.doesExist()) return;

  size_t in_len = status.getSize();
  char *in      = (char *)malloc(in_len);
  {
    Tifstream is(src);
    is.read((char *)in, in_len);
  }

  LZ4F_decompressionContext_t lz4dctx;

  LZ4F_errorCode_t err =
      LZ4F_createDecompressionContext(&lz4dctx, LZ4F_VERSION);
  if (LZ4F_isError(err)) return;

  size_t in_len_read = 0;

  size_t out_len = 1000000, out_len_written, out_len_moved = 0;
  void *out = (void *)malloc(out_len);

  while (in_len_read < in_len) {
    out_len_written = out_len;

    size_t remaining =
        LZ4F_decompress(lz4dctx, out, &out_len_written, in, &in_len, NULL);
    if (LZ4F_isError(remaining)) break;

    str.resize(out_len_moved + out_len_written);

    memcpy((void *)(str.c_str() + out_len_moved), (void *)out, out_len_written);
    out_len_moved += out_len_written;
  }

  LZ4F_freeDecompressionContext(lz4dctx);

  free(in);
  free(out);
}

namespace {
// TODO: Unify with tcodec.cpp's version
bool lz4decompress(LZ4F_decompressionContext_t lz4dctx, char *out,
                   size_t *out_len_res, const char *in, size_t in_len) {
  size_t out_len = *out_len_res, in_read, out_written;

  *out_len_res = 0;

  while (in_len) {
    out_written = out_len;
    in_read     = in_len;

    size_t res = LZ4F_decompress(lz4dctx, (void *)out, &out_written,
                                 (const void *)in, &in_read, NULL);

    if (LZ4F_isError(res)) return false;

    *out_len_res += out_written;

    out += out_written;
    out_len -= out_written;

    in += in_read;
    in_len -= in_read;
  }

  return true;
}
}  // namespace

//===============================================================

class StreamTag {
public:
  std::string m_name;
  std::map<std::string, std::string> m_attributes;
  enum Type { BeginTag, EndTag, BeginEndTag };
  Type m_type;
  StreamTag() : m_type(BeginTag) {}

  operator bool() const { return m_name != ""; }

  void dump() {
    std::cout << "name = '" << m_name << "'" << std::endl;
    std::cout << "type = ";
    switch (m_type) {
    case BeginTag:
      std::cout << "begin Tag";
      break;
    case EndTag:
      std::cout << "end Tag";
      break;
    case BeginEndTag:
      std::cout << "begin/end Tag";
      break;
    default:
      std::cout << "**bad Tag**";
      break;
    }
    std::cout << std::endl;
    std::map<std::string, std::string>::iterator it;
    for (it = m_attributes.begin(); it != m_attributes.end(); ++it) {
      std::cout << " '" << it->first << "' = '" << it->second << "'"
                << std::endl;
    }
  }
};

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

class TPersistFactory {
  typedef std::map<std::string, TPersistDeclaration *> Table;
  static TPersistFactory *m_factory;
  Table m_table;
  TPersistFactory() {}

public:
  static TPersistFactory *instance() {
    if (!m_factory) m_factory = new TPersistFactory;
    return m_factory;
  }
  void add(std::string name, TPersistDeclaration *decl) {
    m_table[name] = decl;
  }
  TPersist *create(std::string name) {
    Table::iterator it = m_table.find(name);
    if (it != m_table.end())
      return (it->second)->create();
    else
      return 0;
  }
};

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

TPersistFactory *TPersistFactory::m_factory = 0;

}  // namespace

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

TPersistDeclaration::TPersistDeclaration(const std::string &id) : m_id(id) {
  TPersistFactory::instance()->add(id, this);
}

//===============================================================

TPersist *TPersist::create(const std::string &name) {
  return TPersistFactory::instance()->create(name);
}

//===============================================================

class TOStream::Imp {
public:
  std::ostream *m_os;
  bool m_chanOwner;
  bool m_compressed;
  std::ostringstream m_ostringstream;

  std::vector<std::string> m_tagStack;
  int m_tab;
  bool m_justStarted;
  typedef std::map<TPersist *, int> PersistTable;
  PersistTable m_table;
  int m_maxId;
  TFilePath m_filepath;

  Imp()
      : m_os(0)
      , m_chanOwner(false)
      , m_tab(0)
      , m_justStarted(true)
      , m_maxId(0)
      , m_compressed(false) {}
};

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

TOStream::TOStream(const TFilePath &fp, bool compressed) : m_imp(new Imp) {
  m_imp->m_filepath = fp;

  if (compressed) {
    m_imp->m_os         = &m_imp->m_ostringstream;
    m_imp->m_compressed = true;
    m_imp->m_chanOwner  = false;
  } else {
    std::unique_ptr<Tofstream> os(new Tofstream(fp));
    m_imp->m_os        = os->isOpen() ? os.release() : 0;
    m_imp->m_chanOwner = true;
  }

  m_imp->m_justStarted = true;
}

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

TOStream::TOStream(std::shared_ptr<Imp> imp) : m_imp(std::move(imp)) {
  assert(!m_imp->m_tagStack.empty());
  std::ostream &os = *m_imp->m_os;
  if (!m_imp->m_justStarted) cr();
  os << "<" << m_imp->m_tagStack.back() << ">";
  m_imp->m_tab++;
  cr();
  m_imp->m_justStarted = true;
}

TOStream::TOStream(TOStream &&that) : m_imp(std::move(that.m_imp)) {}

TOStream &TOStream::operator=(TOStream &&that) {
  if (this != &that) {
    this->m_imp = std::move(that.m_imp);
  }
  return *this;
}

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

TOStream::~TOStream() {
  if (!m_imp) {
    return;
  }
  try {
    if (!m_imp->m_tagStack.empty()) {
      std::string tagName = m_imp->m_tagStack.back();
      m_imp->m_tagStack.pop_back();
      assert(tagName != "");
      std::ostream &os = *m_imp->m_os;
      m_imp->m_tab--;
      if (!m_imp->m_justStarted) cr();
      os << "</" << tagName << ">";
      cr();
      m_imp->m_justStarted = true;
    } else {
      if (m_imp->m_compressed) {
        std::string tmp = m_imp->m_ostringstream.str();
        const void *in  = (const void *)tmp.c_str();

        size_t in_len = strlen((char *)in);

        size_t out_len = LZ4F_compressFrameBound(in_len, NULL);
        void *out      = malloc(out_len);

        out_len = LZ4F_compressFrame(out, out_len, in, in_len, NULL);
        if (!LZ4F_isError(out_len)) {
          Tofstream os(m_imp->m_filepath);
          // TNZC <decompressed data length> <compressed data length>
          // <compressed data>
          os.write("TABc", 4);
          TINT32 v;
          v = 0x0A0B0C0D;
          os.write((char *)&v, sizeof v);
          v = in_len;
          os.write((char *)&v, sizeof v);
          v = out_len;
          os.write((char *)&v, sizeof v);
          os.write((char *)out, out_len);
        }

        free(out);
      }
      if (m_imp->m_chanOwner) delete m_imp->m_os;
    }
  } catch (...) {
  }
}

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

TFilePath TOStream::getFilePath() { return m_imp->m_filepath; }

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

TFilePath TOStream::getRepositoryPath() {
  TFilePath fp = m_imp->m_filepath;
  return fp.getParentDir() + (fp.getName() + "_files");
}

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

TOStream &TOStream::operator<<(int v) {
  *(m_imp->m_os) << v << " ";
  m_imp->m_justStarted = false;
  return *this;
}

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

TOStream &TOStream::operator<<(double v) {
  if (areAlmostEqual(v, 0))  // with very small values (e.g. 1.4e-310) it cannot
                             // read them back!
    v = 0;

  *(m_imp->m_os) << v << " ";
  m_imp->m_justStarted = false;
  return *this;
}

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

TOStream &TOStream::operator<<(std::string v) {
  std::ostream &os = *(m_imp->m_os);
  int len          = v.length();
  if (len == 0) {
    os << "\"\"" << " ";
    m_imp->m_justStarted = false;
    return *this;
  }
  int i;
  for (i = 0; i < len; i++)
    if ((!iswalnum(v[i]) && v[i] != '_' && v[i] != '%') ||
        v[i] < 32      // Less than ASCII for SPACE
        || v[i] > 126  // Greater than ASCII for ~
    )
      break;
  if (i == len)
    os << v << " ";
  else {
    os << '"' << escape(v) << '"';
  }
  m_imp->m_justStarted = false;
  return *this;
}

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

TOStream &TOStream::operator<<(QString _v) {
  std::string v = _v.toStdString();

  std::ostream &os = *(m_imp->m_os);
  int len          = v.length();
  if (len == 0) {
    os << "\"\"" << " ";
    m_imp->m_justStarted = false;
    return *this;
  }
  int i;
  for (i = 0; i < len; i++)
    if ((!iswalnum(v[i]) && v[i] != '_' && v[i] != '%') ||
        v[i] < 32      // Less than ASCII for SPACE
        || v[i] > 126  // Greater than ASCII for ~
    )
      break;
  if (i == len)
    os << v << " ";
  else {
    os << '"' << escape(v) << '"';
  }
  m_imp->m_justStarted = false;
  return *this;
}

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

TOStream &TOStream::operator<<(std::wstring v) {
  return operator<<(::to_string(v));
  /*
std::ostream &os = *(m_imp->m_os);
int len = v.length();
if(len==0)
{
os << "\"" << "\"" << " ";
m_imp->m_justStarted = false;
return *this;
}
int i;
for(i=0;i<len;i++)
if(!iswalnum(v[i]) && v[i]!=L'_')
break;
if(i==len)
{
os << v;
os << " ";
}
else
{
os.put('"');
for(i=0;i<len;i++)
 if(iswalnum(v[i]))
   os.put((char)v[i]);
 else if(v[i]=='"')
   os << "\\\"";
 else if(v[i]=='\n')
   os << "\\n";
 else if(iswprint(v[i]))
   os << v[i];
 else
   {os.put('\\'); os << (int)v[i];}
os << "\" ";
}
m_imp->m_justStarted = false;
return *this;
*/
}

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

TOStream &TOStream::operator<<(const TFilePath &v) {
  return operator<<(v.getWideString());
}

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

TOStream &TOStream::operator<<(const TPixel32 &v) {
  std::ostream &os = *(m_imp->m_os);
  os << (int)v.r << " " << (int)v.g << " " << (int)v.b << " " << (int)v.m
     << " ";
  m_imp->m_justStarted = false;
  return *this;
}

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

TOStream &TOStream::operator<<(const TPixel64 &v) {
  std::ostream &os = *(m_imp->m_os);
  os << (int)v.r << " " << (int)v.g << " " << (int)v.b << " " << (int)v.m
     << " ";
  m_imp->m_justStarted = false;
  return *this;
}

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

void TOStream::cr() {
  *(m_imp->m_os) << std::endl;
  for (int i = 0; i < m_imp->m_tab; i++) *(m_imp->m_os) << "  ";
  m_imp->m_justStarted = false;
}

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

TOStream::operator bool() const { return (m_imp->m_os && *m_imp->m_os); }

//---------------------------------------------------------------
TOStream TOStream::child(std::string tagName) {
  assert(tagName != "");
  m_imp->m_tagStack.push_back(tagName);
  return TOStream(m_imp);
}

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

void TOStream::openChild(std::string tagName) {
  assert(tagName != "");
  m_imp->m_tagStack.push_back(tagName);
  if (m_imp->m_justStarted == false) cr();
  *(m_imp->m_os) << "<" << m_imp->m_tagStack.back() << ">";
  m_imp->m_tab++;
  cr();
  m_imp->m_justStarted = true;
}

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

void TOStream::openChild(std::string tagName,
                         const std::map<std::string, std::string> &attributes) {
  assert(tagName != "");
  m_imp->m_tagStack.push_back(tagName);
  if (m_imp->m_justStarted == false) cr();
  *(m_imp->m_os) << "<" << m_imp->m_tagStack.back();
  for (std::map<std::string, std::string>::const_iterator it =
           attributes.begin();
       it != attributes.end(); ++it) {
    *(m_imp->m_os) << " " << it->first << "=\"" << escape(it->second) << "\"";
  }
  *(m_imp->m_os) << ">";
  m_imp->m_tab++;
  cr();
  m_imp->m_justStarted = true;
}

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

void TOStream::closeChild() {
  assert(!m_imp->m_tagStack.empty());
  std::string tagName = m_imp->m_tagStack.back();
  m_imp->m_tagStack.pop_back();
  assert(tagName != "");
  // std::ostream &os = *m_imp->m_os; // os is not used
  m_imp->m_tab--;
  if (!m_imp->m_justStarted) cr();
  *(m_imp->m_os) << "</" << tagName << ">";
  cr();
  m_imp->m_justStarted = true;
}

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

void TOStream::openCloseChild(
    std::string tagName, const std::map<std::string, std::string> &attributes) {
  assert(tagName != "");
  // m_imp->m_tagStack.push_back(tagName);
  if (m_imp->m_justStarted == false) cr();
  *(m_imp->m_os) << "<" << tagName;
  for (std::map<std::string, std::string>::const_iterator it =
           attributes.begin();
       it != attributes.end(); ++it) {
    *(m_imp->m_os) << " " << it->first << "=\"" << escape(it->second) << "\"";
  }
  *(m_imp->m_os) << "/>";
  // m_imp->m_tab++;
  cr();
  m_imp->m_justStarted = true;
}

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

TOStream &TOStream::operator<<(TPersist &v) {
  v.saveData(*this);
  return *this;
}

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

TOStream &TOStream::operator<<(TPersist *v) {
  Imp::PersistTable::iterator it = m_imp->m_table.find(v);
  if (it != m_imp->m_table.end()) {
    *(m_imp->m_os) << "<" << v->getStreamTag() << " id='" << it->second
                   << "'/>";
    m_imp->m_justStarted = false;
  } else {
    int id            = ++m_imp->m_maxId;
    m_imp->m_table[v] = id;
    *(m_imp->m_os) << "<" << v->getStreamTag() << " id='" << id << "'>";
    m_imp->m_tab++;
    cr();
    v->saveData(*this);
    m_imp->m_tab--;
    cr();
    *(m_imp->m_os) << "</" << v->getStreamTag() << ">";
    cr();
  }
  return *this;
}

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

bool TOStream::checkStatus() const {
  if (!m_imp->m_os) return false;

  m_imp->m_os->flush();
  return m_imp->m_os->rdstate() == std::ios_base::goodbit;
}

std::string TOStream::getCurrentTagName() {
  return (m_imp->m_tagStack.empty()) ? "" : m_imp->m_tagStack.back();
}

//===============================================================
/*!
        This class contains TIStream's attributes.
        It is created by memory allocation in the TIStream's constructor.
*/
class TIStream::Imp {
public:
  std::istream *m_is;
  bool m_chanOwner;
  int m_line;
  std::string m_strbuffer;
  bool m_compressed;

  std::vector<std::string> m_tagStack;

  typedef std::map<int, TPersist *> PersistTable;
  PersistTable m_table;

  StreamTag m_currentTag;

  TFilePath m_filepath;

  VersionNumber m_versionNumber;

  Imp()
      : m_is(0)
      , m_chanOwner(false)
      , m_line(0)
      , m_compressed(false)
      , m_versionNumber(0, 0) {}

  // update m_line if necessary; returns -e if eof
  int getNextChar();

  inline void skipBlanks();
  bool matchTag();
  inline bool match(char c);
  bool matchIdent(std::string &ident);
  bool matchValue(std::string &value);

  void skipCurrentTag();
};

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

TFilePath TIStream::getFilePath() { return m_imp->m_filepath; }

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

TFilePath TIStream::getRepositoryPath() {
  TFilePath fp = m_imp->m_filepath;
  return fp.getParentDir() + (fp.getName() + "_files");
}

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

int TIStream::Imp::getNextChar() {
  char c;
  m_is->get(c);
  if (m_is->eof()) return -1;
  if (c == '\r') m_line++;
  return c;
}

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

void TIStream::Imp::skipBlanks() {
  std::istream &is = *m_is;
  std::istream::int_type c;
  while (c = is.peek(), (isspace(c) || c == '\r')) getNextChar();
}

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

bool TIStream::Imp::match(char c) {
  if (m_is->peek() == c) {
    getNextChar();
    return true;
  } else
    return false;
}

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

bool TIStream::Imp::matchIdent(std::string &ident) {
  std::istream &is = *m_is;
  if (!isalnum(is.peek())) return false;
  ident = "";
  char c;
  is.get(c);
  ident.append(1, c);
  while (c = is.peek(), isalnum(c) || c == '_' || c == '.' || c == '-') {
    is.get(c);
    ident.append(1, c);
  }
  return true;
}

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

bool TIStream::Imp::matchValue(std::string &str) {
  std::istream &is = *m_is;
  char quote       = is.peek();
  char c;
  if (!is || (quote != '\'' && quote != '\"')) return false;
  is.get(c);
  str = "";
  for (;;) {
    is.get(c);
    if (!is) throw TException("expected '\"'");
    if (c == quote) break;
    if (c == '\\') {
      is.get(c);
      if (!is) throw TException("unexpected EOF");
      if (c != '\'' && c != '\"' && c != '\\')
        throw TException("bad escape sequence");
    }
    str.append(1, c);
  }
  if (c != quote) throw TException("missing '\"'");
  return true;
}

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

bool TIStream::Imp::matchTag() {
  if (m_currentTag) return true;
  StreamTag &tag = m_currentTag;
  tag            = StreamTag();
  skipBlanks();
  if (!match('<')) return false;
  skipBlanks();
  if (match('!')) {
    skipBlanks();
    if (!match('-') || !match('-')) throw TException("expected '<!--' tag");
    std::istream &is = *m_is;
    char c;
    int status = 1;
    while (status != 0 && is.get(c)) switch (status) {
      case 1:
        if (c == '-') status = 2;
        break;
      case 2:
        if (c == '-')
          status = 3;
        else
          status = 1;
        break;
      case 3:
        if (c == '>')
          status = 0;
        else if (c == '-') {
        } else
          status = 1;
        break;
      }
    return matchTag();
  }
  if (match('/')) {
    tag.m_type = StreamTag::EndTag;
    skipBlanks();
  }

  if (!matchIdent(tag.m_name)) throw TException("expected identifier");
  skipBlanks();
  for (;;) {
    if (match('>')) break;
    if (match('/')) {
      tag.m_type = StreamTag::BeginEndTag;
      skipBlanks();
      if (match('>')) break;
      throw TException("expected '>'");
    }
    std::string name;
    if (!matchIdent(name)) throw TException("expected identifier");
    skipBlanks();
    if (match('=')) {
      std::string value;
      skipBlanks();
      if (!matchValue(value)) throw TException("expected value");
      tag.m_attributes[name] = value;
      skipBlanks();
    }
  }
  return true;
}

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

void TIStream::Imp::skipCurrentTag() {
  if (m_currentTag.m_type == StreamTag::BeginEndTag) return;
  std::istream &is = *m_is;
  int level        = 1;
  int c;
  for (;;) {
    if (is.eof()) break;  // unexpected eof
    c = is.peek();
    if (c != '<') {
      getNextChar();
      continue;
    }

    // tag found
    c = getNextChar();
    if (c < 0) break;

    c = getNextChar();
    if (c < 0) break;

    if (c == '/') {
      // end tag
      do c = getNextChar();
      while (c >= 0 && c != '>');
      if (c < 0) break;  // unexpected eof
      if (--level <= 0) {
        // m_currentTag.m_type = StreamTag::EndTag;
        m_tagStack.pop_back();
        m_currentTag = StreamTag();
        break;
      }
    } else {
      // tag
      int oldC;
      do {
        oldC = c;
        c    = getNextChar();
      } while (c >= 0 && c != '>');
      if (c < 0) break;  // unexpected eof
      if (oldC != '/') level++;
    }
  }
}

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

TIStream::TIStream(const TFilePath &fp) : m_imp(new Imp) {
  m_imp->m_filepath = fp;
  m_imp->m_is       = new Tifstream(fp);

  if (m_imp->m_is->peek() == 'T')  // does not start with '<' must be compressed
  {
    bool swapForEndianness = false;

    std::unique_ptr<std::istream> is(m_imp->m_is);
    m_imp->m_is = 0;

    char magicBuffer[4];
    is->read(magicBuffer, 4);
    std::string magic(magicBuffer, 4);
    size_t in_len, out_len;

    if (magic == "TNZC") {
      // Tab3.0 beta
      is->read((char *)&out_len, sizeof out_len);
      is->read((char *)&in_len, sizeof in_len);
    } else if (magic == "TABc") {
      TINT32 v;
      is->read((char *)&v, sizeof v);
      printf("magic = %08X\n", v);

      if (v == 0x0A0B0C0D)
        swapForEndianness = false;
      else if (v == 0x0D0C0B0A)
        swapForEndianness = true;
      else {
        swapForEndianness = true;
        printf("UH OH!\n");
      }

      is->read((char *)&v, sizeof v);
      out_len = swapForEndianness ? swapTINT32(v) : v;
      is->read((char *)&v, sizeof v);
      in_len = swapForEndianness ? swapTINT32(v) : v;
    } else
      throw TException("Bad magic number");

    if (in_len <= 0 ||
        in_len >
            100000000)  // 100M of tnzfile (compressed) seems really excessive
      throw TException("Corrupted file");

    LZ4F_decompressionContext_t lz4dctx;

    LZ4F_errorCode_t err =
        LZ4F_createDecompressionContext(&lz4dctx, LZ4F_VERSION);
    if (LZ4F_isError(err)) throw TException("Couldn't decompress file");

    char *in = (char *)malloc(in_len);
    is->read((char *)in, in_len);

    m_imp->m_strbuffer.resize(out_len + 1000);  // for safety
    char *out = (char *)m_imp->m_strbuffer.c_str();

    size_t check_len = out_len;

    // size_t remaining = LZ4F_decompress(lz4dctx, out, &out_len, in, &in_len,
    // NULL);
    bool ok = lz4decompress(lz4dctx, out, &out_len, in, in_len);
    LZ4F_freeDecompressionContext(lz4dctx);

    free(in);

    if (!ok) throw TException("Couldn't decompress file");

    if (check_len != out_len) throw TException("corrupted file");

    m_imp->m_is = new std::istringstream(std::string(out, out_len));
  }

  m_imp->m_chanOwner = true;
}

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

/*
TIStream::TIStream(std::istream &is)
         : m_imp(new Imp)
{
  m_imp->m_is = &is;
}
*/

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

TIStream::~TIStream() {
  if (m_imp->m_chanOwner) delete m_imp->m_is;
}

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

TIStream &TIStream::operator>>(int &v) {
  *(m_imp->m_is) >> v;
  return *this;
}

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

TIStream &TIStream::operator>>(double &v) {
  *(m_imp->m_is) >> v;
  return *this;
}
//---------------------------------------------------------------

TIStream &TIStream::operator>>(std::wstring &v) {
  std::string s;
  operator>>(s);
  v = ::to_wstring(s);
  return *this;
}

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

TIStream &TIStream::operator>>(std::string &v) {
  std::istream &is = *(m_imp->m_is);
  v                = "";
  m_imp->skipBlanks();
  char c;
  is.get(c);
  if (c == '\"') {
    is.get(c);
    while (is && c != '"') {
      if (c == '\\') {
        is.get(c);
        if (!is) throw TException("unexpected EOF");
        if (c == '"')
          v.append(1, '"');
        else if (c == '\\')
          v.append(1, '\\');
        else if (c == '\'')
          v.append(1, '\'');
        else {
          v.append(1, '\\');
          v.append(1, c);
        }
      } else
        v.append(1, c);
      is.get(c);
    }
  } else {
    v.append(1, c);
    while (c = is.peek(), isalnum(c) || c == '_' || c == '&' || c == '#' ||
                              c == ';' || c == '%') {
      is.get(c);
      v.append(1, c);
    }
  }

  return *this;
}

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

TIStream &TIStream::operator>>(QString &v) {
  std::istream &is = *(m_imp->m_is);
  v                = "";
  m_imp->skipBlanks();
  char c;
  is.get(c);
  if (c == '\"') {
    is.get(c);
    while (is && c != '"') {
      if (c == '\\') {
        is.get(c);
        if (!is) throw TException("unexpected EOF");
        if (c == '"')
          v.append('"');
        else if (c == '\\')
          v.append('\\');
        else if (c == '\'')
          v.append('\'');
        else {
          v.append('\\');
          v.append(c);
        }
      } else
        v.append(c);
      is.get(c);
    }
  } else {
    v.append(c);
    while (c = is.peek(), isalnum(c) || c == '_' || c == '&' || c == '#' ||
                              c == ';' || c == '%') {
      is.get(c);
      v.append(c);
    }
  }

  return *this;
}

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

std::string TIStream::getString() {
  std::istream &is = *(m_imp->m_is);
  std::string v    = "";
  m_imp->skipBlanks();
  char c = is.peek();
  while (c != '<') {
    is.get(c);
    c = is.peek();
    if (!is) throw TException("unexpected EOF");
    v.append(1, c);
  }
  return v;
}

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

TIStream &TIStream::operator>>(TPixel32 &v) {
  std::istream &is = *(m_imp->m_is);
  int r, g, b, m;
  is >> r;
  is >> g;
  is >> b;
  is >> m;
  v.r = r;
  v.g = g;
  v.b = b;
  v.m = m;
  return *this;
}
//---------------------------------------------------------------

TIStream &TIStream::operator>>(TPixel64 &v) {
  std::istream &is = *(m_imp->m_is);
  int r, g, b, m;
  is >> r;
  is >> g;
  is >> b;
  is >> m;
  v.r = r;
  v.g = g;
  v.b = b;
  v.m = m;
  return *this;
}

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

TIStream &TIStream::operator>>(TFilePath &v) {
  std::istream &is = *(m_imp->m_is);
  std::string s;
  char c;
  m_imp->skipBlanks();
  is.get(c);
  if (c == '"') {
    is.get(c);
    bool escapeChar = false;
    // If processing double-quote ("), if it's escaped, keep reading.
    while (is && (c != '"' || escapeChar)) {
      // if(c=='\\')
      //   is.get(c);
      if (c == '\\' && !escapeChar)
        escapeChar = true;
      else
        escapeChar = false;
      s.append(1, c);
      is.get(c);
    }
  } else {
    // the filepath is not in quotes: it can only contain alphanumeric
    // characters, % and _
    s.append(1, c);
    while (is) {
      c = is.peek();
      if (!isalnum(c) && c != '%' && c != '_') break;
      is.get(c);
      s.append(1, c);
    }
  }

  v = TFilePath(s);
  return *this;
}
//---------------------------------------------------------------

TIStream &TIStream::operator>>(TPersist &v) {
  v.loadData(*this);
  return *this;
}

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

TIStream &TIStream::operator>>(TPersist *&v) {
  if (!m_imp->matchTag() || m_imp->m_currentTag.m_type == StreamTag::EndTag) {
    throw TException("expected begin tag");
  }
  StreamTag tag       = m_imp->m_currentTag;
  m_imp->m_currentTag = StreamTag();
  std::string tagName = tag.m_name;
  std::map<std::string, std::string>::iterator it;
  int id = -1;
  it     = tag.m_attributes.find("id");
  if (it != tag.m_attributes.end()) id = atoi(it->second.c_str());
  // std::cout << "tagname = " << tagName << " id = " << id << std::endl;

  Imp::PersistTable::iterator pit = m_imp->m_table.find(id);
  if (pit == m_imp->m_table.end()) {
    v = TPersistFactory::instance()->create(tagName);
    if (!v) throw TException("unable to create a persistent '" + tagName + "'");
    m_imp->m_table[id] = v;
    if (tag.m_type != StreamTag::BeginTag)
      throw TException("expected begin tag");
    m_imp->m_tagStack.push_back(tag.m_name);
    v->loadData(*this);
    m_imp->matchTag();
    if (!m_imp->m_currentTag || m_imp->m_currentTag.m_type != StreamTag::EndTag)
      throw TException("expected end tag");
    if (m_imp->m_currentTag.m_name != m_imp->m_tagStack.back())
      throw TException("end tag mismatch");
    m_imp->m_tagStack.pop_back();
    m_imp->m_currentTag = StreamTag();
  } else {
    v = pit->second;
    if (tag.m_type != StreamTag::BeginEndTag)
      throw TException("expected begin/end tag");
  }
  return *this;
}

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

bool TIStream::matchEndTag() {
  if (m_imp->m_tagStack.empty()) throw TException("tag stack empty");
  if (!m_imp->matchTag()) return false;
  if (m_imp->m_currentTag.m_type != StreamTag::EndTag) return false;
  if (m_imp->m_currentTag.m_name != m_imp->m_tagStack.back())
    throw TException("end tag mismatch");
  m_imp->m_tagStack.pop_back();
  m_imp->m_currentTag = StreamTag();
  return true;
}

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

bool TIStream::eos() {
  if (m_imp->matchTag())
    return m_imp->m_currentTag.m_type == StreamTag::EndTag;
  else
    return !(*m_imp->m_is);
}

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

bool TIStream::matchTag(std::string &tagName) {
  if (!m_imp->matchTag()) return false;
  if (m_imp->m_currentTag.m_type == StreamTag::EndTag) return false;
  tagName                    = m_imp->m_currentTag.m_name;
  m_imp->m_currentTag.m_name = "";
  if (m_imp->m_currentTag.m_type != StreamTag::BeginEndTag)
    m_imp->m_tagStack.push_back(tagName);
  return true;
}

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

std::string TIStream::getTagAttribute(std::string name) const {
  StreamTag &tag = m_imp->m_currentTag;
  std::map<std::string, std::string>::const_iterator it =
      tag.m_attributes.find(name);
  if (it == tag.m_attributes.end())
    return "";
  else
    return it->second;
}

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

bool TIStream::getTagParam(std::string paramName, std::string &value) {
  if (m_imp->m_tagStack.empty()) return false;
  StreamTag &tag = m_imp->m_currentTag;
  std::map<std::string, std::string>::const_iterator it =
      tag.m_attributes.find(paramName);
  if (it == tag.m_attributes.end()) return false;
  value = it->second;
  return true;
}

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

bool TIStream::getTagParam(std::string paramName, int &value) {
  std::string svalue;
  if (!getTagParam(paramName, svalue)) return false;
  std::istringstream is(svalue);
  value = 0;
  is >> value;
  return true;
}

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

bool TIStream::isBeginEndTag() {
  return m_imp->m_currentTag.m_type == StreamTag::BeginEndTag;
}

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

bool TIStream::openChild(std::string &tagName) {
  if (!m_imp->matchTag()) return false;
  if (m_imp->m_currentTag.m_type != StreamTag::BeginTag) return false;
  tagName                    = m_imp->m_currentTag.m_name;
  m_imp->m_currentTag.m_name = "";
  m_imp->m_tagStack.push_back(tagName);
  return true;
}

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

void TIStream::closeChild() {
  if (!matchEndTag()) {
    std::string tagName;
    if (!m_imp->m_tagStack.empty()) tagName = m_imp->m_tagStack.back();
    if (tagName != "")
      throw TException("Expected \"" + tagName + "\" end tag");
    else
      throw TException("expected EndTag");
  }
}

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

bool TIStream::match(char c) const {
  m_imp->skipBlanks();
  if (m_imp->m_is->peek() != c) return false;
  m_imp->m_is->get(c);
  if (c == '\r') m_imp->m_line++;
  return true;
}

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

TIStream::operator bool() const { return (m_imp->m_is && *m_imp->m_is); }

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

int TIStream::getLine() const { return m_imp->m_line + 1; }

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

VersionNumber TIStream::getVersion() const { return m_imp->m_versionNumber; }

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

void TIStream::setVersion(const VersionNumber &version) {
  m_imp->m_versionNumber = version;
}

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

void TIStream::skipCurrentTag() { m_imp->skipCurrentTag(); }

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

std::string TIStream::getCurrentTagName() { return m_imp->m_tagStack.back(); }

//===============================================================
// MOVE CONSTRUCTORS AND ASSIGNMENTS FOR TIStream
//===============================================================

TIStream::TIStream(TIStream &&other) noexcept : m_imp(std::move(other.m_imp)) {}

TIStream &TIStream::operator=(TIStream &&other) noexcept {
  if (this != &other) {
    m_imp = std::move(other.m_imp);
  }
  return *this;
}
