#include "historyxmlio.h"

#include "history.h"

#include <QDateTime>
#include <QIODevice>
#include <QList>
#include <QXmlStreamReader>
#include <QXmlStreamWriter>
#include <QDebug>

static void writeHeader( const History &history, QXmlStreamWriter & writer )
{
  writer.writeStartElement( QLatin1String( "header" ) );

  writer.writeStartElement( QLatin1String( "local" ) );
  writer.writeAttribute( QLatin1String( "id" ), history.localContactId() );
  writer.writeEndElement();

  writer.writeStartElement( QLatin1String( "remote" ) );
  writer.writeAttribute( QLatin1String( "id" ), history.remoteContactId() );
  writer.writeEndElement();

  writer.writeEndElement();
}

static void writeMessages( const History &history, QXmlStreamWriter & writer )
{
  writer.writeStartElement( QLatin1String( "messages" ) );

  const History::Message::List messages = history.messages();
  foreach ( const History::Message &message, messages ) {
    writer.writeStartElement( QLatin1String( "message" ) );
    writer.writeAttribute( QLatin1String( "who" ), message.sender() );
    writer.writeAttribute( QLatin1String( "when" ),
                           message.timestamp().toString( Qt::ISODate ) );
    writer.writeCharacters( message.text() );
    writer.writeEndElement();
  }

  writer.writeEndElement();
}

bool HistoryXmlIo::writeHistoryToXml( const History &history, QIODevice *device )
{
  if ( device == 0 || !device->isWritable() )
    return false;

  QXmlStreamWriter writer( device );
  writer.setAutoFormatting( true );

  writer.writeStartDocument();

  writer.writeStartElement( QLatin1String( "history" ) );

  writeHeader( history, writer );
  writeMessages( history, writer );

  writer.writeEndElement();

  writer.writeEndDocument();

  return true;
}

static bool readHeader( QXmlStreamReader &reader, History &history )
{
  bool seenHeader = false;

  while ( !reader.atEnd() ) {
    reader.readNext();

    if ( reader.isStartElement() ) {
      if ( reader.name() == QLatin1String( "header" ) ) {
        seenHeader = true;
      } else if ( reader.name() == QLatin1String( "local" ) ) {
        if ( !seenHeader || !history.localContactId().isEmpty() )
          return false;

        const QXmlStreamAttributes attributes = reader.attributes();
        const QStringRef contactId = attributes.value( QLatin1String( "id" ) );
        if ( contactId.isEmpty() )
          return false;

        history.setLocalContactId( contactId.toString() );
      } else if ( reader.name() == QLatin1String( "remote" ) ) {
        if ( !seenHeader || !history.remoteContactId().isEmpty() )
          return false;

        const QXmlStreamAttributes attributes = reader.attributes();
        const QStringRef contactId = attributes.value( QLatin1String( "id" ) );
        if ( contactId.isEmpty() )
          return false;

        history.setRemoteContactId( contactId.toString() );
      } else {
        return false;
      }
    } else if ( reader.isEndElement() ) {
      if ( reader.name() == QLatin1String( "header" ) )
        break;
    }
  }

  return seenHeader && !history.localContactId().isEmpty() &&
         !history.remoteContactId().isEmpty();
}

static bool readMessages( QXmlStreamReader &reader, History &history )
{
  bool seenMessages = false;

  while ( !reader.atEnd() ) {
    reader.readNext();

    if ( reader.isStartElement() ) {
      if ( reader.name() == QLatin1String( "messages" ) ) {
        seenMessages = true;
      } else if ( reader.name() == QLatin1String( "message" ) ) {
        if ( !seenMessages )
          return false;

        const QXmlStreamAttributes attributes = reader.attributes();
        const QStringRef contactId  = attributes.value( QLatin1String( "who" ) );
        const QStringRef whenString = attributes.value( QLatin1String( "when" ) );
        if ( contactId.isEmpty() || whenString.isEmpty() )
          return false;

        const QDateTime timestamp =
            QDateTime::fromString( whenString.toString(), Qt::ISODate );
        if ( !timestamp.isValid() )
          return false;

        QString text = reader.readElementText();

        History::Message message;
        message.setSender( contactId.toString() );
        message.setTimestamp( timestamp );
        message.setText( text );

        history.addMessage( message );
      } else {
        return false;
      }
    } else if ( reader.isEndElement() ) {
      if ( reader.name() == QLatin1String( "messages" ) )
        break;
    }
  }

  return seenMessages;
}

bool HistoryXmlIo::readHistoryFromXml( QIODevice *device, History &history )
{
  if ( device == 0 || !device->isReadable() )
    return false;

  history = History();

  QXmlStreamReader reader( device );

  while ( !reader.atEnd() ) {
    reader.readNext();

    if ( reader.isStartElement() ) {
      if ( reader.name() == QLatin1String( "history" ) ) {
        if ( !readHeader( reader, history ) ) {
          qDebug() <<  "readHeader failed";
          history = History();
          return false;
        }

        if ( !readMessages( reader, history ) ) {
          qDebug() <<  "readMessages failed";
          history = History();
          return false;
        }

        break;
      } else {
        return false;
      }
    }
  }

  return true;
}

bool HistoryXmlIo::writeHistoryHeaderToXml( const History &history, QIODevice *device )
{
  if ( device == 0 || !device->isWritable() )
    return false;

  QXmlStreamWriter writer( device );
  writer.setAutoFormatting( true );

  writer.writeStartDocument();

  writeHeader( history, writer );

  writer.writeEndDocument();

  return true;
}

bool HistoryXmlIo::readHistoryHeaderFromXml( QIODevice *device, History &history )
{
  if ( device == 0 || !device->isReadable() )
    return false;

  QXmlStreamReader reader( device );

  History partialHistory;
  if ( !readHeader( reader, partialHistory ) )
    return false;

  // only overwrite header fields
  history.setLocalContactId( partialHistory.localContactId() );
  history.setRemoteContactId( partialHistory.remoteContactId() );

  return true;
}

bool HistoryXmlIo::writeHistoryMessagesToXml( const History &history, QIODevice *device )
{
  if ( device == 0 || !device->isWritable() )
    return false;

  QXmlStreamWriter writer( device );
  writer.setAutoFormatting( true );

  writer.writeStartDocument();

  writeMessages( history, writer );

  writer.writeEndDocument();

  return true;
}

bool HistoryXmlIo::readHistoryMessagesFromXml( QIODevice *device, History &history )
{
  if ( device == 0 || !device->isReadable() )
    return false;

  QXmlStreamReader reader( device );

  History partialHistory;
  if ( !readMessages( reader, partialHistory ) )
    return false;

  // save header fields and then overwrite external instance
  partialHistory.setLocalContactId( history.localContactId() );
  partialHistory.setRemoteContactId( history.remoteContactId() );

  history = partialHistory;

  return true;
}
