/***************************************************************************
**
**  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: 2004-10-20
**  Copyright: 2004-2019
**    Marc Wathelet
**    Marc Wathelet (ULg, Liège, Belgium)
**    Marc Wathelet (LGIT, Grenoble, France)
**
***************************************************************************/

#ifndef XMLCLASS_H
#define XMLCLASS_H

#include "QGpCoreToolsDLLExport.h"
#include "XMLStream.h"
#include "XMLContext.h"
#include "XMLMember.h"
#include "XMLSaveAttributes.h"
#include "Translations.h"
#include "StringSection.h"

namespace QGpCoreTools {

#define IMPL_XML_WRITE_PROPERTY(type, convert) \
void XMLClass::writeProperty(XMLStream& s, QString name, type content) \
{ \
  TRACE; \
  QString tmp=s.indent(); \
  tmp+=openBeginTagStr; \
  tmp+=name; \
  tmp+=endTagStr; \
  tmp+=convert; \
  tmp+=closeBeginTagStr; \
  tmp+=name; \
  tmp+=endTagStr; \
  s << tmp << ::endl; \
}

#define IMPL_XML_WRITE_PROPERTY_ATT(type, convert) \
void XMLClass::writeProperty(XMLStream& s, QString name, const XMLSaveAttributes& attributes, type content) \
{ \
  TRACE; \
  QString tmp=s.indent(); \
  tmp+=openBeginTagStr; \
  tmp+=name; \
  tmp+=attributes.toEncodedString(); \
  tmp+=endTagStr; \
  tmp+=convert; \
  tmp+=closeBeginTagStr; \
  tmp+=name; \
  tmp+=endTagStr; \
  s << tmp << ::endl; \
}

#define DECL_XML_WRITE_PROPERTY(type) \
static void writeProperty(XMLStream& s, QString name, type content);
#define DECL_XML_WRITE_PROPERTY_ATT(type) \
static void writeProperty(XMLStream& s, QString name, const XMLSaveAttributes& attributes, type content);

#define XML_WRITEPROPERTIES_ARGS XMLStream& s, XMLContext * context
#define XML_WRITECHILDREN_ARGS XMLStream& s, XMLContext * context
#define XML_MEMBER_ARGS StringSection& tag, const XMLRestoreAttributes& attributes, XMLContext * context
#define XML_SETPROPERTY_ARGS int memberID, StringSection& tag, const XMLRestoreAttributes& attributes, StringSection& content, XMLContext * context
#define XML_POLISH_ARGS XMLContext * context
#define XML_POLISHCHILD_ARGS XMLClass * child, XMLContext * context
#define XML_POLISHED_ARGS
#define XML_WRITEBINARYDATA_ARGS QDataStream& s, XMLContext * context
#define XML_SETBINARYDATA_ARGS QDataStream& s, XMLContext * context
#define XML_ATTRIBUTES_ARGS XMLSaveAttributes& attributes, XMLContext * context
#define XML_SETATTRIBUTES_ARGS const XMLRestoreAttributes& attributes, XMLContext * context

typedef QHash<StringSection, StringSection> XMLRestoreAttributes;
typedef QHash<StringSection, StringSection>::const_iterator XMLRestoreAttributeIterator;

class QGPCORETOOLS_EXPORT XMLClass
{
  TRANSLATIONS( "XMLClass" )
public:
  XMLClass() {}
  virtual ~XMLClass() {}

  enum Error {
    NoError,
    ErrorFileNotOpen,
    ErrorWritingFile,
    ErrorNoDocType,
    ErrorNoVersion,
    ErrorUnmatchedAmpSemiColon,
    ErrorUnknowSpecialCharacter,
    ErrorEmptyTag,
    ErrorUnmatchedTag,
    ErrorUnmatchedTopLevelTag,
    ErrorEmptyContextStack,
    ErrorParsingContent,
    ErrorParsingAttributes,
    ErrorSettingAttributes,
    ErrorSettingBinaryData,
    ErrorWrongBinaryOffset,
    ErrorWrongBinaryTag,
    ErrorBinaryFileNotFound,
    ErrorEndTruncatedTag,
    ErrorEndTruncatedString,
    ErrorEndTruncatedContext,
    ErrorEndStillInside,
    ErrorEndTagNotFound,
    ErrorPolish
  };

  enum FileType {
    TarFile,
    XmlFile
  };

  void xml_save(XMLStream& s, XMLContext * context, const XMLSaveAttributes& attributes=nullSaveAttributes) const;
  XMLClass::Error restore(const QChar *& ptr, int& line, XMLStream * s, XMLContext * context);

  inline virtual void xml_attributes(XML_ATTRIBUTES_ARGS) const;
  inline virtual bool xml_setAttributes(XML_SETATTRIBUTES_ARGS);
  inline virtual XMLMember xml_member(XML_MEMBER_ARGS);
  inline virtual void xml_polishChild(XML_POLISHCHILD_ARGS);

  virtual const QString& xml_tagName() const=0;
  virtual bool xml_isValidTagName(const QString& t) const {return t==xml_tagName();}
  virtual bool xml_inherits(const QString& tagName) const;

  static void skipBlanks(const QChar *& ptr, int& line);
  static QString saveSpecChar(QString content);
  static QString message(Error err, const QString& file=QString(), int line=0);

  // Functions that can be use to write information inside re-implemented functions (see below)
  DECL_XML_WRITE_PROPERTY(const char *)
  DECL_XML_WRITE_PROPERTY(QString)
  DECL_XML_WRITE_PROPERTY(QChar)
  DECL_XML_WRITE_PROPERTY(double)
  DECL_XML_WRITE_PROPERTY(Complex)
  DECL_XML_WRITE_PROPERTY(int)
  DECL_XML_WRITE_PROPERTY(uint)
  DECL_XML_WRITE_PROPERTY(qint64)
  DECL_XML_WRITE_PROPERTY(bool)
  DECL_XML_WRITE_PROPERTY_ATT(const char *)
  DECL_XML_WRITE_PROPERTY_ATT(QString)
  DECL_XML_WRITE_PROPERTY_ATT(QChar)
  DECL_XML_WRITE_PROPERTY_ATT(double)
  DECL_XML_WRITE_PROPERTY_ATT(Complex)
  DECL_XML_WRITE_PROPERTY_ATT(int)
  DECL_XML_WRITE_PROPERTY_ATT(uint)
  DECL_XML_WRITE_PROPERTY_ATT(qint64)
  DECL_XML_WRITE_PROPERTY_ATT(bool)
  static void writeProperty(XMLStream& s, QString name, const XMLSaveAttributes& attributes);
  static void writeChildren(XMLStream& s, QString name, QString children) {
    writeChildren(s, name, nullSaveAttributes, children);
  }
  static void writeChildren(XMLStream& s, QString name, const XMLSaveAttributes& attributes, QString children);
  void writeBinaryData(XMLStream& s, XMLContext * context) const;

  // Functions that can be re-implemented
  inline virtual void xml_writeProperties(XML_WRITEPROPERTIES_ARGS) const;
  inline virtual void xml_writeChildren(XML_WRITECHILDREN_ARGS) const;
  inline virtual bool xml_setProperty(XML_SETPROPERTY_ARGS);
  inline virtual bool xml_polish(XML_POLISH_ARGS);
  inline virtual bool xml_polished(XML_POLISHED_ARGS);
  inline virtual void xml_writeBinaryData(XML_WRITEBINARYDATA_ARGS) const;
  inline virtual bool xml_setBinaryData(XML_SETBINARYDATA_ARGS);
  inline virtual bool xml_setBinaryData200411(XML_SETBINARYDATA_ARGS) {return xml_setBinaryData(s, context);}

  static const QString openBeginTagStr;
  static const QString closeBeginTagStr;
  static const QString endTagStr;
  static const QString closeEndTagStr;
  static const QString trueStr;
  static const QString falseStr;
  static const QString binDataTag;
  static const QString binData200510Tag;
protected:
  static void qobject_writeProperties(const QObject * o, const XMLClass * xmlo, XML_WRITEPROPERTIES_ARGS);
  static XMLMember qobject_member(QObject * o, XML_MEMBER_ARGS);
  static bool qobject_setProperty(QObject * o, XML_SETPROPERTY_ARGS);
private:
  StringSection getPropertyName(const StringSection& tag, int& line);

  static int fileIndex;
  static const XMLSaveAttributes nullSaveAttributes;
};

inline void XMLMember::release()
{
  if(_child && _memberID==TemporaryChild) {
    delete _child;
    _child=nullptr;
  }
  if(_context) {
    delete _context;
  }
}

inline void XMLClass::xml_writeProperties(XML_WRITEPROPERTIES_ARGS) const
{
  Q_UNUSED(s)
  Q_UNUSED(context)
}

inline void XMLClass::xml_writeChildren(XML_WRITECHILDREN_ARGS) const
{
  Q_UNUSED(s)
  Q_UNUSED(context)
}

inline XMLMember XMLClass::xml_member(XML_MEMBER_ARGS)
{
  Q_UNUSED(tag)
  Q_UNUSED(attributes)
  Q_UNUSED(context)
  return XMLMember(XMLMember::Unknown);
}

inline  bool XMLClass::xml_setProperty(XML_SETPROPERTY_ARGS)
{
  Q_UNUSED(memberID)
  Q_UNUSED(tag)
  Q_UNUSED(attributes)
  Q_UNUSED(content)
  Q_UNUSED(context)
  return false;
}

inline bool XMLClass::xml_polish(XML_POLISH_ARGS)
{
  Q_UNUSED(context)
  return true;
}

inline void XMLClass::xml_polishChild(XML_POLISHCHILD_ARGS)
{
  Q_UNUSED(child)
  Q_UNUSED(context)
}

inline bool XMLClass::xml_polished(XML_POLISHED_ARGS)
{
  return false;
}

inline void XMLClass::xml_writeBinaryData(XML_WRITEBINARYDATA_ARGS) const
{
  Q_UNUSED(s)
  Q_UNUSED(context)
}

inline bool XMLClass::xml_setBinaryData(XML_SETBINARYDATA_ARGS)
{
  Q_UNUSED(s)
  Q_UNUSED(context)
  return false;
}

inline void XMLClass::xml_attributes(XML_ATTRIBUTES_ARGS) const
{
  Q_UNUSED(attributes)
  Q_UNUSED(context)
}

inline bool XMLClass::xml_setAttributes(XML_SETATTRIBUTES_ARGS)
{
  Q_UNUSED(attributes)
  Q_UNUSED(context)
  return true;
}

inline QString XMLSaveAttributes::toEncodedString() const
{
  QString tmp;
  for(const_iterator it=begin();it!=end();it++) {
    tmp += " ";
    tmp += it->key();
    tmp += "=\"";
    tmp += XMLClass::saveSpecChar(it->value());
    tmp += "\"";
  }
  return tmp;
}

} // namespace QGpCoreTools

#endif // XMLCLASS_H
