/***************************************************************************
                          chathistorywriter.cpp  -  chat logs writer
                             -------------------
    begin                : Tue Jul 13 2010
    copyright            : (C) 2010 by Valerio Pilo
    email                : valerio@kmess.org
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 ***************************************************************************/

#include "chathistorywriter.h"

#include "../network/chatmessage.h"
#include "../utils/kmessconfig.h"
#include "../utils/kmessshared.h"
#include "../kmessdebug.h"
#include "chathistorymanager.h"
#include "chatmessagestyle.h"

#include <QDir>
#include <QFileInfo>
#include <QtGlobal>


// Amount of bytes to read from the end of the file when searching for the closing tags
#define END_SEEKING_BUFFER_SIZE   64
// Closing conversation tag
#define CLOSING_TAG               "</conversation>"
// Closing conversation tag size (added statically since it never changes)
#define CLOSING_TAG_SIZE          15



// Constructor
ChatHistoryWriter::ChatHistoryWriter( const QString& account, const QString& handle )
: handle_( handle )
, isFirstMessage_( true )
, isNewLogFile_( true )
, lastMessage_( 0 )
, pointer_( 0 )
{
  conversation_ = new Conversation();
  messageStyle_ = new ChatMessageStyle();

  QString dir( KMessConfig::instance()->getAccountDirectory( account ) + "/chatlogs" );

  // Ensure that the logs directory exists
  QDir logsDir( dir );
  if( ! logsDir.exists() )
  {
    logsDir.mkdir( dir );

    // It couldn't be created
    if( ! logsDir.exists() )
    {
      kmWarning() << "Unable to create logs directory" << dir << "- cannot log any message!";
      return;
    }
  }


  QString lastFile;
  QString nextFile( KMessShared::nextSequentialFile( dir, handle, "xml", lastFile ) );

#ifdef KMESSDEBUG_CHATHISTORYWRITER
  kmDebug() << "Setting up log writer";
  kmDebug() << "Last logfile:" << lastFile;
  kmDebug() << "Next logfile:" << nextFile;
#endif

  QFileInfo fileInfo( lastFile );

  // Choose whether to continue writing on the last logfile, or to create a new one
  if( ! fileInfo.exists()
  ||  ! fileInfo.isWritable()
  ||    fileInfo.size() > MAX_LOG_FILE_SIZE )
  {
#ifdef KMESSDEBUG_CHATHISTORYWRITER
    kmDebug() << "Using new file" << nextFile << "to flush content";
#endif
    isNewLogFile_ = true;
    logFile_.setFileName( nextFile );
  }
  else
  {
#ifdef KMESSDEBUG_CHATHISTORYWRITER
    kmDebug() << "Using existing file" << lastFile << "to flush content";
#endif

    isNewLogFile_ = false;
    logFile_.setFileName( lastFile );
  }

  // Open the log file
  if( ! logFile_.open( QIODevice::ReadWrite ) )
  {
    kmWarning() << "Unable to open log file" << logFile_.fileName() << "- cannot log any message!";
    return;
  }

  qint64 currentFileSize = logFile_.size();

  // Add the headers to the new log file, or move to the end of the old file
  if( currentFileSize < 1 )
  {
    const QByteArray header( "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n"
                       "<!-- KMess chat log file -->\n"
                       "<messageRoot xmlns:xsl=\"http://www.w3.org/1999/XSL/Transform\">\n" );
    const QByteArray footer( "</messageRoot>\n" );

    logFile_.write( header + footer );
    pointer_ = header.length();
    logFile_.seek( pointer_ );
  }
  else
  {
    // Find the last conversation closing tag of the file, we'll append content from there
    qint64 searchStartPoint = qMax( currentFileSize - END_SEEKING_BUFFER_SIZE, (qint64)0L );
    logFile_.seek( searchStartPoint );
    qint64 tagPosition = logFile_.read( END_SEEKING_BUFFER_SIZE ).lastIndexOf( CLOSING_TAG );

    if( tagPosition < 0 )
    {
#ifdef KMESSDEBUG_CHATHISTORYWRITER
      kmDebug() << "Unable to open existing log file:" << logFile_.fileName() << "- unrecognized file format!";
#endif
      logFile_.close();
      return;
    }

    pointer_ = ( searchStartPoint + tagPosition + CLOSING_TAG_SIZE );
    logFile_.seek( pointer_ );
  }

  // Ready to log now!
}



// Destructor
ChatHistoryWriter::~ChatHistoryWriter()
{
  delete conversation_;
  delete messageStyle_;

  // Delete the log file if it was created just for this chat, and
  // no messages were appended to it
  if( logFile_.isOpen() && isNewLogFile_ && isFirstMessage_ )
  {
    logFile_.remove();
  }

  logFile_.close();

#ifdef KMESSDEBUG_CHATHISTORYWRITER
  kmDebug() << "Log file closed.";
#endif
}



// Return whether the writer is capable of writing new messages
bool ChatHistoryWriter::isReady() const
{
  return logFile_.isOpen();
}



// Write a message to file
void ChatHistoryWriter::logMessage( const ChatMessage& message )
{
  if( ! isReady() )
  {
#ifdef KMESSDEBUG_CHATHISTORYWRITER
    kmDebug() << "Unable to write the log message:" << message.getBody();
#endif
    return;
  }

  // Start the conversation if needed
  if( isFirstMessage_ )
  {
    // Don't log initial presence in messenger
    if( message.getType() == ChatMessage::TYPE_PRESENCE )
    {
      return;
    }

    // Add the new chat to the logs
//     ChatHistoryManager:: //no non qui, ogni msg ricevuto bisogna chiamare il manager x l'update della conv in corso

#ifdef KMESSDEBUG_CHATHISTORYWRITER
    kmDebug() << "Adding conversation tag..";
#endif

    quint64 timestamp = message.getDateTime().toTime_t();
    isFirstMessage_ = false;
    const QByteArray conversationHeader( "\n\n<conversation timestamp=\""
                                       + QString::number( timestamp ).toLatin1()
                                       + "\">\n" );
    logFile_.write( conversationHeader );
    pointer_ += conversationHeader.length();
    logFile_.flush();

    conversation_->filePath = logFile_.fileName();
    conversation_->timestamp = timestamp;
    conversation_->startPosition = logFile_.pos();
    conversation_->endPosition = logFile_.size();
  }

#ifdef KMESSDEBUG_CHATHISTORYWRITER
  kmDebug() << "Adding a message to chat:" << message.getBody();
#endif

  if( ! messageCache_.isEmpty() )
  {
#ifdef KMESSDEBUG_CHATHISTORYWRITER
    kmDebug() << "Cache not empty, checking message grouping";
#endif

    const ChatMessage& lastCached = messageCache_.last();

    // Verify if the new message can be grouped with the older ones in cache
    const QDateTime newMessageDate ( message.getDateTime() );
    const QDateTime lastMessageDate( lastCached.getDateTime() );

    // It's not groupable, write the new one alone (the cache is already on disk)
    if( ! message.isNormalMessage()
    ||    message.getContactHandle() != lastCached.getContactHandle()
    ||    message.getType() != lastCached.getType()
    ||  ! lastMessageDate.isValid() || lastMessageDate.secsTo( newMessageDate ) > 600 )
    {
#ifdef KMESSDEBUG_CHATHISTORYWRITER
      kmDebug() << "Message was not groupable, clearing cache first";
#endif

      pointer_ = logFile_.pos();
      messageCache_.clear();
    }
  }


  if( lastMessage_
  &&  lastMessage_->getType() == ChatMessage::TYPE_PRESENCE
  &&  lastMessage_->getType()          == message.getType()
  &&  lastMessage_->getContentsClass() == message.getContentsClass()
  &&  lastMessage_->getContactHandle() == message.getContactHandle() )
  {
#ifdef KMESSDEBUG_CHATHISTORYWRITER
    kmDebug() << "Not adding presence message for the same contact again.";
#endif
    return;
  }

  // Add the message and write the entire cache on file again
  delete lastMessage_;
  lastMessage_ = new ChatMessage( message );
  messageCache_.append( message );
  writeCache();

  // Signal the chat history manager the new chat message
  ChatHistoryManager::updateChatLog( handle_, *conversation_ );
}



// Write the cache contents to the file
void ChatHistoryWriter::writeCache()
{
  if( ! isReady() )
  {
#ifdef KMESSDEBUG_CHATHISTORYWRITER
    kmDebug() << "Unable to write the message cache";
#endif
    return;
  }

  // Nothing to do
  if( messageCache_.isEmpty() )
  {
    return;
  }

  logFile_.seek( pointer_ );

  // Write one message
  if( messageCache_.count() == 1 )
  {
    logFile_.write( messageStyle_->convertMessageToXml( messageCache_.first() ).toUtf8() );
  }
  // Write a message group
  else
  {
    QByteArray xml( "<messagegroup>\n" );

    foreach( const ChatMessage& message, messageCache_ )
    {
      xml += messageStyle_->convertMessageToXml( message ).toUtf8();
    }

    xml += "</messagegroup>\n";

    logFile_.write( xml );
    logFile_.flush();
  }

  // Save the point in the log file where the conversation ends
  conversation_->endPosition = logFile_.pos();

  const QByteArray conversationFooter( "</conversation>\n"
                                       "</messageRoot>\n" );

  logFile_.write( conversationFooter );

  // Following writes of new messageGroups will begin just after the closing messageGroup tag
  logFile_.seek( logFile_.pos() - conversationFooter.length() );

  logFile_.flush();
}


