/***************************************************************************
                          chathistorydialog.cpp  -  chat logs browser
                             -------------------
    begin                : Sun Feb 22 2009
    copyright            : (C) 2009 by Dario Freddi
    email                : drf54321@gmail.com
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   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 "chathistorydialog.h"

#include "../chat/chathistorymanager.h"
#include "../chat/chatmessagestyle.h"
#include "../chat/chatmessageview.h"
#include "../utils/kmessconfig.h"
#include "../utils/kmessshared.h"
#include "../accountsmanager.h"
#include "../currentaccount.h"
#include "../kmess.h"
#include "../kmessapplication.h"
#include "../kmessdebug.h"

#include <QDir>
#include <QLayout>
#include <QTimer>
#include <QStandardItemModel>
#include <QSortFilterProxyModel>
#include <QtXml/QDomDocument>
#include <QtXml/QDomNode>
#include <QClipboard>
#include <QTextCharFormat>

#include <KApplication>
#include <KMessageBox>
#include <KLocalizedString>
#include <KDateTime>
#include <KMenu>
#include <KAction>
#include <KHTMLView>
#include <KStandardAction>


#ifdef KMESSDEBUG_CHATHISTORYDIALOG
  #define KMESSDEBUG_CHATHISTORYDIALOG_VERBOSE
#endif


// Constructor
ChatHistoryDialog::ChatHistoryDialog( QWidget *parent )
: KDialog( parent )
, model_( new QStandardItemModel( this ) )
, proxyModel_( new QSortFilterProxyModel( this ) )
{
  // Set up the dialog
  QWidget *mainWidget = new QWidget( this );
  setupUi( mainWidget );
  setMainWidget( mainWidget );
  setButtons( Help | Close );
  setHelp( "using-chat-history" );
  setWindowTitle( i18nc( "Dialog window title", "Chat History" ) );
  loadingLabel_->hide();

  // Let the dialog destroy itself when it's done
  setAttribute( Qt::WA_DeleteOnClose );

  // Set a nice icon
  setWindowIcon( KMessShared::drawIconOverlay( KIconLoader::global()->loadIcon( "kmess", KIconLoader::Panel ), KIcon( "chronometer" ).pixmap( 20, 20 ) ) );

  // Create the chat view to show the chat logs
  chatView_ = new ChatMessageView( mainWidget );
  connect( chatView_, SIGNAL(                popupMenu(const QString&,const QPoint&)  ),
           this,      SLOT  (      slotShowContextMenu(const QString&,const QPoint&)  ) );

  chatView_->updateChatStyle();
  chatView_->getStyle()->setAllowEmoticonLinks( false );
  frame_->layout()->addWidget( chatView_->widget() );

  // Make the splitter assign more space to the view than to the controls,
  // and set its minimum size
  QSizePolicy sizePolicy( QSizePolicy::Preferred, QSizePolicy::Expanding );
  sizePolicy.setVerticalStretch( 100 );
  chatView_->widget()->setMinimumSize( 300, 200 );
  chatView_->widget()->setSizePolicy( sizePolicy );

  // Fill the accounts list
  accountComboBox_->insertItems( 0, KMessConfig::instance()->getAccountsList() );

  // If no accounts are available, disable the dialog
  if( accountComboBox_->count() == 0 )
  {
    mainWidget->setEnabled( false );
  }
  else
  {
    KMessApplication *kmessApp = static_cast<KMessApplication*>( kapp );

    // When disconnected or using a guest account, the accounts list will default to the
    // first available account
    if( ! kmessApp->getContactListWindow()->isConnected()
    ||    CurrentAccount::instance()->isGuestAccount() )
    {
      const QString &currentHandle( accountComboBox_->itemText( 0 ) );
      accountComboBox_->setCurrentItem( currentHandle );
    }
    else
    {
      accountComboBox_->setCurrentItem( CurrentAccount::instance()->getHandle() );
    }
  }

  // Initialize the model for the list of contacts
  proxyModel_->setSourceModel( model_ );
  proxyModel_->setDynamicSortFilter( true );
  proxyModel_->setFilterCaseSensitivity( Qt::CaseInsensitive );

  contactListView_->setModel( proxyModel_ );

  QItemSelectionModel *selectionModel = contactListView_->selectionModel();

  // Attach the signals
  connect( searchEdit_,      SIGNAL(         textChanged(const QString&)                        ),
           proxyModel_,      SLOT  (     setFilterRegExp(const QString&)                        ) );
  connect( selectionModel,   SIGNAL(      currentChanged(const QModelIndex&,const QModelIndex&) ),
           this,             SLOT  (  showAvailableDates(const QModelIndex&)                    ) );
  connect( datePicker_,      SIGNAL(           activated(const QDate&)                          ),
           this,             SLOT  (            showLogs(const QDate&)                          ) );
  connect( datePicker_,      SIGNAL(             clicked(const QDate&)                          ),
           this,             SLOT  (            showLogs(const QDate&)                          ) );
  connect( accountComboBox_, SIGNAL( currentIndexChanged(int)                                   ),
           this,             SLOT  (  slotAccountChanged(int)                                   ) );

  // Save the dialog and splitters sizes
  KConfigGroup group = KMessConfig::instance()->getGlobalConfig( "ChatHistoryDialog" );
  restoreDialogSize( group );
  splitter_ ->setSizes( group.readEntry( "mainSplitterSizes",  QList<int>() << 1 << 100 ) );

  // Normally the focus would be set on the Search box, thus making the
  // preset explanation text to disappear.
  contactListView_->setFocus();

  searchEdit_->setEnabled( false );
  contactListView_->setEnabled( false );
  datePicker_->setEnabled( false );
}



// Destructor
ChatHistoryDialog::~ChatHistoryDialog()
{
  // Save the dialog and splitters size
  KConfigGroup group = KMessConfig::instance()->getGlobalConfig( "ChatHistoryDialog" );
  saveDialogSize( group );
  group.writeEntry( "mainSplitterSizes", splitter_ ->sizes() );
}



// Show on which dates there are logged chats with a certain contact
void ChatHistoryDialog::showAvailableDates( const QModelIndex &index )
{
  const QModelIndex contactIndex( proxyModel_->mapToSource( index ) );
  if( ! contactIndex.isValid() )
  {
    return;
  }

  // Initially the list view emits a currentChanged() signal, but the list
  // doesn't bother to highlight the selected item
  contactListView_->setCurrentIndex( index );

  // Block the UI while we're loading
  setLoading( true );

  const QString handle( model_->itemFromIndex( contactIndex )->text() );

  ChatHistoryManager::setHandle( handle );
  const ConversationList timestamps( ChatHistoryManager::timestamps() );

  switch( ChatHistoryManager::result() )
  {
    case ChatHistoryManager::RESULT_OK:
      break;

    case ChatHistoryManager::RESULT_NON_EXISTING_DIRECTORY:
    case ChatHistoryManager::RESULT_UNREADABLE_LOGS:
    {
#ifdef KMESSDEBUG_CHATHISTORYDIALOG
      kmDebug() << "Unable to read logs: error" << ChatHistoryManager::result();
#endif
      setLoading( false );
      QDir logsDir( KMessConfig::instance()->getAccountDirectory( ChatHistoryManager::account() ) + "/chatlogs" );
      KMessageBox::error( this, i18nc( "Dialog box text",
                                       "There has been an error while opening your logs. This "
                                       "is commonly a permission problem, check if you have "
                                       "read/write access to directory <i>&quot;%1&quot;</i>. "
                                       "Otherwise, your logs may be corrupted.",
                                       logsDir.absolutePath() ),
                                i18nc( "Dialog box title", "Could not open chat history" ) );
      return;
    }

    default:
      setLoading( false );
      kmWarning() << "Unexpected result from the chat history manager: code" << ChatHistoryManager::result();
      return;
  }

  // Highlight the days where conversations were logged
  QTextCharFormat enabledDateFormat;
  enabledDateFormat.setFontPointSize( QApplication::font().pointSizeF() + 1 );
  enabledDateFormat.setFontWeight( QFont::Black );
  enabledDateFormat.setFontUnderline( true );

  // Mark the dates with available chats in the calendar
  QList<quint64> list = timestamps.keys();
  qSort( list );

  QDate lastDate;
  foreach( quint64 timestamp, list )
  {
    lastDate = QDateTime::fromTime_t( timestamp ).date();

    // Set the calendar's first and last available dates, while we're at it
    if( timestamp == list.first() )
    {
      datePicker_->setMinimumDate( lastDate );
    }
    else if( timestamp == list.last() )
    {
      datePicker_->setMaximumDate( lastDate );
    }

    datePicker_->setDateTextFormat( lastDate, enabledDateFormat );
  }

#ifdef KMESSDEBUG_CHATHISTORYDIALOG_VERBOSE
  if( timestamps.isEmpty() )
  {
    kmDebug() << "No conversations available.";
  }
  else
  {
    kmDebug() << timestamps.count() << "conversations were recorded.";
  }
#endif

  // Change the date picker view to display the last date, and show the last log
  if( lastDate.isValid() )
  {
    datePicker_->setCurrentPage( lastDate.year(), lastDate.month() );
    datePicker_->setSelectedDate( lastDate );
    showLogs( lastDate );
  }

  setLoading( false );
}



// Change the account for which to show logs
void ChatHistoryDialog::slotAccountChanged( int index )
{
  setLoading( true );

  ChatHistoryManager::setAccount( accountComboBox_->itemText( index ) );

  datePicker_->setDateTextFormat( QDate(), QTextCharFormat() );

  // Clear the contacts list
  model_->clear();

  const QStringList &list = ChatHistoryManager::contactsList();
  foreach( QString handle, list )
  {
    model_->appendRow( new QStandardItem( handle ) );
  }

  setLoading( false );

  // Disable the listview if there are no contacts
  if( list.isEmpty() )
  {
    searchEdit_->setEnabled( false );
    contactListView_->setEnabled( false );
  }
}



// Load and show the selected chats
void ChatHistoryDialog::showLogs( const QDate& day )
{
  // Don't reload the same chat
  if( lastSelectedDate_.isValid() && lastSelectedDate_ == day )
  {
    return;
  }

  lastSelectedDate_ = day;

  if( ChatHistoryManager::timestamps().isEmpty() )
  {
#ifdef KMESSDEBUG_CHATHISTORYDIALOG
    kmWarning() << "No conversations to load!";
#endif
    return;
  }

  setLoading( true );

#ifdef KMESSDEBUG_CHATHISTORYDIALOG
  kmDebug() << "Loading...";
#endif

  // The text stream is used to dump the DOM nodes to an XML byte array.
  QString xmlData;
  QList<quint64> timestamps;

  // Find all chat log timestamps in the specified day

  // Get the initial and final timestamps of the range
  const quint64 startingDate = QDateTime( day, QTime(  0,  0 ) ).toTime_t();
  const quint64 endingDate   = QDateTime( day, QTime( 23, 59 ) ).toTime_t();


#ifdef KMESSDEBUG_CHATHISTORYDIALOG_VERBOSE
  kmDebug() << "Filtering out conversations before timestamp" << startingDate << "and after" << endingDate;
#endif

  ConversationList list( ChatHistoryManager::timestamps() );
  ConversationList::iterator startIt = list.lowerBound( startingDate );
  ConversationList::iterator endIt = list.upperBound( endingDate );

  // How could the user have made it until here?
  if( startIt == list.end() )
  {
    kmWarning() << "No conversations were logged in this date!";
    setLoading( false );
    return;
  }

  // Add the matching timestamps to a list
  do
  {
    timestamps << startIt.key();
    startIt++;
  }
  while( startIt != endIt );

  // chatLog() takes a list of chat log times and joins all conversations together
  QString xmlString( ChatHistoryManager::chatLog( timestamps, true, false ) );

  // The string must be wrapped into a single tag, to have a valid XML tree even when
  // there is more than one <conversation> tag.
  chatView_->setXml( "<messageRoot>" + xmlString + "</messageRoot>" );

  setLoading( false );

#ifdef KMESSDEBUG_CHATHISTORYDIALOG
  kmDebug() << "Chat log loading returned" << ChatHistoryManager::result();
  kmDebug() << "Reload done.";
#endif
}



/**
 * Set the contact for whom the logs will be initially shown
 *
 * @param handle the account to search logs in
 * @param handle the handle to get contact history for
 * @returns whether there was history for the given contact
 */
bool ChatHistoryDialog::setInitialContact( const QString &account, const QString &handle )
{
  if( account.isEmpty() )
  {
#ifdef KMESSDEBUG_CHATHISTORYDIALOG
    kmDebug() << "Account is empty, selecting nothing.";
#endif
    contactListView_->setCurrentIndex( QModelIndex() );
    return false;
  }

#ifdef KMESSDEBUG_CHATHISTORYDIALOG
  kmDebug() << "Pre-selecting account" << account;
#endif

  // Select the account first (calling the slot directly, or no change signal would be fired)
  slotAccountChanged( accountComboBox_->findText( account ) );

  if( handle.isEmpty() )
  {
#ifdef KMESSDEBUG_CHATHISTORYDIALOG
    kmDebug() << "Handle is empty, not selecting a contact.";
#endif
    contactListView_->setCurrentIndex( QModelIndex() );
    return false;
  }

  // Then select the contact handle
  QList<QStandardItem*> items = model_->findItems( handle, Qt::MatchContains );

  if( items.isEmpty() )
  {
#ifdef KMESSDEBUG_CHATHISTORYDIALOG
    kmDebug() << handle << "has no chat history in account" << account;
#endif
    return false;
  }

#ifdef KMESSDEBUG_CHATHISTORYDIALOG
  kmDebug() << "Pre-selecting handle" << handle;
#endif

  contactListView_->setCurrentIndex( proxyModel_->mapFromSource( items.first()->index() ) );

  return true;
}



// Set whether the dialog is loading chats or not
void ChatHistoryDialog::setLoading( bool isLoading )
{
  if( isLoading )
  {
#ifdef KMESSDEBUG_CHATHISTORYDIALOG_VERBOSE
    kmDebug() << "Now loading...";
#endif

    // Show the loading animation
    loadingLabel_->show();
    loadingLabel_->start();

    searchEdit_->setEnabled( false );
    contactListView_->setEnabled( false );
    datePicker_->setEnabled( false );

    // Clear the view
    chatView_->clearView( true );
  }
  else
  {
    // Hide the loading animation
    loadingLabel_->stop();
    loadingLabel_->hide();

    searchEdit_->setEnabled( ! ChatHistoryManager::contactsList().isEmpty() );
    contactListView_->setEnabled( ! ChatHistoryManager::contactsList().isEmpty() );
    datePicker_->setEnabled( ! ChatHistoryManager::timestamps().isEmpty() );
    datePicker_->setFocus();

#ifdef KMESSDEBUG_CHATHISTORYDIALOG_VERBOSE
    kmDebug() << "...loading ended";
#endif
  }

  accountComboBox_->setEnabled( ! isLoading );
}



// The user right clicked at the KHTMLPart to show a popup.
void ChatHistoryDialog::slotShowContextMenu( const QString &clickedUrl, const QPoint &point )
{
  Q_UNUSED( clickedUrl )

  KMenu *popup = chatView_->popupMenu();
  popup->exec( point );
  delete popup;
}



#include "chathistorydialog.moc"
