/***************************************************************************
**
**  This file is part of QGpCoreTools.
**
**  This library is free software; you can redistribute it and/or
**  modify it under the terms of the GNU Lesser General Public
**  License as published by the Free Software Foundation; either
**  version 2.1 of the License, or (at your option) any later version.
**
**  This file is distributed in the hope that it will be useful, but WITHOUT
**  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
**  FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
**  License for more details.
**
**  You should have received a copy of the GNU Lesser General Public
**  License along with this library; if not, write to the Free Software
**  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
**
**  See http://www.geopsy.org for more information.
**
**  Created: 2009-06-08
**  Copyright: 2009-2019
**    Marc Wathelet
**    Marc Wathelet (LGIT, Grenoble, France)
**
***************************************************************************/

#include "XMLHeader.h"
#include "XMLTarStream.h"
#include "XMLStringStream.h"
#include "XMLByteArrayStream.h"
#include "Version.h"
#include "QGpCoreToolsVersion.h"
#include "Global.h"
#include "CoreApplication.h"
#include "Trace.h"
#include "MemoryChecker.h"
#include "XMLGenericItem.h"

namespace QGpCoreTools {

/*!
  \class XMLHeader XMLHeader.h
  \brief Manage header of any XML content

  This class is part of XMLParser. It helps initiation of parsing the main object
  of an XML buffer. For usual cases it simply uses the object's tag. For compatibility
  issues, you can also specify alternate XML tags.

  In the past, this class was responsible of XML header: DOCTYPE and GpXMLClass context.
  GpXMLClass (or QtbXMLClass) context is kept only for compatibility with
  previous version. XML structures created with this version do not generate
  any specific header.
*/

const QString XMLHeader::xmlHeaderTag="GpXMLClass";
const QString XMLHeader::xmlHeaderOldTag="QtbXMLClass";

/*!
  Description of constructor still missing.
*/
XMLHeader::XMLHeader(XMLClass * object)
    : XMLClass()
{
  _object=object;
  _polished=false;
  _tag=xmlHeaderTag;
}

XMLHeader::XMLHeader(const XMLHeader& o)
  : XMLClass()
{
  _object=o._object;
  _polished=o._polished;
  _tag=o._tag;
  _alternateTags=o._alternateTags;
}

/*!
  Create a compressed tar file for storing the xml part (contents.xml) and,
  if any, the binary parts (random names stored in xml part).
*/
XMLClass::Error XMLHeader::xml_saveFile(QString fileName, XMLContext * context, FileType ft) const
{
  XMLSaveAttributes attributes;
  _object->xml_attributes(attributes, context);
  if(ft==TarFile) {
    XMLTarStream s (QIODevice::WriteOnly);
    if(s.open(fileName)) {
      //xml_saveDocType(s);
      _object->xml_save(s, context, attributes);
      if( !s.addXmlFile()) {
        App::log(tr("Cannot write to file %1.\n").arg(fileName) );
        return ErrorWritingFile;
      }
      return NoError;
    } else {
      App::log(tr("Cannot open file %1 for writing.\n").arg(fileName) );
      return ErrorFileNotOpen;
    }
  } else {
    QFile f(fileName);
    if(f.open(QIODevice::WriteOnly)) {
      XMLStringStream s (QIODevice::WriteOnly);
      //xml_saveDocType(s);
      _object->xml_save(s, context, attributes);
      QTextStream sf(&f);
      //sf.setCodec("ISO-8859-1");
      sf.setCodec("UTF-8");
      sf.setGenerateByteOrderMark (true);
      sf << s.toString();
      return NoError;
    } else {
      App::log(tr("Cannot open file %1 for writing.\n").arg(fileName) );
      return ErrorFileNotOpen;
    }
  }
}

/*!
  Create a QTextStream attached to the given QString and export XML data.
  No binary data can be saved this way.
*/
QString XMLHeader::xml_saveString(bool header, XMLContext * context) const
{
  TRACE;
  XMLStringStream s(QIODevice::WriteOnly);
  XMLSaveAttributes attributes;
  _object->xml_attributes(attributes, context);
  if(header) {
    //xml_saveDocType(s);
    _object->xml_save(s, context, attributes);
  } else {
    _object->xml_save(s, context, attributes);
  }
  return s.toString();
}

/*!
  Create a QByteArray which contains the XML part and the attached files
*/
QByteArray XMLHeader::xml_saveByteArray(XMLContext * context) const
{
  TRACE;
  XMLByteArrayStream s(QIODevice::WriteOnly);
  //xml_saveDocType(s);
  XMLSaveAttributes attributes;
  _object->xml_attributes(attributes, context);
  _object->xml_save(s, context, attributes);
  s.addXmlFile();
  return s.data();
}

/*!
  Open a file
*/
XMLClass::Error XMLHeader::xml_restoreFile(QString fileName, XMLContext * context, FileType ft)
{
  XMLTarStream s(QIODevice::ReadOnly);
  if(s.open(fileName)) {
    QByteArray xmlArray;
    if(ft==TarFile && s.file("contents.xml", xmlArray)) {
      QTextStream xmlStream(xmlArray, QIODevice::ReadOnly);
      //xmlStream.setCodec("ISO-8859-1");
      xmlStream.setCodec("UTF-8");
      xmlStream.setAutoDetectUnicode(true);
      QString buf=xmlStream.readAll();
      int line=1;
      const QChar * ptr=buf.data();
      if(checkDocType(ptr, line))
        return restore(ptr, line, &s, context);
      else
        return ErrorNoDocType;
    } else {  // check for compatibility with format before November 2006
      if(ft==TarFile) {
        App::log(1, tr("No contents.xml found in archive, trying old versions...\n"));
      }
      QFile f(fileName);
      if(f.open(QIODevice::ReadOnly)) {
        QTextStream xmlStream(&f);
        //xmlStream.setCodec("ISO-8859-1");
        xmlStream.setCodec("UTF-8");
        xmlStream.setAutoDetectUnicode(true);
        QString buf=xmlStream.readAll();
        int line=1;
        const QChar * ptr=buf.data();
        if(checkDocType(ptr, line))
          return restore(ptr, line, &s, context);
        else
          return ErrorNoDocType;
      }
      return ErrorFileNotOpen;
    }
  }
  return ErrorFileNotOpen;
}

XMLClass::Error XMLHeader::xml_restoreString(QString buf, XMLContext * context)
{
  int line=1;
  const QChar * ptr=buf.data();
  XMLStringStream s(QIODevice::ReadOnly);
  if(checkDocType(ptr, line))
    return restore(ptr, line, &s, context);
  else
    return ErrorNoDocType;
}

XMLClass::Error XMLHeader::xml_restoreByteArray(QByteArray data, XMLContext * context)
{
  QByteArray xmlArray;
  XMLByteArrayStream s(QIODevice::ReadOnly);
  if(!s.open(data) || !s.file("contents.xml", xmlArray)) {
    return ErrorFileNotOpen;
  }
  QTextStream xmlStream(xmlArray, QIODevice::ReadOnly);
  //xmlStream.setCodec("ISO-8859-1");
  xmlStream.setCodec("UTF-8");
  xmlStream.setAutoDetectUnicode (true);
  QString buf=xmlStream.readAll();
  int line=1;
  const QChar * ptr=buf.data();
  if(checkDocType(ptr, line))
    return restore(ptr, line, &s, context);
  else
    return ErrorNoDocType;
}

/*!
  Print the DOCTYPE line in header.
*/
void XMLHeader::xml_saveDocType(XMLStream& s)
{
  // No longer useful
  s << "<!DOCTYPE GpXMLClass>" << Qt::endl;
}

bool XMLHeader::checkDocType(const QChar *& ptr, int& line)
{
  TRACE;
  XMLClass::skipBlanks(ptr, line);
  // DOCTYPE is kept only for compatibility. Very old versions of XMLClass
  // did not include a DOCTYPE. From 20100127, there no DOCTYPE anymore.
  StringSection tmp(ptr, 9);
  if(tmp!="<!DOCTYPE") {
    // No DOCTYPE
    return true;
  }
  ptr += 9;
  XMLClass::skipBlanks(ptr, line);
  tmp.set(ptr, 10);
  if(tmp!=xmlHeaderTag) {
    tmp.set(ptr, 11);
    if(tmp!=xmlHeaderOldTag) {
      App::log(tr("Wrong DOCTYPE at line \n")+line+"\n");
      return false;
    }
    ptr++;
  }
  ptr += 10;
  XMLClass::skipBlanks(ptr, line);
  if(*ptr!=0x003E) { // '>'
    App::log(tr("Missing '>' at line \n")+line+"\n");
    return false;
  }
  ptr++;
  return true;
}

const QString& XMLHeader::xml_tagName() const
{
  return _tag;
}

void XMLHeader::xml_writeChildren(XML_WRITECHILDREN_ARGS) const
{
  ASSERT(_object);
  XMLSaveAttributes att;
  _object->xml_attributes(att, context);
  _object->xml_save(s, context, att);
}

XMLMember XMLHeader::xml_member(XML_MEMBER_ARGS)
{
  ASSERT(_object);
  Q_UNUSED(attributes)
  Q_UNUSED(context)
  if(tag==_object->xml_tagName()) {
    return XMLMember(_object);
  } else if(tag==xmlHeaderTag) {
    _tag=xmlHeaderTag;
    return XMLMember(this);
  } else if(tag==xmlHeaderOldTag) {
    _tag=xmlHeaderOldTag;
    return XMLMember(this);
  } else {
    for(QStringList::const_iterator it=_alternateTags.begin();it!=_alternateTags.end();it++) {
      if(tag==*it) {
        return XMLMember(_object);
      }
    }
    if(_object->xml_inherits(XMLGenericItem::xmlGenericTag)) {
      static_cast<XMLGenericItem *>(_object)->setTag(tag.toString());
      return XMLMember(_object);
    }
    return XMLMember(XMLMember::Unknown);
  }
}

void XMLHeader::xml_attributes(XML_ATTRIBUTES_ARGS) const
{
  Q_UNUSED(context)
  Q_UNUSED(attributes)
  //attributes.add( "version", QGPCORETOOLS_VERSION);
}

bool XMLHeader::xml_setAttributes(XML_SETATTRIBUTES_ARGS)
{
  Q_UNUSED(context)
  for(XMLRestoreAttributeIterator it=attributes.begin(); it!= attributes.end(); it++ ) {
    const StringSection& att=it.key();
    if(att=="version") {} // compatibility
    else return false;
  }
  return true;
}

void XMLHeader::xml_polishChild(XML_POLISHCHILD_ARGS)
{
  Q_UNUSED(context)
  if(child==_object) _polished=true;
}

} // namespace QGpCoreTools
