/******************************************************************************
 *
 *  Copyright 2008 Szymon Tomasz Stefanek <pragma@kvirc.net>
 *
 *  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.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 *******************************************************************************/

#ifndef __MESSAGELIST_CORE_ITEM_P_H__
#define __MESSAGELIST_CORE_ITEM_P_H__

#include "core/item.h"
#include "core/subjectutils_p.h"

// See the MessageList::ItemPrivate::insertChildItem() function below for an explaination of this macro.
#if __GNUC__ >= 3
  #define GCC_DONT_INLINE_THIS __attribute__((noinline))
#else
  #define GCC_DONT_INLINE_THIS
#endif

namespace MessageList
{

namespace Core
{

class ItemPrivate
{
public:
  ItemPrivate( Item *owner )
    : q( owner ) { }

  /**
   * Implements "in the middle" insertions of child items.
   * The template argument class must export a static inline bool firstGreaterOrEqual( Item *, Item * )
   * function which must return true when the first parameter item is considered to be greater
   * or equal to the second parameter item and false otherwise.
   *
   * The insertion function *IS* our very bottleneck on flat views
   * (when there are items with a lot of children). This is somewhat pathological...
   * beside the binary search based insertion sort we actually can only do "statement level" optimization.
   * I've found no better algorithms so far. If someone has a clever idea, please write to pragma
   * at kvirc dot net :)
   *
   * GCC_DONT_INLINE_THIS is a macro defined above to __attribute__((noinline))
   * if the current compiler is gcc. Without this attribute gcc attempts to inline THIS
   * function inside the callers. The problem is that while inlining this function
   * it doesn't inline the INNER comparison functions (which we _WANT_ to be inlined)
   * because they would make the caller function too big.
   *
   * This is what gcc reports with -Winline:
   *
   * /home/pragma/kmail-soc/kmail/messagelistview/item.h:352: warning: inlining failed in call to
   *   'static bool MessageList::ItemSubjectComparator::firstGreaterOrEqual(MessageList::Item*, MessageList::Item*)':
   *    --param large-function-growth limit reached while inlining the caller
   * /home/pragma/kmail-soc/kmail/messagelistview/model.cpp:239: warning: called from here
   *
   * The comparison functions then appear in the symbol table:
   *
   * etherea kmail # nm /usr/kde/4.0/lib/libkmailprivate.so | grep Comparator
   * 00000000005d2c10 W _ZN5KMail15MessageList18ItemDateComparator19firstGreaterOrEqualEPNS0_4ItemES3_
   * 00000000005d2cb0 W _ZN5KMail15MessageList20ItemSenderComparator19firstGreaterOrEqualEPNS0_4ItemES3_
   * 00000000005d2c50 W _ZN5KMail15MessageList21ItemSubjectComparator19firstGreaterOrEqualEPNS0_4ItemES3_
   * ...
   *
   * With this attribute, instead, gcc doesn't complain at all and the inner comparisons
   * *seem* to be inlined correctly (there is no sign of them in the symbol table).
   */
  template< class ItemComparator, bool bAscending > int GCC_DONT_INLINE_THIS insertChildItem( Model *model, Item *child )
  {
    if ( !mChildItems )
      return q->appendChildItem( model, child );

    int cnt = mChildItems->count();
    if ( cnt < 1 )
      return q->appendChildItem( model, child );

    int idx;
    Item * pivot;

    if ( bAscending )
    {
      pivot = mChildItems->at( cnt - 1 );

      if ( ItemComparator::firstGreaterOrEqual( child, pivot ) ) // gcc: <-- inline this instead, thnx
        return q->appendChildItem( model, child ); // this is very likely in date based comparisons (FIXME: not in other ones)

      // Binary search based insertion
      int l = 0;
      int h = cnt - 1;

      for(;;)
      {
        idx = (l + h) / 2;
        pivot = mChildItems->at( idx );
        if ( ItemComparator::firstGreaterOrEqual( pivot, child ) ) // gcc: <-- inline this instead, thnx
        {
          if ( l < h )
            h = idx - 1;
          else
            break;
        } else {
          if ( l < h )
            l = idx + 1;
          else {
            idx++;
            break;
          }
        }
      }
    } else {

      pivot = mChildItems->at( 0 );
      if ( ItemComparator::firstGreaterOrEqual( child, pivot ) ) // gcc: <-- inline this instead, thnx
        idx = 0;  // this is very likely in date based comparisons (FIXME: not in other ones)
      else {

        // Binary search based insertion
        int l = 0;
        int h = cnt - 1;

        for(;;)
        {
          idx = (l + h) / 2;
          pivot = mChildItems->at( idx );
          if ( ItemComparator::firstGreaterOrEqual( child, pivot ) ) // gcc: <-- inline this instead, thnx
          {
            if ( l < h )
              h = idx - 1;
            else
              break;
          } else {
            if ( l < h )
              l = idx + 1;
            else {
              idx++;
              break;
            }
          }
        }
      }
    }

    Q_ASSERT( idx >= 0 );
    Q_ASSERT( idx <= mChildItems->count() );

    if ( mIsViewable && model )
      model->beginInsertRows( model->index( q, 0 ), idx, idx ); // BLEAH :D

    mChildItems->insert( idx, child );
    child->setIndexGuess( idx );
    if ( mIsViewable )
    {
      if ( model )
        model->endInsertRows(); // BLEAH :D
      child->setViewable( model, true );
    }

    return idx;
  }

  /**
   * Checks if the specified child item is actually in the wrong
   * position in the child list and returns true in that case.
   * Returns false if the item is already in the right position
   * and no re-sorting is needed.
   */
  template< class ItemComparator, bool bAscending > bool childItemNeedsReSorting( Item * child )
  {
    if ( !mChildItems )
      return false; // not my child! (ugh... should assert ?)

    int idx = child->indexGuess();
    if ( !q->childItemHasIndex( child, idx ) )
      idx = q->indexOfChildItem( child );

    if ( idx > 0 )
    {
      Item * prev = mChildItems->at( idx - 1 );
      if ( bAscending )
      {
        // child must be greater or equal to the previous item
        if ( !ItemComparator::firstGreaterOrEqual( child, prev ) )
          return true; // wrong order: needs re-sorting
      } else {
        // previous must be greater or equal to the child item
        if ( !ItemComparator::firstGreaterOrEqual( prev, child ) )
          return true; // wrong order: needs re-sorting
      }
    }

    if ( idx < ( mChildItems->count() - 1 ) )
    {
      Item * next = mChildItems->at( idx + 1 );
      if ( bAscending )
      {
        // next must be greater or equal to child
        if ( !ItemComparator::firstGreaterOrEqual( next, child ) )
          return true; // wrong order: needs re-sorting
      } else {
        // child must be greater or equal to next
        if ( !ItemComparator::firstGreaterOrEqual( child, next ) )
          return true; // wrong order: needs re-sorting
      }
    }

    return false;
  }

  /**
   * Internal handler for managing the children list.
   */
  void childItemDead( Item * child );

  Item * const q;

  Item::Type mType;                                 ///< The type of this item
  QList< Item * > *mChildItems;               ///< List of children, may be 0
  Item * mParent;                             ///< The parent view item
  time_t mMaxDate;                            ///< The maximum date in the subtree
  time_t mDate;                               ///< The date of the message (or group date)
  size_t mSize;                               ///< The size of the message in bytes
  QString mSender;                            ///< The sender of the message (or group sender)
  QString mReceiver;                          ///< The receiver of the message (or group receiver)
  QString mSenderOrReceiver;                  ///< Depending on the folder setting: sender or receiver
  int mThisItemIndexGuess;                    ///< The guess for the index in the parent's child list
  QString mSubject;                           ///< The subject of the message (or group subject)
  bool mIsViewable;                           ///< Is this item attacched to the viewable root ?
  Item::InitialExpandStatus mInitialExpandStatus;   ///< The expand status we have to honor when we attach to the viewable root
  QString mFormattedSize;                     ///< The size above formatted as string, this is done only on request
  QString mFormattedDate;                     ///< The formatted date of the message, formatting takes time so it is done only on request
  QString mFormattedMaxDate;                  ///< The maximum date above formatted (lazily)
  KPIM::MessageStatus mStatus;                ///< The status of the message (may be extended to groups in the future)
};

/**
 * A helper class used with MessageList::Item::childItemNeedsReSorting() and
 * MessageList::Item::insertChildItem().
 */
class ItemSizeComparator
{
public:
  static inline bool firstGreaterOrEqual( Item * first, Item * second )
  {
    if ( first->size() < second->size() )
      return false;
    // When the sizes are equal compare by date too
    if ( first->size() == second->size() )
      return first->date() >= second->date();
    return true;
  }
};

/**
 * A helper class used with MessageList::Item::childItemNeedsReSorting() and
 * MessageList::Item::insertChildItem().
 */
class ItemDateComparator
{
public:
  static inline bool firstGreaterOrEqual( Item * first, Item * second )
  {
    if ( first->date() < second->date() )
      return false;
    // When the dates are equal compare by subject too
    // This is useful, for example, in kernel mailing list where people
    // send out multiple messages with patch parts at exactly the same time.
    if ( first->date() == second->date() )
      return first->subject() >= second->subject();
    return true;
  }
};

/**
 * A helper class used with MessageList::Item::childItemNeedsReSorting() and
 * MessageList::Item::insertChildItem().
 */
class ItemMaxDateComparator
{
public:
  static inline bool firstGreaterOrEqual( Item * first, Item * second )
  {
    if ( first->maxDate() < second->maxDate() )
      return false;
    if ( first->maxDate() == second->maxDate() )
      return first->subject() >= second->subject();
    return true;
  }
};

/**
 * A helper class used with MessageList::Item::childItemNeedsReSorting() and
 * MessageList::Item::insertChildItem().
 */
class ItemSubjectComparator
{
public:
  static inline bool firstGreaterOrEqual( Item * first, Item * second )
  {
    int ret = SubjectUtils::stripOffPrefixes( first->subject() ).
                compare( SubjectUtils::stripOffPrefixes( second->subject() ), Qt::CaseInsensitive );
    if ( ret < 0 )
      return false;
    // compare by date when subjects are equal
    if ( ret == 0 )
      return first->date() >= second->date();
    return true;
  }
};

/**
 * A helper class used with MessageList::Item::childItemNeedsReSorting() and
 * MessageList::Item::insertChildItem().
 */
class ItemSenderComparator
{
public:
  static inline bool firstGreaterOrEqual( Item * first, Item * second )
  {
    int ret = first->sender().compare( second->sender(), Qt::CaseInsensitive );
    if ( ret < 0 )
      return false;
    // compare by date when senders are equal
    if ( ret == 0 )
      return first->date() >= second->date();
    return true;
  }
};

/**
 * A helper class used with MessageList::Item::childItemNeedsReSorting() and
 * MessageList::Item::insertChildItem().
 */
class ItemReceiverComparator
{
public:
  static inline bool firstGreaterOrEqual( Item * first, Item * second )
  {
    int ret = first->receiver().compare( second->receiver(), Qt::CaseInsensitive );
    if ( ret < 0 )
      return false;
    // compare by date when receivers are equal
    if ( ret == 0 )
      return first->date() >= second->date();
    return true;
  }
};

/**
 * A helper class used with MessageList::Item::childItemNeedsReSorting() and
 * MessageList::Item::insertChildItem().
 */
class ItemSenderOrReceiverComparator
{
public:
  static inline bool firstGreaterOrEqual( Item * first, Item * second )
  {
    int ret = first->senderOrReceiver().compare( second->senderOrReceiver(), Qt::CaseInsensitive );
    if ( ret < 0 )
      return false;
    // compare by date when sender/receiver are equal
    if ( ret == 0 )
      return first->date() >= second->date();
    return true;
  }
};

/**
 * A helper class used with MessageList::Item::childItemNeedsReSorting() and
 * MessageList::Item::insertChildItem().
 */
class ItemActionItemStatusComparator
{
public:
  static inline bool firstGreaterOrEqual( Item * first, Item * second )
  {
    if ( first->status().isToAct() )
    {
      if ( second->status().isToAct() )
        return first->date() >= second->date();
      return true;
    }
    if ( second->status().isToAct() )
      return false;
    return first->date() >= second->date();
  }
};

/**
 * A helper class used with MessageList::Item::childItemNeedsReSorting() and
 * MessageList::Item::insertChildItem().
 */
class ItemNewUnreadStatusComparator
{
public:
  static inline bool firstGreaterOrEqual( Item * first, Item * second )
  {
    if ( first->status().isNew() )
    {
      // fist is new
      if ( second->status().isNew() )
        return first->date() >= second->date(); // both are new
      // new comes always first with respect to non-new
      return true;
    }
    if ( second->status().isNew() )
      return false;
    if ( first->status().isUnread() )
    {
      // fist is unread
      if ( second->status().isUnread() )
        return first->date() >= second->date(); // both are unread
      // unread comes always first with respect to non-unread
      return true;
    }
    if ( second->status().isUnread() )
      return false;
    // both are read
    return first->date() >= second->date();
  }
};

} // namespace Core

} // namespace MessageList

#endif //!__MESSAGELIST_CORE_ITEM_P_H__
