/***************************************************************************
                          chat.cpp  -  description
                             -------------------
    begin                : Wed Jan 15 22:41:32 CST 2003
    copyright            : (C) 2003 by Mike K. Bennett
    email                : mkb137b@hotmail.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 "chat.h"


#include "../contact/contactextension.h"
#include "../contact/invitedcontact.h"
#include "../contact/msnobject.h"
#include "../model/contactlist.h"
#include "../utils/kmessconfig.h"
#include "../utils/kmessshared.h"
#include "../currentaccount.h"
#include "../kmessapplication.h"
#include "../kmessdebug.h"
#include "../kmess.h"
#include "chatmessageview.h"
#include "chatwindow.h"
#include "contactswidget.h"

#include <QDir>
#include <QFile>
#include <QRegExp>

#include <KFileDialog>
#include <KIO/NetAccess>
#include <KMessageBox>



#ifdef KMESSDEBUG_CHAT
  #define KMESSDEBUG_CHAT_GENERAL
  #define KMESSDEBUG_CHAT_CONTACTS
  #define KMESSDEBUG_CHAT_FILETRANSFER
//   #define KMESSDEBUG_CHAT_TYPING_MESSAGES
#endif



// The constructor
Chat::Chat( QWidget *parent )
: ChatView( parent )
, firstMessage_( true )
, initialized_( false )
, msnSwitchboardConnection_( 0 )
{
  hide();

  // Setup the contact typing information timer
  typingTimer_.setSingleShot( true );

  connect( &typingTimer_, SIGNAL(       timeout() ),
           this,          SLOT  ( contactTyping() ) );
}



// The destructor
Chat::~Chat()
{
#ifdef KMESSDEBUG_CHAT_GENERAL
  kDebug() << "DESTROYED.";
#endif
}



// Choose the contact to start an invitation with.
const QString Chat::chooseContact()
{
  if( KMESS_NULL(msnSwitchboardConnection_) ) return QString();

  // Choose the contact.
  const QStringList &contactsInChat = msnSwitchboardConnection_->getContactsInChat();
  switch( contactsInChat.count() )
  {
    case 0:
      // No contacts in the chat
      if( msnSwitchboardConnection_->getLastContact().isEmpty() )
      {
        // Send invitation to the first contact that should appear in the chat.
        return msnSwitchboardConnection_->getFirstContact();
      }
      else
      {
        // Resume chat with last contact
        return msnSwitchboardConnection_->getLastContact();
      }

    case 1:
      // Choose the only contact available in the chat
      return contactsInChat.first();

    default:
      // Multiple contacts in the chat.
      // TODO: The official client opens a contact-choose dialog here, and starts a new chat with the selected contact.
      KMessageBox::sorry( this, i18nc( "Error dialog box text",
                                       "You cannot send invitations when there are multiple contacts in a chat. "
                                       "Please start a separate chat with the contact you wanted to send the invitation to." ) );
      return QString();
  }
}



// A contact joined the chat
void Chat::contactJoined( ContactBase* contact, bool offlineContact )
{
  // Don't replace the bool offlineContact with contact->isOffline(), because there may be
  // real chats with invisible contacts
  const QString&     handle      ( contact->getHandle()                               );
  const QString&     friendlyName( contact->getFriendlyName( STRING_CLEANED_ESCAPED ) );
  const QStringList &participants( getParticipants()    );

  // Don't show the message when the contact went/is offline (because in such cases, the message
  // is unreliable, and if we started the chat we're just pretending the contact joined)
  if( ! offlineContact && ( currentAccount_->getShowSessionInfo() || participants.count() > 1 ) )
  {
    showMessage( ChatMessage( ChatMessage::TYPE_PRESENCE,
                              ChatMessage::CONTENT_PRESENCE_JOIN,
                              false,
                              i18n("%1 has joined the chat.", friendlyName ),
                              handle,
                              friendlyName ) );
  }

  // Change the chat title
  emit chatInfoChanged();

  // Allow future changes in the nickname and status to update the chat informations
  if( contact )
  {
    // disconnect any old signals, just to be sure
    disconnect( contact, 0, this, 0 );

    connect( contact, SIGNAL(            changedStatus() ),
             this,    SLOT  ( slotContactChangedStatus() ) );
    connect( contact, SIGNAL(            changedStatus() ),
             this,    SIGNAL(          chatInfoChanged() ) );
    connect( contact, SIGNAL(      changedFriendlyName() ),
             this,    SIGNAL(          chatInfoChanged() ) );
  }
}



// A contact left the chat
void Chat::contactLeft( ContactBase *contact, bool isChatIdle )
{
  const QString&     handle      ( contact->getHandle() );
  const QStringList &participants( getParticipants()    );

#ifdef KMESSDEBUG_CHAT_GENERAL
  kDebug() << "contact '" << handle << "' has left" << (isChatIdle ? " due to inactivity" : "") << ".";
#endif

  // A contact has left the chat.
  // Group chats: it gets disconnected completely from it
  // 1-on-1 chats: the chat UI will still show the contact, so don't disconnect its update signals
  if( participants.count() > 1 )
  {
    disconnect( contact, 0, this, 0 );
  }

  // Always show a message when this is a group chat,
  // but never if it would be the first message.
  if( ! firstMessage_ && ( currentAccount_->getShowSessionInfo() || participants.count() > 1 ) )
  {
    const QString& contactName( contact->getFriendlyName( STRING_CLEANED_ESCAPED ) );
    QString message;

    if( isChatIdle )
    {
      message = i18nc( "Message shown in chat, %1 is the contact's friendly name",
                       "The chat went idle, %1 has left it.",
                       contactName );
    }
    else
    {
      message = i18nc( "Message shown in chat, %1 is the contact's friendly name",
                       "%1 has left the chat.",
                       contactName );
    }

    showMessage( ChatMessage( ChatMessage::TYPE_PRESENCE,
                              ChatMessage::CONTENT_PRESENCE_LEAVE,
                              false,
                              message,
                              handle,
                              contactName ) );
  }

  // Change the caption to remove the contact who has left
  emit chatInfoChanged();
}



// A contact is typing
// If called without arguments, it will only update the list of typing contacts.
void Chat::contactTyping( ContactBase *contact, bool forceExpiration )
{
  int secsTo;
  int nextUpdate = CHAT_TYPING_EXPIRATION_TIME; // Allow a maximum number of seconds between each update
  QString handle;
  QString friendlyName;

  if( contact != 0 )
  {
    handle       = contact->getHandle();
    friendlyName = contact->getFriendlyName( STRING_CLEANED );

    if( friendlyName.isEmpty() ) // If the contact has no friendlyName, use its handle as name
    {
      friendlyName = handle;
    }
  }

#ifdef KMESSDEBUG_CHAT_TYPING_MESSAGES
  kDebug() << "Called with forceExpiration=" << forceExpiration << ", handle=" << handle << ", friendlyName=" << friendlyName;
  kDebug() << "Current list:" << typingContactsNames_.keys();
#endif

  // Do not add anything to the list if the friendly name is empty
  if( ! handle.isEmpty() )
  {
    QTime startTime = QTime::currentTime();

    // If a contact is still typing, re-insert it in the list, so that the typing start time is updated
    if( typingContactsNames_.contains( handle ) )
    {
#ifdef KMESSDEBUG_CHAT_TYPING_MESSAGES
      kDebug() << "Removing" << handle << "from the list.";
#endif
      typingContactsNames_.remove( handle );
      typingContactsTimes_.remove( handle );
    }

    // When we receive a message from this contact, we only remove it from the typing contacts
    if( ! forceExpiration )
    {
#ifdef KMESSDEBUG_CHAT_TYPING_MESSAGES
      kDebug() << "Adding" << handle << "to the list.";
#endif
      // Add this contact to the list of contacts which are typing, and record when it has started writing
      startTime.start();
      typingContactsNames_.insert( handle, friendlyName );
      typingContactsTimes_.insert( handle, startTime    );
    }
#ifdef KMESSDEBUG_CHAT_TYPING_MESSAGES
    else
    {
      kDebug() << "Removal from the list was forced.";
    }
#endif
  }

  // Update the list to remove expired typing events
  QMutableHashIterator<QString,QTime> it( typingContactsTimes_ );
  while( it.hasNext() )
  {
    it.next();
    secsTo = (int) ( it.value().elapsed() * .001 ); // Only keep the seconds

    // If the typing event has expired, stop displaying it
    if( secsTo >= CHAT_TYPING_EXPIRATION_TIME )
    {
#ifdef KMESSDEBUG_CHAT_TYPING_MESSAGES
      kDebug() << "Removing" << handle << "from the list due to expiration.";
#endif
      typingContactsNames_.remove( it.key() );
      typingContactsTimes_.remove( it.key() );
    }

    // Find the typing event which will expire first, so we can next update when it expires
    if( secsTo && secsTo < nextUpdate )
    {
      nextUpdate = secsTo;
    }
  }

#ifdef KMESSDEBUG_CHAT_TYPING_MESSAGES
  kDebug() << "List after cleanup and update:" << typingContactsNames_.keys();
#endif

  // The more time passes from the last update, the more we get close to the next update
  if( nextUpdate < CHAT_TYPING_EXPIRATION_TIME )
  {
    nextUpdate = CHAT_TYPING_EXPIRATION_TIME - nextUpdate;
  }

#ifdef KMESSDEBUG_CHAT_TYPING_MESSAGES
  kDebug() << "Next update in" << nextUpdate << "seconds.";
#endif

  // Update the user interface
  if( typingContactsNames_.contains( handle ) )
  {
    contactsWidget_->contactTyping( contact );
  }
  else
  {
    contactsWidget_->messageReceived( handle );
  }

#ifdef KMESSDEBUG_CHAT_TYPING_MESSAGES
  kDebug() << "emitting signal from:" << this;
#endif

  // Let the other window components update, too
  emit gotTypingMessage( this );

  // Activate the timer for the next update if needed
  if( ! typingContactsTimes_.isEmpty() )
  {
    typingTimer_.start( nextUpdate * 1000 );
  }
}



// Return a list of the contacts in the chat to be used as window caption
const QString Chat::getCaption()
{
  // Grep the current participants or the last participant
  const QStringList &participants( getParticipants() );
  QString caption;

  // How many people are there in here?
  switch( participants.count() )
  {
    // The chat is completely empty - it is not even a chat!
    case 0:
      break;

    // One contact is connected; or the only contact of the session has left
    case 1:
      caption = currentAccount_->getContactFriendlyNameByHandle( participants[0], STRING_CLEANED );
      break;

    // Two contacts are connected
    case 2:
      caption = i18nc( "Name of a chat tab", "%1 and %2",
                       currentAccount_->getContactFriendlyNameByHandle( participants[0], STRING_CLEANED ),
                       currentAccount_->getContactFriendlyNameByHandle( participants[1], STRING_CLEANED )
                     );
      break;

    // Three or more contacts are connected
    default:
      caption =  i18nc( "Name of a chat tab", "%1 et al.",
                        currentAccount_->getContactFriendlyNameByHandle( participants[0], STRING_CLEANED )
                      );
  }

  // Replace the 'return chars' with 'space chars'
  return caption.replace( "\n", " " );
}



// Return the chat window which currently contains this Chat
ChatWindow *Chat::getChatWindow()
{
  return qobject_cast<ChatWindow*>( window() );
}



// Return the list of contacts in the chat
const QStringList Chat::getParticipants() const
{
  if( msnSwitchboardConnection_ == 0 )
  {
    return lastKnownContacts_;
  }

  return msnSwitchboardConnection_->getContactsInChat();
}



// Return the icon to use in the tab icon chat
KIcon Chat::getParticipantsTabIcon()
{
  const QStringList &participants( getParticipants() );

  // If there are more participants use group icon
  if(  participants.count() != 1 )
  {
    return KIcon( "system-users" );
  }

  // Else use the status icon of the current contact
  const QString& handle( participants.first() );

  // Find the contact, if it isn't in the contact list, use the "unknown" tab icon
  const ContactBase *contact = currentAccount_->getContactByHandle( handle );
  if( contact == 0 )
  {
    return KIcon( "view-media-artist" );
  }

  return KIcon( MsnStatus::getIcon( contact->getStatus() ) );
}



// Return the list of previously sent messages
QStringList& Chat::getQuickRetypeList()
{
  return quickRetypeList_;
}



// Return the list of contacts which are typing
const QStringList Chat::getTypingContacts() const
{
  return typingContactsNames_.values();
}



// Return the date and time the chat has started
QDateTime Chat::getStartTime() const
{
  return QDateTime( startDate_, startTime_ );
}



// Return the switchboard connection used by the chat window.
MsnSwitchboardConnection * Chat::getSwitchboardConnection() const
{
  return msnSwitchboardConnection_;
}



// Initialize the object
bool Chat::initialize( MsnSwitchboardConnection *switchboardConnection )
{
  if ( initialized_ )
  {
    kDebug() << "already initialized.";
    return false;
  }

  if( ! ChatView::initialize() )
  {
    kDebug() << "Couldn't setup the chat view widget.";
    return false;
  }

  // Connect the contacts widget's signals
  const ContactList *contactList = currentAccount_->getContactList();
  connect( contactsWidget_, SIGNAL( contactAllowed(QString)      ),
           this,            SIGNAL( contactAllowed(QString)      ) );
  connect( contactsWidget_, SIGNAL(   contactAdded(QString,bool) ),
           this,            SIGNAL(   contactAdded(QString,bool) ) );
  connect( contactsWidget_, SIGNAL( contactBlocked(QString,bool) ),
           this,            SIGNAL( contactBlocked(QString,bool) ) );
  connect( contactsWidget_, SIGNAL( startPrivateChat( const QString ) ),
           this,            SIGNAL( startPrivateChat( const QString ) ) );
  connect( contactList,     SIGNAL(      contactAdded(Contact*)     ),
           contactsWidget_, SLOT  (      contactAdded(Contact*)     ) );
  connect( contactList,     SIGNAL(    contactRemoved(Contact*)     ),
           contactsWidget_, SLOT  (    contactRemoved(Contact*)     ) );

  // Save the switchboard connection
  setSwitchboardConnection( switchboardConnection );

  // Save the date/time the chat started.
  // Use it to save a chatlog later.
  startDate_ = QDate::currentDate();
  startTime_ = QTime::currentTime();

  // Update the chat window caption with the contact name
  emit chatInfoChanged();

  initialized_ = true;
  return true;
}



// Invite a contact to the chat
void Chat::inviteContacts( const QStringList &contacts )
{
  if( msnSwitchboardConnection_ == 0 )
  {
    return;
  }

  // Invite each selected contact, if it's not in chat already
  foreach( const QString &handle, contacts )
  {
    if( ! isContactInChat( handle, false ) )
    {
      msnSwitchboardConnection_->inviteContact( handle );
    }
  }
}



// Return whether or not the chat is at its first incoming message
bool Chat::isChatFirstMessage()
{
  return firstMessage_;
}



// Return whether or not the contact is in this chat.
bool Chat::isContactInChat( const QString &handle, bool isExclusiveChatWithContact )
{
  const QStringList &participants( getParticipants() );

#ifdef KMESSDEBUG_CHAT_CONTACTS
  kDebug() << "Is" << handle << "in chat" << this << "?"
           << "1-on-1 required?" << isExclusiveChatWithContact
           << "Participants:" << participants
           << "Has switchboard?" << ( msnSwitchboardConnection_ != 0 )
           << "Last known contacts:" << lastKnownContacts_;
#endif

  if( participants.isEmpty() )
  {
    return false;
  }

  // Check if the contact is one of the participants to this chat.
  bool check = participants.contains( handle );

  // Check if is exclusive chat with the contact passed as the argument
  if( check && isExclusiveChatWithContact )
  {
    check = ( participants.count() == 1 );
  }

#ifdef KMESSDEBUG_CHAT_CONTACTS
  kDebug() << "Returning" << check;
#endif

  return check;
}



// The chat is closing
void Chat::queryClose()
{
#ifdef KMESSDEBUG_CHAT_GENERAL
  kDebug() << "Saving chat log";
#endif

  // Notify ChatMaster
  emit closing( this );

  // Save the chat
  saveChatAutomatically();

  /**
   * TODO: Uncomment this part: it needs a _reliable_ method to check whether there are running file
   * transfers. The commented part uses "ApplicationList::isEmpty()" which I've removed, the new
   * method to check will probably be elsewhere. The main code of that method was this:
   * <code>
      // check if there are active applications
      if ( mimeApplications_.count() +
          p2pApplications_.count() -
          abortingApplications_.count() == 0)
        return true; else return false;
   * </code>
   */
  /*
  // If there are active applications show a warning to the user
  if( participants_.count() == 1 ) // Transfers are not possibile in multiple chats
  {
    // Check if there are active applications
    ContactBase *contact = currentAccount_->getContactByHandle( participants_.first() );
    if( contact != 0 && contact->getApplicationList() != 0
        && ! contact->getApplicationList()->isEmpty() )
    {
      int choice = KMessageBox::warningContinueCancel( this, i18n("Continue closing the chat window?\nActive transfers will be aborted!"));
      if( choice == KMessageBox::Cancel )
      {
        return false;
      }
    }
  }
  */
}



// The application is exiting
void Chat::queryExit()
{
#ifdef KMESSDEBUG_CHAT_GENERAL
  kDebug() << "Saving chat log";
#endif

  // Just save the chat before going down
  saveChatAutomatically();
}



// A message was received from a contact.
void Chat::receivedMessage(const ChatMessage &message)
{
#ifdef KMESSDEBUG_CHAT_GENERAL
  kDebug() << "Displaying message, internal type=" << message.getType();
  kDebug() << "First message = " << firstMessage_ << ".";
#endif

  showMessage( message );

  // Do not notify about presence messages
  if( message.getType() == ChatMessage::TYPE_PRESENCE )
  {
    return;
  }

  // Update the date of the last received message. Only allowed for contacts on the contact list.
  Contact *sourceContact = currentAccount_->getContactList()->getContactByHandle( message.getContactHandle() );
  if( sourceContact != 0 && sourceContact->getExtension() )
  {
    sourceContact->getExtension()->setLastMessageDate();
  }

  // Check if we should send an automatic reply to the chat message
  if( currentAccount_           != 0
  &&  msnSwitchboardConnection_ != 0
  &&  currentAccount_->getAutoreply()
  && ( lastSentAutoMessage_.isNull() || lastSentAutoMessage_.elapsed() > 120000 ) )
  {
    // Send an auto away message every two minutes (if the contact keeps writing)

#ifdef KMESSDEBUG_CHAT_GENERAL
    kDebug() << "Sending auto reply to chat message.";
#endif

    QString awayMessage( i18nc( "Automatic reply message",
                                "%1 (This message was sent automatically)",
                                currentAccount_->getAutoreplyMessage() ) );

    // Send the autoreply message to the contact(s)
    msnSwitchboardConnection_->sendChatMessage( awayMessage );

    // Show the autoreply message
    ChatMessage autoMessage = ChatMessage( ChatMessage::TYPE_OUTGOING,
                                            ChatMessage::CONTENT_MESSAGE,
                                            false,
                                            awayMessage,
                                            currentAccount_->getHandle(),
                                            currentAccount_->getFriendlyName( STRING_ORIGINAL ),
                                            currentAccount_->getPicturePath(),
                                            currentAccount_->getFont(),
                                            currentAccount_->getFontColor() );
    showMessage( autoMessage );

    // Save the time the message was sent, to display the next after some time
    lastSentAutoMessage_.start();
  }

  // Remove this contact from the list of typing messages and let the parent window to update
  // this chat's typing information
  contactTyping( sourceContact, true );

  // Emit the new message signal so that the user will receive a notification
  emit gotChatMessage( message, this );

  // If this was the first received message, clear the flag
  if( firstMessage_ )
  {
    firstMessage_ = false;
  }
}



/**
 * Save the chat if needed.
 *
 * The boolean argument should not be used, as it's used to recurse once when
 * the chat should also be saved to an external file.
 */
void Chat::saveChatAutomatically( bool saveExternalFile )
{
  // Check if "save chats" is enabled
  if( ! currentAccount_->getSaveChats() )
  {
#ifdef KMESSDEBUG_CHAT_GENERAL
    kDebug() << "User has save chats disabled, not saving chat.";
#endif
    return;
  }

  // Check if the message area is empty, we won't save empty chats
  if( isEmpty() )
  {
#ifdef KMESSDEBUG_CHAT_GENERAL
    kDebug() << "Message area is empty, not saving chat.";
#endif
    return;
  }

#ifdef KMESSDEBUG_CHAT_GENERAL
  if( ! saveExternalFile )
  {
    kDebug() << "Automatically saving the XML log file.";
  }
  else
  {
    kDebug() << "Automatically saving the external log file.";
  }
#endif

  QDir    dir;
  QString fileName, extension;
  Account::ChatExportFormat format;

  // Set the base file name, same for all logs
  const QStringList &participants( getParticipants() );
  if( participants.isEmpty() )
  {
    // Should never, never, ever happen.
    fileName = "KMess";
  }
  else
  {
    fileName = participants.first();
  }

  if( ! saveExternalFile )
  {
    extension = "xml";
    format = Account::EXPORT_XML;
    dir.setPath( KMessConfig::instance()->getAccountDirectory( currentAccount_->getHandle() )
               + "/chatlogs" );
  }
  else
  {
    dir.setPath( currentAccount_->getSaveChatPath() );
    format = currentAccount_->getSaveChatsFormat();
    switch( format )
    {
      case Account::EXPORT_TEXT:
        extension = "txt";
        break;
      case Account::EXPORT_HTML:
        extension = "html";
        break;
      default:
        kWarning() << "Extension is unknown for save format" << format << "! Using \"html\".";
        extension = "html";
        format = Account::EXPORT_HTML;
        break;
    }

    const QString& year ( startDate_.toString( "yyyy" ) );
    const QString& month( startDate_.toString( "MM"   ) );
    const QString& day  ( startDate_.toString( "dd"   ) );

    // Determine the path where to save the log file
    switch( currentAccount_->getSavedChatDirectoryStructure() )
    {
      // Save as <logs-path>/yyyy/contactemail.ext
      case Account::BYYEAR:
        if( ! dir.exists( year  ) ) dir.mkdir( year  );
        dir.cd( year  );
        break;

      // Save as <logs-path>/yyyy/mm/contactemail.ext
      case Account::BYMONTH:
        if( ! dir.exists( year  ) ) dir.mkdir( year  );
        dir.cd( year  );
        if( ! dir.exists( month ) ) dir.mkdir( month );
        dir.cd( month );
        break;

      // Save as <logs-path>/yyyy/mm/dd/contactemail.ext
      case Account::BYDAY:
        if( ! dir.exists( year  ) ) dir.mkdir( year  );
        dir.cd( year  );
        if( ! dir.exists( month ) ) dir.mkdir( month );
        dir.cd( month );
        if( ! dir.exists( day   ) ) dir.mkdir( day   );
        dir.cd( day   );
        break;

      // Save as <logs-path>/contactemail.ext
      case Account::SINGLEDIRECTORY:
      default:
        break;
    }
  }

  // Try creating the directory if it doesn't exist
  if( ! dir.exists() )
  {
    dir.mkpath( dir.absolutePath() );
  }
  if( ! dir.exists() ) // It couldn't be created
  {
    KMessApplication *kmessApp = static_cast<KMessApplication*>( kapp );
    if( ! kmessApp->quitSelected() )
    {
      KMessageBox::sorry( getChatWindow(),
                          i18n( "<html>KMess could not save the log for this chat:<br />"
                                "The chat logs directory, &quot;%1&quot;, does not exist.</html>",
                                dir.absolutePath() ) );
    }
    // TODO: show a KDE save file dialog

    // Always warn via debug log, too
    kWarning() << "Unable to save the chat log. The base directory" << dir.absolutePath() << "doesn't exist.";
    return;
  }

  int fileNumber = 0;
  QString fileCount;
  QString savePath( dir.absolutePath() + "/" );

  // We need to get the current (last) logging file; if it's not too big we'll append
  // to it; if it is too big or there is no current/last logging file, we'll create a new one.
  bool lastFileExists = KMessShared::selectNextFile( savePath, fileName, fileCount, extension, fileNumber );

  // this is the last file as pointed to by selectNextFile; lastFileExists should be the same as file.exists()
  QFile file( savePath + fileName + fileCount + "." + extension );

  // There are log files already for this contact: verify if we can
  // append to the last one: logs should not be too big (2MB max)
  if( lastFileExists && file.size() > Chat::MAX_LOG_FILE_SIZE )
  {
#ifdef KMESSDEBUG_CHAT_GENERAL
    kDebug() << "Last file" << file.fileName() << "is too big, creating a new file";
#endif
    // so automatically choose the next file name.
    file.setFileName( savePath + fileName + "." + QString::number( fileNumber ) + "." + extension );
  }

  // if lastFileExists == false. then the base file is available for writing.
  // use it.

#ifdef KMESSDEBUG_CHAT_GENERAL
  kDebug() << "Saving to chat log file:" << file.fileName();
#endif

  // Save the chat in the format requested by the user
  if( ! saveChatToFile( file.fileName(), format,
                        false, /* Don't overwrite */
                        false  /* No user interaction */ ) )
  {
    // If saving fails (due to a corrupted file for example) try to save the chat
    // with the next available file name

    // Possible race condition but we'll take it: since we can assume the
    // returned file above was indeed the last file, the next file must be
    // nonexistant, so we use that as a new file now

    // Another thing is that if we were *already* creating a new file and the
    // save failed, we will try to save to the same file yet again. However,
    // when saving to a new file already fails, whatever we do it probably
    // won't work with a different filename either.
    // TODO: make this more robust.

    file.setFileName( savePath + fileName + "." + QString::number( fileNumber ) + "." + extension );
    kWarning() << "Save failed. Trying next file name=" << file.fileName();

    // Try to save it again, this time warning the user if it fails once more
    if( ! saveChatToFile( file.fileName(), format, false, true ) )
    {
      kWarning() << "Second save failed too.";
    }
#ifdef KMESSDEBUG_CHAT_GENERAL
    else
    {
      kDebug() << "Second save attempt succeeded.";
    }
#endif
  }

  if( saveExternalFile == false && currentAccount_->getSaveChatsToFile() )
  {
#ifdef KMESSDEBUG_CHAT_GENERAL
    kDebug() << "Done, saving external file too...";
#endif
    saveChatAutomatically( true );
  }
}



// Send a message to the current chat
void Chat::sendChatMessage( const QString &message )
{
#ifdef KMESSTEST
  KMESS_ASSERT( msnSwitchboardConnection_ );
#endif

  // If we send the first message of the chat, clear the first message status
  if( firstMessage_ )
  {
    firstMessage_ = false;
  }

  msnSwitchboardConnection_->sendChatMessage( message );
}



// Send an ink drawing to the current chat
void Chat::sendInkMessage( InkFormat format, const QByteArray &inkData )
{
#ifdef KMESSTEST
  KMESS_ASSERT( msnSwitchboardConnection_ );
#endif

  msnSwitchboardConnection_->sendInk( format, inkData );
}



// Send an "user is typing" message to the contacts
void Chat::sendTypingMessage()
{
  if( KMESS_NULL(msnSwitchboardConnection_) ) return;

  msnSwitchboardConnection_->sendTypingMessage();
}



// Send an wink to the current chat
void Chat::sendWink( const MsnObject &msnObject )
{
  QString winkHtml;
  msnSwitchboardConnection_->sendWink( msnObject );

  // Only show the sent wink if they're enabled
  if( currentAccount_->getShowWinks() )
  {
    WinksWidget::getHtmlFromWink( msnObject.getLocation(), winkHtml );
  }

  // If the stored wink didn't have a name, don't show it:
  QString notificationText;
  if( msnObject.getFriendly().isEmpty() )
  {
    notificationText = i18nc( "Message shown in the chat window (when the wink name is unknown)",
                              "You have sent a wink!" );
  }
  else
  {
    notificationText = i18nc( "Message shown in the chat window, %1 is the wink name",
                              "You have sent the &quot;%1&quot; wink!",
                              msnObject.getFriendly() );
  }

  ChatMessage message( ChatMessage::TYPE_NOTIFICATION,
                       ChatMessage::CONTENT_NOTIFICATION_WINK,
                       false,
                       notificationText + winkHtml, // Add the actual wink after the message
                       QString() );
  showMessage( message );
}



// The user wants to block or unblock a contact.
void Chat::setContactBlocked( QString handle, bool isBlocked )
{
  emit contactBlocked( handle, isBlocked );
}



// Enable or disable the parts of the window which allow user interaction
void Chat::setEnabled( bool isEnabled )
{
  // Also remove the switchboard connection when disabling
  if( ! isEnabled && msnSwitchboardConnection_ != 0 )
  {
    setSwitchboardConnection( 0 );
  }

  if( ! isEnabled )
  {
    getChatWindow()->showStatusMessage( ChatStatusBar::Disconnected, i18n("The chat has been disabled because you are no longer connected to the Live Messenger server." ));
  }
  else
  {
    getChatWindow()->showStatusMessage( ChatStatusBar::DefaultType, " " );
  }


  // Do not reactivate if there is no switchboard connection (ChatMaster will recreate it for us)
  if( isEnabled && msnSwitchboardConnection_ == 0 )
  {
    return;
  }

  ChatView::setEnabled( isEnabled );

  if ( isEnabled )
  {
    getChatWindow()->setUIState( ChatWindow::Connected );
  }

}



// Change the switchboard connection used by the chat window.
void Chat::setSwitchboardConnection( MsnSwitchboardConnection *newConnection )
{
  // Everything is connected already.
#ifdef KMESSDEBUG_CHAT_GENERAL
  if( msnSwitchboardConnection_ == 0 )
  {
    kDebug() << "Switchboard was not set up.";
  }
  else
  {
    kDebug() << "Switchboard was "
              << ( msnSwitchboardConnection_ == newConnection ? "already set" : "different" ) << "." << endl;
  }
#endif

  if( msnSwitchboardConnection_ != 0 )
  {
    // Notify that all contacts have left the chat
    if( ! msnSwitchboardConnection_->isEmpty() )
    {
      const QStringList &participants( getParticipants() );

#ifdef KMESSDEBUG_CHAT_GENERAL
      kDebug() << "Removing old switchboard's participants from the chat:" << participants;
#endif
      foreach( const QString &handle, participants )
      {
        ContactBase* contact = currentAccount_->getContactByHandle( handle );
        if( contact == 0 )
        {
          contact = currentAccount_->addInvitedContact( handle );
        }

        // NOTE: This slot doesn't use the second parameter
        contactsWidget_->contactLeft( contact, false );
      }
    }

    // Delete all signals between us and the old switchboard
    disconnect( this, 0, msnSwitchboardConnection_, 0 );
    disconnect( msnSwitchboardConnection_, 0, contactsWidget_, 0 );
    disconnect( msnSwitchboardConnection_, 0, this, 0 );

    // We are switching switchboards, clean up
    if( msnSwitchboardConnection_ != newConnection )
    {
      // Some info of the previous switchboard must be recorded to allow
      // retrieving details about the chat when no switchboard is present
      lastKnownContacts_ = msnSwitchboardConnection_->getContactsInChat();

      // Also delete the old connection
      msnSwitchboardConnection_->deleteLater();
    }
  }

  msnSwitchboardConnection_ = newConnection;

  // The old connection has been removed, we're done
  if( newConnection == 0 )
  {
    return;
  }

  // Make the new signals connections
  connect( msnSwitchboardConnection_, SIGNAL(        contactJoinedChat(ContactBase*)                 ),
           contactsWidget_,           SLOT  (            contactJoined(ContactBase*)                 ) );
  connect( msnSwitchboardConnection_, SIGNAL(          contactLeftChat(ContactBase*, bool)           ),
           contactsWidget_,           SLOT  (              contactLeft(ContactBase*, bool)           ) );
  connect( msnSwitchboardConnection_, SIGNAL(            contactTyping(ContactBase*)                 ),
           contactsWidget_,           SLOT  (            contactTyping(ContactBase*)                 ) );

  connect( msnSwitchboardConnection_, SIGNAL(        contactJoinedChat(ContactBase*)                 ),
           this,                      SLOT  (            contactJoined(ContactBase*)                 ) );
  connect( msnSwitchboardConnection_, SIGNAL(          contactLeftChat(ContactBase*, bool)           ),
           this,                      SLOT  (              contactLeft(ContactBase*, bool)           ) );
  connect( msnSwitchboardConnection_, SIGNAL(            contactTyping(ContactBase*)                 ),
           this,                      SLOT  (            contactTyping(ContactBase*)                 ) );
  connect( msnSwitchboardConnection_, SIGNAL(              chatMessage(const ChatMessage&)           ),
           this,                      SLOT  (          receivedMessage(const ChatMessage&)           ) );
  connect( msnSwitchboardConnection_, SIGNAL(            receivedNudge(ContactBase*)                 ),
           this,                      SLOT  (        slotReceivedNudge(ContactBase*)                 ) );
  connect( msnSwitchboardConnection_, SIGNAL(            sendingFailed(const QString&,const MimeMessage&)),
           this,                      SLOT  (        slotSendingFailed(const QString&,const MimeMessage&)) );
  connect( msnSwitchboardConnection_, SIGNAL(              showWarning(MsnSwitchboardConnection::WarningType,ContactBase*)),
           this,                      SLOT  (              showWarning(MsnSwitchboardConnection::WarningType,ContactBase*)) );
  connect( msnSwitchboardConnection_, SIGNAL(                 deleteMe(MsnSwitchboardConnection*)    ),
           this,                      SLOT  ( setSwitchboardConnection()                             ) );


  // Add to the contacts widget all the contacts that are present in the new switchboard
  const QStringList &participants( getParticipants() );
#ifdef KMESSDEBUG_CHAT_GENERAL
  kDebug() << "Adding new switchboard's participants to the chat:" << participants;
#endif
  foreach( const QString &handle, participants )
  {
    ContactBase *contact = currentAccount_->getContactByHandle( handle );
    if( contact == 0 )
    {
      contact = currentAccount_->addInvitedContact( handle );
    }

    contactJoined( contact, true /* don't notify about the join */ );
    contactsWidget_->contactJoined( contact );
  }
}



// A warning from the switchboard has been received, display it
void Chat::showWarning( MsnSwitchboardConnection::WarningType type, ContactBase *contact )
{
  QString handle, friendlyName;
  if( contact == 0 )
  {
    handle       = currentAccount_->getHandle();
    friendlyName = currentAccount_->getFriendlyName( STRING_CLEANED_ESCAPED );
  }
  else
  {
    handle       = contact->getHandle();
    friendlyName = contact->getFriendlyName( STRING_CLEANED_ESCAPED );
  }

  switch( type )
  {
    case MsnSwitchboardConnection::WARNING_CONNECTION_DROP:
      showMessage( ChatMessage( ChatMessage::TYPE_SYSTEM,
                                ChatMessage::CONTENT_SYSTEM_ERROR,
                                true,
                                i18nc( "Warning message shown in chat",
                                       "There has been a connection problem." ),
                                handle,
                                friendlyName ) );
      break;

    case MsnSwitchboardConnection::WARNING_TOO_MANY_EMOTICONS:
      showMessage( ChatMessage( ChatMessage::TYPE_SYSTEM,
                                ChatMessage::CONTENT_SYSTEM_ERROR,
                                true,
                                i18nc( "Warning message shown in chat",
                                       "There were too many different custom emoticons in your last message. Only the first 7 will be sent." ),
                                handle,
                                friendlyName ) );
      break;

    case MsnSwitchboardConnection::WARNING_UNSUPPORTED_VOICECLIP:
#ifdef KMESSTEST
      KMESS_ASSERT( contact != 0 );
#endif
      showMessage( ChatMessage( ChatMessage::TYPE_SYSTEM,
                                ChatMessage::CONTENT_SYSTEM_ERROR,
                                true,
                                i18nc( "Warning message shown in chat, %1 is the contact's friendly name",
                                       "%1 has sent you a voice clip, but KMess does not support voice clips yet.",
                                       friendlyName ),
                                handle,
                                friendlyName ) );
      break;

    case MsnSwitchboardConnection::WARNING_UNSUPPORTED_ACTIONMESSAGE:
#ifdef KMESSTEST
      KMESS_ASSERT( contact != 0 );
#endif
      showMessage( ChatMessage( ChatMessage::TYPE_SYSTEM,
                                ChatMessage::CONTENT_SYSTEM_ERROR,
                                true,
                                i18nc( "Warning message shown in chat, %1 is the contact's friendly name",
                                       "%1 has sent you an action message, but KMess does not support action messages yet.",
                                       friendlyName ),
                                handle,
                                friendlyName ) );
      break;

    case MsnSwitchboardConnection::WARNING_INK_UNSUPPORTED_BY_CONTACT:
      showMessage( ChatMessage( ChatMessage::TYPE_SYSTEM,
                                ChatMessage::CONTENT_SYSTEM_ERROR,
                                true,
                                i18nc( "Warning message shown in chat",
                                       "One or more contacts do not support the handwriting message." ),
                                handle,
                                friendlyName ) );
      break;

    case MsnSwitchboardConnection::WARNING_UNSUPPORTED_UNKNOWN:
#ifdef KMESSTEST
      KMESS_ASSERT( contact != 0 );
#endif
    default:
      showMessage( ChatMessage( ChatMessage::TYPE_SYSTEM,
                                ChatMessage::CONTENT_SYSTEM_ERROR,
                                true,
                                i18nc( "Warning message shown in chat, %1 is the contact's friendly name",
                                       "%1 has sent you a Live Messenger feature that KMess does not support yet.",
                                       friendlyName ),
                                handle,
                                friendlyName ) );
      break;
  }
}



// Show a wink received by a contact
void Chat::showWink( const QString &handle, const QString &filename, const QString &animationName )
{
#ifdef KMESSDEBUG_CHAT_GENERAL
  kDebug() << "Processing received wink data.";
#endif

  QString message;
  const QString& friendlyName( currentAccount_->getContactFriendlyNameByHandle( handle, STRING_CLEANED_ESCAPED ) );

  // If we aren't showing winks, notify the user that we got one but don't actually display the animation.
  if( ! currentAccount_->getShowWinks() )
  {
    if( animationName.isEmpty() )
    {
      message = i18nc( "Message shown in the chat window, %1 is the contact's friendly name",
                       "You received a wink from %1, but displaying "
                       "winks has been disabled. "
                       "You can re-enable it in the <a href='kmess://accountconfig'>"
                       "account settings</a>.",
                       friendlyName );
    }
    else
    {
      message = i18nc( "Message shown in the chat window, %1 is the contact's "
                       "friendly name, %2 is the wink name",
                       "You received the &quot;%2&quot; wink from %1, but displaying "
                       "winks has been disabled. "
                       "You can re-enable it in the <a href='kmess://accountconfig'>"
                       "account settings</a>.",
                       friendlyName,
                       animationName );
    }
    showMessage( ChatMessage( ChatMessage::TYPE_NOTIFICATION,
                              ChatMessage::CONTENT_NOTIFICATION_WINK,
                              false,
                              message,
                              handle ) );
    return;
  }

  // Test whether the file exists, before cabextract fails.
  if( ! QFile::exists( filename ) )
  {
    kWarning() << "file not found: " << filename << "!";
    return;
  }

  QString html;
  WinksWidget::CABEXTRACTOR value = WinksWidget::getHtmlFromWink( filename, html );

  if( value == WinksWidget::SUCCESS )
  {
    if( animationName.isEmpty() )
    {
      message = i18nc( "Message shown in the chat window, %1 is the contact's friendly name",
                                        "%1 has sent you a wink!",
                                        friendlyName );
    }
    else
    {
      message = i18nc( "Message shown in the chat window, %1 is the contact's "
                                        "friendly name, %2 is the wink name",
                                        "%1 has sent you a wink: &quot;%2&quot;!",
                                        friendlyName,
                                        animationName );
    }

    // Show a notification
    receivedMessage( ChatMessage( ChatMessage::TYPE_NOTIFICATION,
                                  ChatMessage::CONTENT_NOTIFICATION_WINK,
                                  true,
                                  message + html,
                                  handle,
                                  friendlyName ) );
    return;
  }

  // Extraction failed
  switch( value )
  {
    case WinksWidget::NOTINSTALLED:
      if( animationName.isEmpty() )
      {
        message = i18nc( "Message shown in the chat window, %1 is the contact's friendly name",
                         "You received a wink from %1, but it "
                         "could not be displayed. Make sure you have "
                         "the &quot;cabextract&quot; program installed.",
                         friendlyName );
      }
      else
      {
        message = i18nc( "Message shown in the chat window, %1 is the contact's "
                         "friendly name, %2 is the wink name",
                         "You received the &quot;%2&quot; wink from %1, but it "
                         "could not be displayed. Make sure you have "
                         "the &quot;cabextract&quot; program installed.",
                         friendlyName,
                         animationName );
      }
      break;

    case WinksWidget::FAILED:
      if( animationName.isEmpty() )
      {
        message = i18nc( "Message shown in the chat window, %1 is the contact's friendly name",
                         "You received a wink from %1, but it "
                         "could not be displayed. Extracting the wink "
                         "package with &quot;cabextract&quot; has failed.",
                         friendlyName );
      }
      else
      {
        message = i18nc( "Message shown in the chat window, %1 is the contact's "
                         "friendly name, %2 is the wink name",
                         "You received the &quot;%2&quot; wink from %1, but it "
                         "could not be displayed. Extracting the wink "
                         "package with &quot;cabextract&quot; has failed.",
                         friendlyName,
                         animationName );
      }
      break;

    default:
      if( animationName.isEmpty() )
      {
        message = i18nc( "Message shown in the chat window, %1 is the contact's friendly name",
                         "You received a wink from %1, but it "
                         "could not be displayed. The data could not be "
                         "read.",
                         friendlyName );
      }
      else
      {
        message = i18nc( "Message shown in the chat window, %1 is the contact's "
                         "friendly name, %2 is the wink name",
                         "You received the &quot;%2&quot; wink from %1, but it "
                         "could not be displayed. The data could not be "
                         "read.",
                         friendlyName,
                         animationName );
      }
      break;
  }

  showMessage( ChatMessage( ChatMessage::TYPE_SYSTEM,
                            ChatMessage::CONTENT_SYSTEM_ERROR,
                            false,
                            message,
                            handle ) );
}



// Display a message to notify of a contact status change
void Chat::slotContactChangedStatus()
{
  ContactBase *contact = static_cast<ContactBase*>( sender() );
  if( KMESS_NULL( contact ) ) return;

  // Make sure this contact is in chat
  if( ! isContactInChat( contact->getHandle(), false ) )
  {
#ifdef KMESSDEBUG_CHAT_GENERAL
    kDebug() << "Received a status change signal from a contact not in chat!";
#endif
    return;
  }

  QString message;
  const QString &friendlyName( contact->getFriendlyName( STRING_CLEANED_ESCAPED ) );

  // Display a message when the user goes offline.
  if( contact->isOffline() )
  {
    // Show a different one depending if this is an exclusive chat (which means we send offline IMs)
    if( getParticipants().size() == 1 )
    {
      message = i18n( "%1 has gone offline. Any messages you send will be delivered the next time he or she logs in.",
                      friendlyName );
    }
    else if( currentAccount_->getShowSessionInfo() )
    {
      message = i18n( "%1 has gone offline.",
                      friendlyName );
    }
    else
    {
      return;
    }
  }
  else if( currentAccount_->getShowSessionInfo() )
  {
    message = i18n( "%1 has changed his or her status to &quot;%2&quot;.",
                    friendlyName,
                    MsnStatus::getName( contact->getStatus() ) );
  }
  else
  {
    return;
  }

  receivedMessage( ChatMessage( ChatMessage::TYPE_PRESENCE,
                                ChatMessage::CONTENT_PRESENCE_STATUS,
                                true,
                                message,
                                contact->getHandle() ) );
}



// Notify the user of a received nudge
void Chat::slotReceivedNudge( ContactBase *contact )
{
#ifdef KMESSDEBUG_CHAT_GENERAL
  kDebug() << "Received a nudge.";
#endif

  // Get friendly name
  const QString& friendlyName( contact->getFriendlyName( STRING_CLEANED_ESCAPED ) );

  ChatMessage message( ChatMessage::TYPE_NOTIFICATION,
                       ChatMessage::CONTENT_NOTIFICATION_NUDGE,
                       true,
                       i18n( "%1 has sent you a nudge!", friendlyName ),
                       contact->getHandle(),
                       friendlyName );

  receivedMessage( message );


  // Emit the new message signal so that the user will receive a notification
  emit gotChatMessage( message, this );

  // Start the buzz effect.
  emit gotNudge();
}



// Signal that a message could not be sent to the switchboard
void Chat::slotSendingFailed( const QString &handle, const MimeMessage &message )
{
  const QString& contentType( message.getValue( "Content-Type" ) );
  QString errorMessage;
  QString friendlyName;

#ifdef KMESSDEBUG_CHAT_GENERAL
  kDebug() << "Showing a sending failure notice.";
#endif

  // It's possible that a message could not be delivered to any of the contacts in chat:
  // in this case, the handle is *
  if( handle == "*" )
  {
    friendlyName = i18nc( "Phrase to be inserted in place of a contact name, when a message can't be delivered "
                          "to any of the recipients",
                          "all contacts" );
  }
  else
  {
    friendlyName = currentAccount_->getContactFriendlyNameByHandle( handle, STRING_CLEANED_ESCAPED );
  }

  if( contentType.startsWith( "text/x-msnmsgr-datacast" ) )
  {
    switch( message.getValue( "ID" ).toInt() )
    {
      case 1: // Nudge
        errorMessage = i18nc( "Error message shown in chat, %1 is the contact's friendly name",
                              "Failed to send the nudge to %1.",
                              friendlyName );
        break;
      case 2: // Wink
        errorMessage = i18nc( "Error message shown in chat, %1 is the contact's friendly name",
                              "Failed to send the wink to %1.",
                              friendlyName );
        break;
      default: // Unsupported datacast?!
        kWarning() << "Failed sending datacast message: no sending failure chat message available!";
        return;
    }
  }
  else if( contentType.startsWith( "image/gif" ) || contentType.startsWith( "application/x-ms-ink" ) )
  {
    errorMessage = i18nc( "Error message shown in chat, %1 is the contact's friendly name", "Failed to send the handwritten message to %1.",
                          friendlyName );
  }
  else if( contentType.startsWith( "text/plain" ) && ! message.getBody().isEmpty() )
  {
    QString body( Qt::escape( message.getBody() ) );

    // Cut down the message a bit if needed
    if( body.length() > 16 )
    {
      body.truncate( 16 );
      body += "...";
    }

    errorMessage = i18nc( "Error message shown in chat, %1 is the sent message, %2 is the contact's friendly name",
                          "Failed to send the message to %2:<br/>%1",
                          body,
                          friendlyName );
  }
  else
  {
#ifdef KMESSDEBUG_CHAT_GENERAL
    kDebug() << "Not displaying a sending failure notice for messages of type" << contentType;
#endif
    return;
  }

  showMessage( ChatMessage( ChatMessage::TYPE_SYSTEM,
                            ChatMessage::CONTENT_SYSTEM_ERROR,
                            false,
                            errorMessage,
                            handle,
                            friendlyName ) );
}



// Send a file to a contact.
void Chat::startFileTransfer( QList<QUrl> fileList )
{
  if( KMESS_NULL(msnSwitchboardConnection_) ) return;

  // Ask for a contact
  const QString& handle( chooseContact() );

  // If no contact can be chosen, stop
  if( handle.isEmpty() )
  {
#ifdef KMESSDEBUG_CHAT_FILETRANSFER
    kDebug() << "Unable to find a contact to send files to. Aborting.";
#endif
    return;
  }

#ifdef KMESSDEBUG_CHAT_FILETRANSFER
  kDebug() << "File list contents:" << fileList;
  kDebug() << "Chosen handle:" << handle;
#endif

  if( fileList.isEmpty() )
  {
#ifdef KMESSDEBUG_CHAT_FILETRANSFER
    kDebug() << "Asking for a file to send";
#endif

    // If no file names were given, ask for a filename
    KUrl fileName( KFileDialog::getOpenUrl( KUrl( "kfiledialog:///:fileupload" ) ) );

    // Stop if no file was selected
    if( fileName.isEmpty() )
    {
      return;
    }

    fileList.append( fileName );
  }

  foreach( const QUrl &fileUrl, fileList )
  {
    KUrl localFileUrl( KIO::NetAccess::mostLocalUrl( fileUrl, this ) );

    // If the url can't be translated to a local file, copy it to disk
    if( ! localFileUrl.isLocalFile() )
    {
#ifdef KMESSDEBUG_CHAT_FILETRANSFER
      kDebug() << "Not a local file, copying it locally.";
#endif
      // Download the remote file to a temporary folder
      QString localFilePath;
      if( ! KIO::NetAccess::download( fileUrl, localFilePath, this ) )
      {
#ifdef KMESSDEBUG_CHAT_FILETRANSFER
        kDebug() << "Copy failed.";
#endif
        // Show a message to the user
        showMessage( ChatMessage( ChatMessage::TYPE_SYSTEM,
                                  ChatMessage::CONTENT_APP_FAILED,
                                  false,
                                  i18n( "The file &quot;%1&quot; could not be found on "
                                        "your computer, and the download failed.",
                                        localFileUrl.prettyUrl() ),
                                  handle ) );
        continue;
      }

      // TODO Remove the temporary file after the transfer
      localFileUrl.setUrl( localFilePath );
    }

#ifdef KMESSDEBUG_CHAT_FILETRANSFER
    kDebug() << "Original file URL:" << fileUrl << "-- Local file URL:" << localFileUrl;
#endif

    // Ask the ChatMaster to initiate the file transfer. If there are multiple contacts
    // in this chat, it needs to create a new chat session for that contact.
    emit requestFileTransfer( handle, localFileUrl.toLocalFile() );
  }
}



// Send a nudge to a contact
void Chat::slotSendNudge()
{
  if( msnSwitchboardConnection_ == 0 )
  {
    return;
  }

  // Show a more detailed message if we're in an active chat
  QString messageText;
  const QStringList &contactsInChat = msnSwitchboardConnection_->getContactsInChat();
  if( contactsInChat.count() == 1 && msnSwitchboardConnection_->isConnected() )
  {
    messageText = i18nc( "Message shown in chat window, %1 is the contact's friendly name",
                         "You have sent a nudge to %1!",
                         currentAccount_->getContactFriendlyNameByHandle( contactsInChat.first(), STRING_CLEANED_ESCAPED ) );
  }
  else
  {
    // Chat is either empty (should resume), or a multi-chat
    messageText = i18n( "You have sent a nudge!" );
  }

  // Display message
  ChatMessage message( ChatMessage::TYPE_NOTIFICATION,
                       ChatMessage::CONTENT_NOTIFICATION_NUDGE,
                       false,
                       messageText,
                       QString::null );
  showMessage( message );

  // Send nudge
  msnSwitchboardConnection_->sendNudge();

  // Shake the window
  emit gotNudge();
}



// Start a chat
void Chat::startChat()
{
  if( KMESS_NULL(msnSwitchboardConnection_) )
  {
#ifdef KMESSDEBUG_CHAT_GENERAL
    kWarning() << "Tried to open a chat without a switchboard!";
#endif
    return;
  }

#ifdef KMESSDEBUG_CHAT_GENERAL
  kDebug() << "Opening a chat of type:" << ( msnSwitchboardConnection_->isInactive() ? "Offline" : "Active" );
#endif

  // Add the participant contacts to the chat, even if they're offline
  const QStringList &participants( msnSwitchboardConnection_->getContactsInChat() );

#ifdef KMESSDEBUG_CHAT_GENERAL
  kDebug() << "Adding initial chat participants to the chat:" << participants;
#endif

  foreach( const QString &handle, participants )
  {
    ContactBase *contact = currentAccount_->getContactByHandle( handle );
    if( contact == 0 )
    {
      contact = currentAccount_->addInvitedContact( handle );
    }
    contactsWidget_->contactJoined( contact );
  }

  // if this is an offline chat, inform the user.
  if( getParticipants().size() == 1 && msnSwitchboardConnection_->isInactive() )
  {
    ContactBase *contact = currentAccount_->getContactByHandle( getParticipants().first() );
    if( contact && contact->isOffline() )
    {
#ifdef KMESSDEBUG_CHAT_GENERAL
      kDebug() << "Initial chat participant " << contact->getHandle() << " is offline; pretending the contact joined";
#endif
      contactJoined( contact, true );

      receivedMessage( ChatMessage( ChatMessage::TYPE_PRESENCE,
                                    ChatMessage::CONTENT_SYSTEM_NOTICE,
                                    false,
                                    i18n( "%1 is currently offline. Any messages you send will be delivered the next time he or she logs in.",
                                          contact->getFriendlyName( STRING_CLEANED_ESCAPED ) ),
                                    contact->getHandle() ) );
    }
  }
}



#include "chat.moc"
