/***************************************************************************
                          contactswidget.cpp  -  description
                             -------------------
    begin                : Thu Jan 16 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 "contactswidget.h"

#include "../contact/contact.h"
#include "../accountsmanager.h"
#include "../currentaccount.h"
#include "../kmessdebug.h"
#include "../network/msnswitchboardconnection.h"
#include "../utils/kmessshared.h"
#include "../utils/kmessconfig.h"

#include <QDockWidget>
#include <QMouseEvent>
#include <QDir>

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

// The constructor
ContactsWidget::ContactsWidget( QWidget *parent )
: QWidget( parent )
, Ui::ContactsWidget()
, wasVisible_( false )
{
  setupUi( this );

  container_->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Ignored);

  // Create the layout which will organize the contact frames
  layout_ = new QBoxLayout( QBoxLayout::TopToBottom, container_ );
  layout_->setSpacing( 2 );
  layout_->setContentsMargins( 0, 0, 0, 0 );
  //layout_->setSizeConstraint( QLayout::SetMinAndMaxSize );
  layout_->setAlignment( Qt::AlignTop | Qt::AlignLeft );

  // The scroll area background should mimetize with the rest of the app
  area_->setBackgroundRole( QPalette::Window );

  // Initialize the user picture frame
  userPixmapLabel_->installEventFilter( this );
  CurrentAccount *account = CurrentAccount::instance();
  connect( account, SIGNAL(         changedMsnObject() ),
           this,    SLOT  ( slotUpdateDisplayPicture() ) );
  slotUpdateDisplayPicture();
}



// The destructor
ContactsWidget::~ContactsWidget()
{
  // Remove all contact frames
  qDeleteAll( contactFrames_ );
  contactFrames_.clear();

#ifdef KMESSDEBUG_CONTACTSWIDGET
  kmDebug() << "DESTROYED.";
#endif
}



// A contact was added to the contact list
void ContactsWidget::contactAdded( Contact *contact )
{
  // Update any invited contact frame
  ContactFrame *contactFrame = getContactFrameByHandle( contact->getHandle() );
  if ( contactFrame != 0 )
  {
    contactFrame->activate( contact );
  }
}



// A contact joined the chat
void ContactsWidget::contactJoined( ContactBase* contact )
{
  // See if the contact already has a frame
  const QString& handle = contact->getHandle();
  ContactFrame *contactFrame = getContactFrameByHandle( handle );

  if( contactFrame != 0 )
  {
    // Reactivate the contact's frame
    contactFrame->setEnabled( true );
    return;
  }

#ifdef KMESSDEBUG_CONTACTSWIDGET
  kmDebug() << handle;
#endif

  ContactFrame::DisplayMode frameMode;

  // Get the first available contact frame
  contactFrame = createContactFrame();
  if( KMESS_NULL(contactFrame) ) return;

  // Activate the frame
  contactFrame->activate( contact );

  // Find the most suitable frame display mode
  frameMode = getBestFrameMode();

  // Set it to all the frames, including the new one
  foreach( ContactFrame *frame, contactFrames_ )
  {
    frame->setDisplayMode( frameMode );
  }
}



// A contact left the chat
void ContactsWidget::contactLeft( ContactBase *contact, bool /*isChatIdle*/ )
{
  const QString& handle = contact->getHandle();
  ContactFrame *contactFrame = getContactFrameByHandle( handle );

  if ( contactFrame == 0 )
  {
    return;
  }

#ifdef KMESSDEBUG_CONTACTSWIDGET
  kmDebug() << handle;
#endif


  ContactFrame::DisplayMode frameMode;

  // Delete the frames whenever there is more than one; when only one is left, grey it out instead.
  if( contactFrames_.count() > 1 )
  {
    contactFrames_.removeAll( contactFrame );
    delete contactFrame;

    // Find the most suitable frame mode
    frameMode = getBestFrameMode();

    // Set it to all the frames
    foreach( ContactFrame *frame, contactFrames_ )
    {
      frame->setDisplayMode( frameMode );
    }
  }
  else
  {
    // Deactivate the contact frame
    contactFrame->setEnabled( false );
  }
}



// A contact was removed from the contact list
void ContactsWidget::contactRemoved( Contact *contact )
{
  // Update any invited contact frame
  ContactFrame *contactFrame = getContactFrameByHandle( contact->getHandle() );
  if ( contactFrame != 0 )
  {
    contactFrame->activate( 0 );
  }
}



// A contact is typing
void ContactsWidget::contactTyping( ContactBase *contact )
{
  ContactFrame *contactFrame = getContactFrameByHandle( contact->getHandle() );
  if ( contactFrame != 0 )
  {
    contactFrame->startTyping();
  }
}



// The user picture frame received an event
bool ContactsWidget::eventFilter( QObject *obj, QEvent *event )
{
  QEvent::Type eventType = event->type();

  if( obj != userPixmapLabel_ )
  {
#ifdef KMESSDEBUG_CONTACTSWIDGET
    kmWarning() << "Received event '" << eventType << "' from object '" << obj->objectName() << "'.";
#endif
    return false;
  }

  CurrentAccount *currentAccount = CurrentAccount::instance();

  // Avoid wasting time :)
  if( ! userPictureFrame_->isVisible() )
  {
    return false;
  }

  switch( eventType )
  {
    // Make the picture to glow when hovered
    case QEvent::Enter:
    {
      QImage glowingImage( currentAccount->getPicturePath() );
      KIconEffect::toGamma( glowingImage, 0.8f );
      userPixmapLabel_->setPixmap( QPixmap::fromImage( glowingImage ) );
    }
    break;

    // Restore the picture when the mouse is not hovering over it anymore
    case QEvent::Leave:
    {
      userPixmapLabel_->setPixmap( QPixmap( currentAccount->getPicturePath() ) );
    }
    break;

    // When the picture is clicked, show the account settings
    case QEvent::MouseButtonRelease:
    {
      AccountsManager::instance()->showAccountSettings( currentAccount );
      return true;
    }
    break;

    case QEvent::DragEnter:
    {
      QDragEnterEvent *dragEvent = static_cast<QDragEnterEvent*>( event );
      const QMimeData *mimeData = dragEvent->mimeData();

      // The mimeData object can contain one of the following objects: an image, HTML text, plain text, or a list of URLs.
      // We need only the first URL object
      if( mimeData->hasUrls() )
      {
        dragEvent->acceptProposedAction();
      }
      else
      {
        dragEvent->ignore();
      }
    }
    break;

    case QEvent::Drop:
    {
      QDropEvent* dropEvent = static_cast<QDropEvent*>( event );
      const QMimeData *mimeData = dropEvent->mimeData();

      // The mimeData object can contain one of the following objects: an image, HTML text, plain text, or a list of URLs.
      // We need only the first URL object
      if( ! mimeData->hasUrls() )
      {
        return true;
      }

      KUrl mimeUrl = mimeData->urls().first();
      bool isRemoteFile = false;
      QString localFilePath;
      QImage  pictureData;

      if( mimeUrl.isEmpty() )
      {
        kmWarning() << "Dropped empty MIME URL";
        return true;
      }

      if( mimeUrl.isLocalFile() )
      {
        localFilePath = mimeUrl.path();
      }
      else
      {
        // File is remote, download it to a local folder
        // first because QPixmap doesn't have KIO magic.
        // KIO::NetAccess::download fills the localFilePath field.
        if( ! KIO::NetAccess::download(mimeUrl, localFilePath, this ) )
        {
          KMessageBox::sorry( this, i18n( "Downloading of display picture failed" ), i18n("KMess") );
          return true;
        }

        isRemoteFile = true;
      }

      if( ! pictureData.load( localFilePath ) )
      {
        kmWarning() << "The file dropped is not an image or doesn't exist";
        return true;
      }

      if( isRemoteFile )
      {
        KIO::NetAccess::removeTempFile( localFilePath );
      }

      pictureData = pictureData.scaled( 96, 96, Qt::KeepAspectRatioByExpanding, Qt::SmoothTransformation ); // Makes smallest edge 96 pixels, preserving aspect ratio

      if( pictureData.width() != 96 || pictureData.height() != 96 )
      {
        // Copy the middle part of the picture.
        pictureData = pictureData.copy( (pictureData.width()  - 96) / 2, // X
                                        (pictureData.height() - 96) / 2, // Y
                                         96, 96 );
      }

      QString pictureDir = KMessConfig::instance()->getAccountDirectory( currentAccount->getHandle() ) + "/displaypics/";
      QString tempPictureFile = pictureDir + "temporary-picture.png";

      if( ! pictureData.save( tempPictureFile, "PNG" ) )
      {
        KMessageBox::sorry( this,
                            i18n( "An error occurred while trying to change the display picture.\n"
                                  "Make sure that you have selected an existing image file." ),
                            i18n( "KMess" ) );
      }

      QString msnObjectHash( KMessShared::generateFileHash( tempPictureFile ).toBase64() );
      const QString safeMsnObjectHash( msnObjectHash.replace( QRegExp("[^a-zA-Z0-9+=]"), "_" ) );

      // Save the new display picture with its hash as name, to ensure correct caching
      QString pictureName = msnObjectHash + ".png";
      QDir pictureFolder = QDir( pictureDir );

      // Do not overwrite identical pictures
      if( ! pictureFolder.exists( pictureName ) )
      {
        if( ! pictureFolder.rename( tempPictureFile, pictureName ) )
        {
          kmWarning() << "The display picture file could not be renamed from" << tempPictureFile << "to" << pictureName << ".";
          return true;
        }
      }

      if ( ! currentAccount->getShowPicture() )
      {
        currentAccount->setShowPicture( true );
        currentAccount->saveProperties();
      }

      currentAccount->setPicturePath( pictureDir + pictureName );
      currentAccount->setOriginalPicturePath( mimeUrl.path() );

      dropEvent->acceptProposedAction();
    }
    break;

    default:
      break;
  }

  return false;
}



// Get the most suitable frame size, depending on how many contacts are currently in chat with us
inline ContactFrame::DisplayMode ContactsWidget::getBestFrameMode()
{
  // Find out how many are chatting at the moment
  int count = contactFrames_.count();

  if( count > 5 )
  {
    // More than 5 contacts in chat with us: it's a big group chat and we need to be able to display as many frames as possible
    return ContactFrame::ModeTiny;
  }
  else if( count > 2 )
  {
    // A normal group chat: reduce the size of the frames to be able to see all of our contacts at once
    return ContactFrame::ModeSmall;
  }
  else if( count == 2 )
  {
    // A small group chat: Show all details, there's still a lot of space
    return ContactFrame::ModeNormal;
  }

  // A simple 1-on-1 chat: we can show as much detail as possible
  return ContactFrame::ModeSingle;
}



// Find the contact frame with the given handle
ContactFrame* ContactsWidget::getContactFrameByHandle( const QString& handle )
{
  foreach( ContactFrame *contactFrame, contactFrames_ )
  {
    if( contactFrame->getHandle() == handle )
    {
      return contactFrame;
    }
  }

  return 0;
}



// Return one new contact frame
ContactFrame* ContactsWidget::createContactFrame()
{
  ContactFrame *contactFrame = new ContactFrame( container_ );

  connect( contactFrame,   SIGNAL( startPrivateChat( const QString& )  ),
           this,           SIGNAL( startPrivateChat( const QString& )  ) );
  connect( contactFrame,   SIGNAL( contactAllowed( QString )  ),
           this,           SIGNAL( contactAllowed( QString )  ) );
  connect( contactFrame,   SIGNAL(   contactAdded( QString, bool )  ),
           this,           SIGNAL(   contactAdded( QString, bool )  ) );
  connect( contactFrame,   SIGNAL( contactBlocked( QString, bool )  ),
           this,           SIGNAL( contactBlocked( QString, bool )  ) );

  // Add it to the viewBox so it appears in the widget
  layout_->insertWidget( children().count() - 2, contactFrame );

  // put it in the list of frames so we can find it again.
  contactFrames_.append( contactFrame );

  return contactFrame;
}



// A message was received from one of the contacts... notify its frame
void ContactsWidget::messageReceived( const QString& handle )
{
  ContactFrame *frame = getContactFrameByHandle( handle );
  if ( frame != 0 )
  {
    frame->messageReceived();
  }
}



// Enable/disable the frames
void ContactsWidget::setEnabled( bool isEnabled )
{
  ContactBase *contact;
  CurrentAccount *account = CurrentAccount::instance();

  if( isEnabled )
  {
    // Loop through all frames
    foreach( ContactFrame *frame, contactFrames_ )
    {
      contact = account->getContactByHandle( frame->getHandle() );

      // Only reactivate frames for contacts which exist in the account
      if( contact )
      {
        // Reactivate the frame
        frame->activate( contact );
      }
    }
  }
  else
  {
    // Loop through all frames
    foreach( ContactFrame *frame, contactFrames_ )
    {
      // Remove the activation status
      frame->activate( 0 );
    }
  }
}



// Connect the widget to a dock
void ContactsWidget::setDockWidget( QDockWidget *dockWidget, Qt::DockWidgetArea initialArea )
{
#ifdef KMESSTEST
  KMESS_ASSERT( dockWidget );
#endif

  // We need to change layout when the dock moves
  connect( dockWidget, SIGNAL( dockLocationChanged(Qt::DockWidgetArea) ),
           this,       SLOT  ( slotLocationChanged(Qt::DockWidgetArea) ) );
  connect( dockWidget, SIGNAL(     topLevelChanged(bool)               ),
           this,       SLOT  ( slotTopLevelChanged(bool)               ) );

  slotLocationChanged( initialArea );
}



// The location of the parent dock widget has changed
void ContactsWidget::slotLocationChanged( Qt::DockWidgetArea area )
{
#ifdef KMESSDEBUG_CONTACTSWIDGET
  kmDebug() << "Changing location to" << area;
#endif

  switch( area )
  {
    case Qt::TopDockWidgetArea:
    case Qt::BottomDockWidgetArea:

      // Loop through all frames to switch the layout mode for frames
      foreach( ContactFrame *frame, contactFrames_ )
      {
        frame->setDisplayMode( ContactFrame::ModeSmall );
      }

      layout_->setDirection( QBoxLayout::LeftToRight );
      area_->setHorizontalScrollBarPolicy( Qt::ScrollBarAsNeeded  );
      area_->setVerticalScrollBarPolicy  ( Qt::ScrollBarAlwaysOff );
      userPictureFrame_->setVisible( false );
      break;

    case Qt::LeftDockWidgetArea:
    case Qt::RightDockWidgetArea:
    default:

      // Loop through all frames to switch the layout mode for frames
      ContactFrame::DisplayMode mode = getBestFrameMode();
      foreach( ContactFrame *frame, contactFrames_ )
      {
        frame->setDisplayMode( mode );
      }

      layout_->setDirection( QBoxLayout::TopToBottom );
      area_->setHorizontalScrollBarPolicy( Qt::ScrollBarAlwaysOff );
      area_->setVerticalScrollBarPolicy  ( Qt::ScrollBarAsNeeded  );

      // Determine if the user picture needs to be shown
      slotUpdateDisplayPicture();
      break;
  }

  // Ensure that the view shows the top left corner
  area_->ensureVisible( 0, 0 );
}



// The parent dock widget floating status has changed
void ContactsWidget::slotTopLevelChanged( bool isTopLevel )
{
  // Moving has started
  if( isTopLevel )
  {
    wasVisible_ = isVisible();
    setVisible( false );
  }
  // The widget has been dropped somewhere
  else
  {
    setVisible( wasVisible_ );
  }

  adjustSize();
}



// Update the user's display picture
void ContactsWidget::slotUpdateDisplayPicture()
{
  CurrentAccount *currentAccount = CurrentAccount::instance();

  bool isVisible = currentAccount->getShowChatUserPicture();

  userPictureFrame_->setVisible( isVisible );

  // Don't update it if it's not even visible
  if( ! isVisible )
  {
#ifdef KMESSDEBUG_CONTACTSWIDGET
    kmDebug() << "Not updating hidden user picture widget";
#endif
    return;
  }

  // The picture is unchanged, do nothing
  const QString pictureFileName = currentAccount->getPicturePath();
  if( userPixmapLabel_->property( "PictureFileName" ) == pictureFileName )
  {
#ifdef KMESSDEBUG_CONTACTSWIDGET
    kmDebug() << "User picture unchanged, not updating widget";
#endif
    return;
  }

#ifdef KMESSDEBUG_CONTACTSWIDGET
  kmDebug() << "Updating user picture widget";
#endif

  // Change the picture
  const QPixmap image( pictureFileName );
  userPixmapLabel_->setPixmap( image );
  userPixmapLabel_->setProperty( "PictureFileName", pictureFileName );
}



#include "contactswidget.moc"
