/***************************************************************************
                          kmessviewdelegate.cpp - description
                             -------------------
    begin                : Sat Feb 16 2008
    copyright            : (C) 2008 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 "kmessviewdelegate.h"

#include "currentaccount.h"
#include "emoticonmanager.h"
#include "contact/msnstatus.h"
#include "contact/specialgroups.h"
#include "model/contactlistmodelitem.h"
#include "utils/richtextparser.h"
#include "kmessdebug.h"
#include "model/contactlist.h"
#include "contact/contact.h"

#include <QApplication>
#include <QLabel>
#include <QModelIndex>
#include <QPainter>
#include <QTextDocument>

#include <KIconLoader>
#include <KLocale>
#include <KGlobalSettings>


/// Default spacing between items
#define ITEM_SPACE    4


/**
 * Constructor
 *
 * Creates two labels which will be used to paint the contact list elements at the location of every item
 *
 * @param parent  The parent object, usually a QTreeView
 */
KMessViewDelegate::KMessViewDelegate( QWidget *parent )
    : QStyledItemDelegate( parent )
{
  // Set up the group label style
  fontBold_.setBold( true );

  // create the text document used to render HTML text labels.
  textDocument_ = new QTextDocument();
  textDocument_->setDocumentMargin( 0.0 );

  QTextOption noWrap;
  noWrap.setWrapMode( QTextOption::NoWrap );
  noWrap.setAlignment( Qt::AlignTop );

  textDocument_->setDefaultTextOption( noWrap );

  // Save the current account's reference
  currentAccount_ = CurrentAccount::instance();

  // Cache the icons for the various media types
  mediaEmoticonMusic_  = EmoticonManager::instance()->getReplacement( "(8)",  true );
  mediaEmoticonGaming_ = EmoticonManager::instance()->getReplacement( "(xx)", true );
  
  KIconLoader *loader = KIconLoader::global();
  
  arrowUp_ = loader->loadIcon( "arrow-up", KIconLoader::Small );
  arrowDown_ = loader->loadIcon( "arrow-down", KIconLoader::Small );
  arrowLeft_ = loader->loadIcon( "arrow-left", KIconLoader::Small );
  arrowRight_ = loader->loadIcon( "arrow-right", KIconLoader::Small );
  
}



/**
 * Destructor
 */
KMessViewDelegate::~KMessViewDelegate()
{
  delete textDocument_;
}



/**
 * Paint an item of the contact list
 *
 * This method is called whenever a contact list item needs do be drawn.
 * The 'index' parameter is used to extract the data from the list item, and depending on the list item type
 * (contact or group mainly) it decides what needs to be drawn and how.
 *
 * @param painter Painter item, we'll use it to draw our item's contents
 * @param option  Contains some details about the item we have to draw
 * @param index   Points to the actual data to be represented
 */
void KMessViewDelegate::paint( QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index ) const
{
  QStyledItemDelegate::paint( painter, option, index );

  // Only render column zero
  if( index.column() != 0 )
  {
    return;
  }

  QString text;

  // Save the painter state to avoid messing up further painting operations with it
  painter->save();

  // Reset coordinate translation in the painter (defaults are relative to the parent's window) - and make rendering prettier possibly
  painter->translate( 0, 0 );
  painter->setRenderHints( QPainter::Antialiasing | QPainter::SmoothPixmapTransform |
                           QPainter::TextAntialiasing, true );

  // Read the actual data off the model index
  const ModelDataList itemData( index.data().toMap() );

  // We're only able to display groups and contacts
  if( itemData[ "type" ].toInt() != ContactListModelItem::ItemGroup
  &&  itemData[ "type" ].toInt() != ContactListModelItem::ItemContact )
  {
    // Restore the painter to the previous state
    painter->restore();

    kmWarning() << "Unknown type of item:" << itemData[ "type" ].toInt();
    kmWarning() << "Index:" << index;
    kmWarning() << "Data:" << itemData;
    return;
  }

  int displayPictureSize = currentAccount_->getListPictureSize();

  // Load the required pixmaps: their sizes will be used to compute the final
  // rendering positions

  QPixmap iconPixmap;
  QPixmap picturePixmap;

  // Group pixmaps
  if( itemData[ "type" ].toInt() == ContactListModelItem::ItemGroup )
  {
    QString groupIconName;
    bool showOfflineContacts = currentAccount_->getShowOfflineContacts();

    // Only show the expansion arrow if there are contacts to show
    if( (   showOfflineContacts && itemData[ "totalContacts"  ].toInt() > 0 )
    ||  ( ! showOfflineContacts && itemData[ "onlineContacts" ].toInt() > 0 ) )
    {
      if( itemData[ "isExpanded" ].toBool() )
      {
        iconPixmap = arrowDown_;
      }
      else if( QApplication::isRightToLeft() )
      {
        iconPixmap = arrowLeft_;
      }
      else
      {
        iconPixmap = arrowRight_;
      }
    }
    else
    {
      // Show only arrow down if there aren't contacts to show in group
      iconPixmap = arrowDown_;
    }
  }
  else // Contact pixmaps
  {
    // Only load the picture if it must be shown
    if( displayPictureSize != 0 )
    {
      picturePixmap = CurrentAccount::instance()->getContactList()->getContactByHandle( itemData[ "handle" ].toString() )->getScaledDisplayPicture();
    }

    // Determine which status pixmap to load
    Flags statusFlags = ( itemData[ "isBlocked" ].toBool() ? FlagBlocked : FlagNone );
    iconPixmap = MsnStatus::getIcon( (Status) itemData[ "status" ].toInt(), statusFlags );
  }


  // Compute the positions where the objects will be painted
  QPoint originPoint;

  if( QApplication::isLeftToRight() )
  {
    originPoint = option.rect.topLeft();
    originPoint.rx()++;
  }
  else
  {
    originPoint = option.rect.topRight();
    originPoint.rx()--;
  }
  originPoint.ry()++;

  QPoint iconPoint( originPoint );
  QPoint picturePoint( originPoint );
  // Labels get adjusted to 2 pixels below the top border, because otherwise, they get painted
  // too close to it
  QRect labelRect( option.rect.adjusted( ITEM_SPACE, 2, -ITEM_SPACE, 0 ) );

  // Group item positions
  if( itemData[ "type" ].toInt() == ContactListModelItem::ItemGroup )
  {
    if( QApplication::isLeftToRight() )
    {
      // iconPoint is painted at originPoint
      labelRect.setLeft( iconPixmap.width() + ITEM_SPACE );
    }
    else
    {
      iconPoint.rx() -= iconPixmap.width();
      labelRect.setRight( iconPoint.x() - ITEM_SPACE );
    }
  }
  else // Contact item positions
  {
    if( QApplication::isLeftToRight() )
    {
      // picturePoint is painted at originPoint

      // Show the icon, then the label
      if( displayPictureSize == 0 )
      {
        labelRect.setLeft( iconPoint.x() + iconPixmap.width() + ITEM_SPACE );
      }
      else if( displayPictureSize < 48 ) // Show the picture, the icon, then the label
      {
        iconPoint.rx() = iconPoint.x() + picturePixmap.width() + ITEM_SPACE;
        labelRect.setLeft( iconPoint.x() + iconPixmap.width() + ITEM_SPACE );
      }
      else // Show the picture and the label; then show the icon over the picture, at its bottom left corner
      {
        iconPoint.ry() += displayPictureSize - KIconLoader::SizeSmall;
        labelRect.setLeft( iconPoint.x() + picturePixmap.width() + ITEM_SPACE );
      }
    }
    else
    {
      // Show the icon, then the label
      if( displayPictureSize == 0 )
      {
        iconPoint.rx() -= iconPixmap.width();
        labelRect.setRight( iconPoint.x() - ITEM_SPACE );
      }
      else if( displayPictureSize < 48 ) // Show the picture, the icon, then the label
      {
        picturePoint.rx() -= picturePixmap.width();
        iconPoint.rx() = picturePoint.x() - iconPixmap.width() - ITEM_SPACE;
        labelRect.setRight( iconPoint.x() - ITEM_SPACE );
      }
      else // Show the picture and the label; then show the icon over the picture, at its bottom right corner
      {
        picturePoint.rx() -= picturePixmap.width();
        iconPoint.rx() -= iconPixmap.width();
        iconPoint.ry() += displayPictureSize - KIconLoader::SizeSmall;
        labelRect.setRight( picturePoint.x() - ITEM_SPACE );
      }
    }
  }

  // If the contact hasn't us in its CL, colorize the line
  if(   itemData[ "type" ].toInt() == ContactListModelItem::ItemContact
  &&  ! itemData[ "isReverse" ].toBool() ) // The contact does not have us in its list
  {
    // TODO: fontknocker (19 Feb 10): re-enable this when we get our list memberships sorted out.
    //painter->fillRect( option.rect, QColor( 255,0,0,20 ) );
  }

  // Paint the images
  if( ! picturePixmap.isNull() )
  {
    painter->drawPixmap( picturePoint, picturePixmap );
  }
  painter->drawPixmap( iconPoint, iconPixmap );

  // Paint the label: first the group case
  if( itemData[ "type" ].toInt() == ContactListModelItem::ItemGroup )
  {
    QString text;
    if( ! itemData[ "isSpecialGroup" ].toBool()
    ||    itemData[ "id" ] == SpecialGroups::INDIVIDUALS )
    {
      text = i18nc( "Group name in the contact list with online/total contacts of that group"
                   , "%1 (%2/%3)"
                   , Qt::escape( itemData[ "name" ].toString() )
                   , itemData[ "onlineContacts" ].toString()
                   , itemData[ "totalContacts"  ].toString() );
    }
    else
    {
      text = i18nc( "Group name in the contact list with total contacts of that group"
                   , "%1 (%2)"
                   , Qt::escape( itemData[ "name" ].toString() )
                   , itemData[ "totalContacts"  ].toString() );
    }

    // The group names are always shown in bold font, to be more visible
    painter->setFont( fontBold_ );

    QFontMetrics fm( painter->font() );
    QTextOption textOpt;
    textOpt.setWrapMode( QTextOption::NoWrap );

    if( ( fm.width( text ) + labelRect.height() ) > labelRect.width() )
    {
      QPixmap label( labelRect.size() );
      label.fill( Qt::transparent );

      // create a linear gradient, white to transparent
      QLinearGradient gradient( QPoint( 0, 0 ), labelRect.bottomRight() );

      if( QApplication::isLeftToRight() )
      {
        gradient.setColorAt( 0.90, Qt::white       );
        gradient.setColorAt( 1.00, Qt::transparent );
      }
      else
      {
        gradient.setColorAt( 0.10, Qt::white       );
        gradient.setColorAt( 0.00, Qt::transparent );
      }

      QPainter pLabel( &label );
      pLabel.translate( -labelRect.topLeft() );
      pLabel.setFont( fontBold_ );
      pLabel.setRenderHints( QPainter::TextAntialiasing, true );

      pLabel.drawText( labelRect, text, textOpt );

      pLabel.setCompositionMode( QPainter::CompositionMode_DestinationIn );
      pLabel.fillRect( labelRect, QBrush( gradient ) );

      painter->drawPixmap( labelRect, label );
    }
    else
    {
      painter->drawText( labelRect, text, textOpt );
    }
  }
  else // then the contact case
  {
    // Get the contact information.
    QString friendlyName, personalMessage;

    // Parse the name and message of the contact, adding emoticons and formatting
    if( currentAccount_->getUseListFormatting() )
    {
      if( ! currentAccount_->getShowContactEmail() )
      {
        friendlyName  = itemData[ "friendlyFormatted" ].toString();
      }
      else
      {
        friendlyName = itemData[ "handle" ].toString();
      }

      personalMessage = itemData[ "personalMessageFormatted" ].toString();
    }
    else
    {
      if( ! currentAccount_->getShowContactEmail() )
      {
        friendlyName = itemData[ "friendly" ].toString();
        RichTextParser::parseMsnString( friendlyName, true, true, false, false );
      }
      else
      {
        friendlyName = itemData[ "handle" ].toString();
      }

      // Display the emoticons alone when MSN Plus formatting is disabled: the formatted
      // version has emoticons already.
      personalMessage = itemData[ "personalMessage" ].toString();
      RichTextParser::parseMsnString( personalMessage, true, true, false, false );
    }

    // If it's empty, then the user wants to see the email in the contact list
    if( friendlyName.isEmpty() )
    {
      friendlyName = itemData[ "handle" ].toString();
    }

    QString messageString;

    const QString mediaString( itemData[ "mediaString" ].toString() );
    if( ! mediaString.isEmpty() )
    {
      // Determine icon for the various media types
      const QString mediaType( itemData[ "mediaType" ].toString() );
      if( mediaType == "Music" )
      {
        messageString = mediaEmoticonMusic_ + "<i>" + Qt::escape( mediaString ) + "</i>";
      }
      else if( mediaType == "Gaming" || mediaType == "Game" )
      {
        messageString = mediaEmoticonGaming_ + "<i>" + Qt::escape( mediaString ) + "</i>";
      }
      else
      {
        // unknown media type, fallback to personal message.
        messageString = "<i>" + personalMessage + "</i>";
      }
    }
    else if( ! personalMessage.isEmpty() ) // When there's no media, show the PM
    {
      messageString = "<i>" + personalMessage + "</i>";
    }

    // Draw the text widget
    if( messageString.isEmpty() )
    {
      // Only person name
      text = friendlyName;
    }
    else if( ! displayPictureSize )
    {
      // Person with message after it
      text = friendlyName + "<small><font style='color:palette(window-text)'> - " + messageString + "</font></small>";
    }
    else
    {
      // Message below,
      text = friendlyName + "<br /><small><font style='color:palette(window-text)'>" + messageString + "</font></small>";
    }

    painter->translate( labelRect.topLeft() );
    labelRect.translate( - labelRect.topLeft() );

    textDocument_->setHtml( text );

    QFontMetrics fm( painter->font() );
    const int labelWidth = fm.width( text );

    // Paint directly when it fits in the available space...
    // The + rect.height() is to have a nicer effect when resizing the window :)
    if( ( labelWidth + labelRect.height() ) <= labelRect.width() )
    {
      textDocument_->drawContents( painter );
    }
    // ...otherwise (when the text goes over the available space) fade it away
    else
    {
      // Create a transparent image as big as the text label
      QPixmap labelPixmap( labelRect.width(), labelRect.height() );
      labelPixmap.fill( Qt::transparent );

      // create a linear gradient, white to transparent
      QLinearGradient gradient( QPoint( 0, 0 ), labelRect.bottomRight() );
      if( QApplication::isLeftToRight() )
      {
        gradient.setColorAt( 0.90, Qt::white       );
        gradient.setColorAt( 1.00, Qt::transparent );
      }
      else
      {
        gradient.setColorAt( 0.10, Qt::white       );
        gradient.setColorAt( 0.00, Qt::transparent );
      }

      // The painter will be used to blend the label with the gradient
      QPainter pixmapPainter( &labelPixmap );
      pixmapPainter.setRenderHints( QPainter::Antialiasing | QPainter::SmoothPixmapTransform | QPainter::TextAntialiasing,
                        true );

      // Adjust the label to the contained text
      textDocument_->adjustSize();

      // When using a right-to-left aligned language (like Arabic) the label needs to be
      // moved on the right side of the available space to honor the alignment.
      QPoint offset( 0, 0 );
      if( QApplication::isRightToLeft() )
      {
        offset.rx() = labelRect.width() - textDocument_->idealWidth();
      }

      // Paint the label over the image
      textDocument_->drawContents( &pixmapPainter );

      // Blend together the image and the gradient
      pixmapPainter.setCompositionMode( QPainter::CompositionMode_DestinationIn );
      pixmapPainter.fillRect( labelRect, QBrush( gradient ) );

      // Paint the resulting image over the widget
      painter->drawPixmap( labelRect, labelPixmap );
    }
  }

  // Restore the painter to the previous state
  painter->restore();
}



/**
 * Return an hint about the size of an item
 *
 * @param option  Contains some details about the item we have to determine size of
 * @param index   Points to the actual list item
 * @return The ize for the given index
 */
QSize KMessViewDelegate::sizeHint( const QStyleOptionViewItem &option, const QModelIndex &index ) const
{
  Q_UNUSED( option );

  // Read the actual data off the model index
  const ModelDataList itemData( index.data().toMap() );

  // the height of the item should be
  // large enough to accommodate the font.
  QFont generalFont = KGlobalSettings::generalFont();
  QFontMetricsF metrics( generalFont );

  QSize size( KIconLoader::SizeSmall + 2, KIconLoader::SizeSmall + 4 );

  if ( size.height() < metrics.height() )
  {
    size.setHeight( metrics.height() );
  }

  if( itemData.isEmpty() )
  {
    return size;
  }

  int picturesDimension = currentAccount_->getListPictureSize();
  switch( itemData[ "type" ].toInt() )
  {
    case ContactListModelItem::ItemContact:
      if( picturesDimension > 0 )
      {
        QSize pictureSize( size );
        if( picturesDimension <= 32 )
        {
          // Fix the margin
          pictureSize.rheight() = 36;
        }
        else
        {
          pictureSize.rheight() = picturesDimension + 2;
        }

        qreal fontHeight = metrics.height() * 2; // x2 to account for PSM.

        if ( fontHeight > pictureSize.height() )
        {
          pictureSize.setHeight( fontHeight );
        }

        return pictureSize;
      }
      else
      {
        return size;
      }

    case ContactListModelItem::ItemGroup:
    default:
      return size;
  }
}

