/******************************************************************************
 *
 *  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.
 *
 *******************************************************************************/

#include "messagelistview/core/theme.h"

#include <QDataStream>

#include <KLocale>
#include <KGlobalSettings>
#include <KDebug>

namespace KMail
{

namespace MessageListView
{

namespace Core
{

//
// Theme versioning
//
// The themes simply have a DWORD version number attacched.
// The earliest version we're able to load is 0x1013.
// 
// Theme revision history:
//
//  Version Date introduced Description
// --------------------------------------------------------------------------------------------------------------
//  0x1013  08.11.2008      Initial theme version, introduced when this piece of code has been moved into trunk.
//  0x1014  12.11.2008      Added runtime column data: width and column visibility
//  0x1015  03.03.2009      Added icon size
//  0x1016  08.03.2009      Added support for sorting by New/Unread status
//  0x1017  16.08.2009      Added support for column icon
//
static const int gThemeCurrentVersion = 0x1017; // increase if you add new fields or change the meaning of some
// you don't need to change the values below, but you might want to add new ones
static const int gThemeMinimumSupportedVersion = 0x1013;
static const int gThemeMinimumVersionWithColumnRuntimeData = 0x1014;
static const int gThemeMinimumVersionWithIconSizeField = 0x1015;
static const int gThemeMinimumVersionWithSortingByNewUnreadStatusAllowed = 0x1016;
static const int gThemeMinimumVersionWithColumnIcon = 0x1017;

// the default icon size
static const int gThemeDefaultIconSize = 16;


Theme::ContentItem::ContentItem( Type type )
  : mType( type ), mFlags( 0 )
{
}

Theme::ContentItem::ContentItem( const ContentItem &src )
  : mType( src.mType ),
    mFlags( src.mFlags ),
    mFont( src.mFont ),
    mCustomColor( src.mCustomColor )
{
}

QString Theme::ContentItem::description( Type type )
{
  switch ( type )
  {
    case Subject:
      return i18nc( "Description of Type Subject", "Subject" );
    break;
    case Date:
      return i18nc( "Description of Type Date", "Date" );
    break;
    case SenderOrReceiver:
      return i18n( "Sender/Receiver" );
    break;
    case Sender:
      return i18nc( "Description of Type Sender", "Sender" );
    break;
    case Receiver:
      return i18nc( "Description of Type Receiver", "Receiver" );
    break;
    case Size:
      return i18nc( "Description of Type Size", "Size" );
    break;
    case ReadStateIcon:
      return i18n( "New/Unread/Read Icon" );
    break;
    case AttachmentStateIcon:
      return i18n( "Attachment Icon" );
    break;
    case RepliedStateIcon:
      return i18n( "Replied/Forwarded Icon" );
    break;
    case CombinedReadRepliedStateIcon:
      return i18n( "Combined New/Unread/Read/Replied/Forwarded Icon" );
    break;
    case ActionItemStateIcon:
      return i18n( "Action Item Icon" );
    break;
    case ImportantStateIcon:
      return i18n( "Important Icon" );
    break;
    case GroupHeaderLabel:
      return i18n( "Group Header Label" );
    break;
    case SpamHamStateIcon:
      return i18n( "Spam/Ham Icon" );
    break;
    case WatchedIgnoredStateIcon:
      return i18n( "Watched/Ignored Icon" );
    break;
    case ExpandedStateIcon:
      return i18n( "Group Header Expand/Collapse Icon" );
    break;
    case EncryptionStateIcon:
      return i18n( "Encryption State Icon" );
    break;
    case SignatureStateIcon:
      return i18n( "Signature State Icon" );
    break;
    case VerticalLine:
      return i18n( "Vertical Separation Line" );
    break;
    case HorizontalSpacer:
      return i18n( "Horizontal Spacer" );
    break;
    case MostRecentDate:
      return i18n( "Max Date" );
    break;
    case TagList:
      return i18n( "Message Tags" );
    break;
    default:
      return i18nc( "Description for an Unknown Type", "Unknown" );
    break;
  }
}


bool Theme::ContentItem::applicableToMessageItems( Type type )
{
  return ( static_cast< int >( type ) & ApplicableToMessageItems );
}

bool Theme::ContentItem::applicableToGroupHeaderItems( Type type )
{
  return ( static_cast< int >( type ) & ApplicableToGroupHeaderItems );
}

void Theme::ContentItem::setFont( const QFont &font )
{
  mFont = font;
}

void Theme::ContentItem::save( QDataStream &stream ) const
{
  stream << (int)mType;
  stream << mFlags;
  stream << mFont;
  stream << mCustomColor;
}

bool Theme::ContentItem::load( QDataStream &stream, int /*themeVersion*/ )
{
  int val;

  stream >> val;
  mType = static_cast< Type >( val );
  switch( mType )
  {
    case Subject:
    case Date:
    case SenderOrReceiver:
    case Sender:
    case Receiver:
    case Size:
    case ReadStateIcon:
    case AttachmentStateIcon:
    case RepliedStateIcon:
    case GroupHeaderLabel:
    case ActionItemStateIcon:
    case ImportantStateIcon:
    case SpamHamStateIcon:
    case WatchedIgnoredStateIcon:
    case ExpandedStateIcon:
    case EncryptionStateIcon:
    case SignatureStateIcon:
    case VerticalLine:
    case HorizontalSpacer:
    case MostRecentDate:
    case CombinedReadRepliedStateIcon:
    case TagList:
      // ok
    break;
    default:
      kDebug() << "Invalid content item type";
      return false; // b0rken
    break;
  }

  stream >> mFlags;
  stream >> mFont;
  stream >> mCustomColor;
  if ( mFlags & UseCustomColor )
  {
    if ( !mCustomColor.isValid() )
      mFlags &= ~UseCustomColor;
  }
  return true;
}




Theme::Row::Row()
{
}

Theme::Row::Row( const Row &src )
{
  for ( QList< ContentItem * >::ConstIterator it = src.mLeftItems.constBegin(); it != src.mLeftItems.constEnd() ; ++it )
    addLeftItem( new ContentItem( *( *it ) ) );
  for ( QList< ContentItem * >::ConstIterator it = src.mRightItems.constBegin(); it != src.mRightItems.constEnd() ; ++it )
    addRightItem( new ContentItem( *( *it ) ) );
}


Theme::Row::~Row()
{
  removeAllLeftItems();
  removeAllRightItems();
}

void Theme::Row::removeAllLeftItems()
{
  while( !mLeftItems.isEmpty() )
    delete mLeftItems.takeFirst();
}

void Theme::Row::removeAllRightItems()
{
  while( !mRightItems.isEmpty() )
    delete mRightItems.takeFirst();
}

void Theme::Row::insertLeftItem( int idx, ContentItem * item )
{
  if ( idx >= mLeftItems.count() )
  {
    mLeftItems.append( item );
    return;
  }
  mLeftItems.insert( idx, item );
}

void Theme::Row::insertRightItem( int idx, ContentItem * item )
{
  if ( idx >= mRightItems.count() )
  {
    mRightItems.append( item );
    return;
  }
  mRightItems.insert( idx, item );
}

bool Theme::Row::containsTextItems() const
{
  for ( QList< ContentItem * >::ConstIterator it = mLeftItems.constBegin(); it != mLeftItems.constEnd() ; ++it )
  {
    if ( ( *it )->displaysText() )
      return true;
  }
  for ( QList< ContentItem * >::ConstIterator it = mRightItems.constBegin(); it != mRightItems.constEnd() ; ++it )
  {
    if ( ( *it )->displaysText() )
      return true;
  }
  return false;
}

void Theme::Row::save( QDataStream &stream ) const
{
  stream << (int)mLeftItems.count();

  int cnt = mLeftItems.count();

  for ( int i = 0; i < cnt ; ++i )
  {
    ContentItem * ci = mLeftItems.at( i );
    ci->save( stream );
  }

  stream << (int)mRightItems.count();

  cnt = mRightItems.count();

  for ( int i = 0; i < cnt ; ++i )
  {
    ContentItem * ci = mRightItems.at( i );
    ci->save( stream );
  }
}

bool Theme::Row::load( QDataStream &stream, int themeVersion )
{
  removeAllLeftItems();
  removeAllRightItems();

  int val;

  // left item count

  stream >> val;

  if ( ( val < 0 ) || ( val > 50 ) )
    return false; // senseless

  for ( int i = 0; i < val ; ++i )
  {
    ContentItem * ci = new ContentItem( ContentItem::Subject ); // dummy type
    if ( !ci->load( stream, themeVersion ) )
    {
      kDebug() << "Left content item loading failed";
      delete ci;
      return false;
    }
    addLeftItem( ci );
  }

  // right item count

  stream >> val;

  if ( ( val < 0 ) || ( val > 50 ) )
    return false; // senseless

  for ( int i = 0; i < val ; ++i )
  {
    ContentItem * ci = new ContentItem( ContentItem::Subject ); // dummy type
    if ( !ci->load( stream, themeVersion ) )
    {
      kDebug() << "Right content item loading failed";
      delete ci;
      return false;
    }
    addRightItem( ci );
  }

  return true;
}


Theme::Column::SharedRuntimeData::SharedRuntimeData( bool currentlyVisible, int currentWidth )
  : mReferences( 0 ), mCurrentlyVisible( currentlyVisible ), mCurrentWidth( currentWidth )
{
}

Theme::Column::SharedRuntimeData::~SharedRuntimeData()
{
}

void Theme::Column::SharedRuntimeData::addReference()
{
  mReferences++;
}

bool Theme::Column::SharedRuntimeData::deleteReference()
{
  mReferences--;
  Q_ASSERT( mReferences >= 0 );
  return mReferences > 0;
}

void Theme::Column::SharedRuntimeData::save( QDataStream &stream ) const
{
  stream << mCurrentlyVisible;
  stream << mCurrentWidth;
}

bool Theme::Column::SharedRuntimeData::load( QDataStream &stream, int /* themeVersion */ )
{
  stream >> mCurrentlyVisible;
  stream >> mCurrentWidth;
  if ( mCurrentWidth > 10000 )
  {
    kDebug() << "Theme has insane column width " << mCurrentWidth << " chopping to 100";
    mCurrentWidth = 100; // avoid really insane values
  }
  return (mCurrentWidth >= -1);
}


Theme::Column::Column()
  : mVisibleByDefault( true ),
    mIsSenderOrReceiver( false ),
    mMessageSorting( SortOrder::NoMessageSorting )
{
  mSharedRuntimeData = new SharedRuntimeData( true, -1 );
  mSharedRuntimeData->addReference();
}

Theme::Column::Column( const Column &src )
{
  mLabel = src.mLabel;
  mPixmapName = src.mPixmapName;
  mVisibleByDefault = src.mVisibleByDefault;
  mIsSenderOrReceiver = src.mIsSenderOrReceiver;
  mMessageSorting = src.mMessageSorting;

  mSharedRuntimeData = src.mSharedRuntimeData;
  mSharedRuntimeData->addReference();

  for ( QList< Row * >::ConstIterator it = src.mMessageRows.constBegin(); it != src.mMessageRows.constEnd() ; ++it )
    addMessageRow( new Row( *( *it ) ) );
  for ( QList< Row * >::ConstIterator it = src.mGroupHeaderRows.constBegin(); it != src.mGroupHeaderRows.constEnd() ; ++it )
    addGroupHeaderRow( new Row( *( *it ) ) );
}

Theme::Column::~Column()
{
  removeAllMessageRows();
  removeAllGroupHeaderRows();
  if( !( mSharedRuntimeData->deleteReference() ) )
    delete mSharedRuntimeData;
}

void Theme::Column::detach()
{
  if( mSharedRuntimeData->referenceCount() < 2 )
    return; // nothing to detach
  mSharedRuntimeData->deleteReference();

  mSharedRuntimeData = new SharedRuntimeData( mVisibleByDefault, -1 );
  mSharedRuntimeData->addReference();
    
}

void Theme::Column::removeAllMessageRows()
{
  while ( !mMessageRows.isEmpty() )
    delete mMessageRows.takeFirst();
}

void Theme::Column::removeAllGroupHeaderRows()
{
  while ( !mGroupHeaderRows.isEmpty() )
    delete mGroupHeaderRows.takeFirst();
}

void Theme::Column::insertMessageRow( int idx, Row * row )
{
  if ( idx >= mMessageRows.count() )
  {
    mMessageRows.append( row );
    return;
  }
  mMessageRows.insert( idx, row );
}

void Theme::Column::insertGroupHeaderRow( int idx, Row * row )
{
  if ( idx >= mGroupHeaderRows.count() )
  {
    mGroupHeaderRows.append( row );
    return;
  }
  mGroupHeaderRows.insert( idx, row );
}

bool Theme::Column::containsTextItems() const
{
  for ( QList< Row * >::ConstIterator it = mMessageRows.constBegin(); it != mMessageRows.constEnd() ; ++it )
  {
    if ( ( *it )->containsTextItems() )
      return true;
  }
  for ( QList< Row * >::ConstIterator it = mGroupHeaderRows.constBegin(); it != mGroupHeaderRows.constEnd() ; ++it )
  {
    if ( ( *it )->containsTextItems() )
      return true;
  }
  return false;
}

void Theme::Column::save( QDataStream &stream ) const
{
  stream << mLabel;
  stream << mPixmapName;
  stream << mVisibleByDefault;
  stream << mIsSenderOrReceiver;
  stream << (int)mMessageSorting;

  stream << (int)mGroupHeaderRows.count();

  int cnt = mGroupHeaderRows.count();

  for ( int i = 0; i < cnt ; ++i )
  {
    Row * row = mGroupHeaderRows.at( i );
    row->save( stream );
  }

  stream << (int)mMessageRows.count();

  cnt = mMessageRows.count();

  for ( int i = 0; i < cnt ; ++i )
  {
    Row * row = mMessageRows.at( i );
    row->save( stream );
  }

  // added in version 0x1014
  mSharedRuntimeData->save( stream );

}

bool Theme::Column::load( QDataStream &stream, int themeVersion )
{
  removeAllGroupHeaderRows();
  removeAllMessageRows();

  stream >> mLabel;

  if ( themeVersion >= gThemeMinimumVersionWithColumnIcon )
    stream >> mPixmapName;

  stream >> mVisibleByDefault;
  stream >> mIsSenderOrReceiver;

  int val;

  stream >> val;
  mMessageSorting = static_cast< SortOrder::MessageSorting >( val );
  if ( !SortOrder::isValidMessageSorting( mMessageSorting ) )
  {
    kDebug() << "Invalid message sorting";
    return false;
  }

  if ( themeVersion < gThemeMinimumVersionWithSortingByNewUnreadStatusAllowed )
  {
    // The default "Classic" theme "New/Unread" column had sorting disabled here.
    // We want to be nice to the existing users and automatically set
    // the new sorting method for this column (so they don't have to make the
    // complex steps to set it by themselves).
    // This piece of code isn't strictly required: it's just a niceness :)
    if ( ( mMessageSorting == SortOrder::NoMessageSorting ) && ( mLabel == i18n( "New/Unread" ) ) )
      mMessageSorting = SortOrder::SortMessagesByNewUnreadStatus;
  }

  // group header row count
  stream >> val;

  if ( ( val < 0 ) || ( val > 50 ) )
  {
    kDebug() << "Invalid group header row count";
    return false; // senseless
  }

  for ( int i = 0; i < val ; i++ )
  {
    Row * row = new Row();
    if ( !row->load( stream, themeVersion ) )
    {
      kDebug() << "Group header row loading failed";
      delete row;
      return false;
    }
    addGroupHeaderRow( row );
  }

  // message row count
  stream >> val;

  if ( ( val < 0 ) || ( val > 50 ) )
  {
    kDebug() << "Invalid message row count";
    return false; // senseless
  }

  for ( int i = 0; i < val ; i++ )
  {
    Row * row = new Row();
    if ( !row->load( stream, themeVersion ) )
    {
      kDebug() << "Message row loading failed";
      delete row;
      return false;
    }
    addMessageRow( row );
  }

  if ( themeVersion >= gThemeMinimumVersionWithColumnRuntimeData )
  {
    // starting with version 0x1014 we have runtime data too
    if( !mSharedRuntimeData->load( stream, themeVersion ) )
    {
      kDebug() << "Shared runtime data loading failed";
      return false;
    }
  } else {
    // assume default shared data
    mSharedRuntimeData->setCurrentlyVisible( mVisibleByDefault );
    mSharedRuntimeData->setCurrentWidth( -1 );
  }

  return true;
}




Theme::Theme()
  : OptionSet()
{
  mGroupHeaderBackgroundMode = AutoColor;
  mViewHeaderPolicy = ShowHeaderAlways;
  mIconSize = gThemeDefaultIconSize;
}

Theme::Theme( const QString &name, const QString &description )
  : OptionSet( name, description )
{
  mGroupHeaderBackgroundMode = AutoColor;
  mGroupHeaderBackgroundStyle = StyledJoinedRect;
  mViewHeaderPolicy = ShowHeaderAlways;
  mIconSize = gThemeDefaultIconSize;
}


Theme::Theme( const Theme &src )
  : OptionSet( src )
{
  mGroupHeaderBackgroundMode = src.mGroupHeaderBackgroundMode;
  mGroupHeaderBackgroundColor = src.mGroupHeaderBackgroundColor;
  mGroupHeaderBackgroundStyle = src.mGroupHeaderBackgroundStyle;
  mViewHeaderPolicy = src.mViewHeaderPolicy;
  mIconSize = src.mIconSize;

  for ( QList< Column * >::ConstIterator it = src.mColumns.constBegin(); it != src.mColumns.constEnd() ; ++it )
    addColumn( new Column( *( *it ) ) );
}

Theme::~Theme()
{
  removeAllColumns();
}

void Theme::detach()
{
  for ( QList< Column * >::Iterator it = mColumns.begin(); it != mColumns.end() ; ++it )
    ( *it )->detach();
}

void Theme::resetColumnState()
{
  for ( QList< Column * >::Iterator it = mColumns.begin(); it != mColumns.end() ; ++it )
  {
    ( *it )->setCurrentlyVisible( ( *it )->visibleByDefault() );
    ( *it )->setCurrentWidth( -1 );
  }
}

void Theme::resetColumnSizes()
{
  for ( QList< Column * >::Iterator it = mColumns.begin(); it != mColumns.end() ; ++it )
    ( *it )->setCurrentWidth( -1 );
}


void Theme::removeAllColumns()
{
  while ( !mColumns.isEmpty() )
    delete mColumns.takeFirst();
}

void Theme::insertColumn( int idx, Column * column )
{
  if ( idx >= mColumns.count() )
  {
    mColumns.append( column );
    return;
  }
  mColumns.insert( idx, column );
}

void Theme::setGroupHeaderBackgroundMode( GroupHeaderBackgroundMode bm )
{
  mGroupHeaderBackgroundMode = bm;
  if ( ( bm == CustomColor ) && !mGroupHeaderBackgroundColor.isValid() )
    mGroupHeaderBackgroundColor = QColor( 127, 127, 127 ); // something neutral     
}

QList< QPair< QString, int > > Theme::enumerateViewHeaderPolicyOptions()
{
  QList< QPair< QString, int > > ret;
  ret.append( QPair< QString, int >( i18n( "Never Show" ), NeverShowHeader ) );
  ret.append( QPair< QString, int >( i18n( "Always Show" ), ShowHeaderAlways ) );
  return ret;
}

QList< QPair< QString, int > > Theme::enumerateGroupHeaderBackgroundStyles()
{
  QList< QPair< QString, int > > ret;
  ret.append( QPair< QString, int >( i18n( "Plain Rectangles" ), PlainRect ) );
  ret.append( QPair< QString, int >( i18n( "Plain Joined Rectangle" ), PlainJoinedRect ) );
  ret.append( QPair< QString, int >( i18n( "Rounded Rectangles" ), RoundedRect ) );
  ret.append( QPair< QString, int >( i18n( "Rounded Joined Rectangle" ), RoundedJoinedRect ) );
  ret.append( QPair< QString, int >( i18n( "Gradient Rectangles" ), GradientRect ) );
  ret.append( QPair< QString, int >( i18n( "Gradient Joined Rectangle" ), GradientJoinedRect ) );
  ret.append( QPair< QString, int >( i18n( "Styled Rectangles" ), StyledRect ) );
  ret.append( QPair< QString, int >( i18n( "Styled Joined Rectangles" ), StyledJoinedRect ) );

  return ret;
}

void Theme::setIconSize( int iconSize )
{
  mIconSize = iconSize;
  if ( ( mIconSize < 8 ) || ( mIconSize > 64 ) )
    mIconSize = gThemeDefaultIconSize;
}

bool Theme::load( QDataStream &stream )
{
  removeAllColumns();

  int themeVersion;

  stream >> themeVersion;

  // We support themes starting at version gThemeMinimumSupportedVersion (0x1013 actually)

  if (
       ( themeVersion > gThemeCurrentVersion ) ||
       ( themeVersion < gThemeMinimumSupportedVersion )
     )
  {
    kDebug() << "Invalid theme version";
    return false; // b0rken (invalid version)
  }

  int val;

  stream >> val;
  mGroupHeaderBackgroundMode = (GroupHeaderBackgroundMode)val;
  switch(mGroupHeaderBackgroundMode)
  {
    case Transparent:
    case AutoColor:
    case CustomColor:
      // ok
    break;
    default:
      kDebug() << "Invalid theme group header background mode";
      return false; // b0rken
    break;
  }

  stream >> mGroupHeaderBackgroundColor;

  stream >> val;
  mGroupHeaderBackgroundStyle = (GroupHeaderBackgroundStyle)val;
  switch(mGroupHeaderBackgroundStyle)
  {
    case PlainRect:
    case PlainJoinedRect:
    case RoundedRect:
    case RoundedJoinedRect:
    case GradientRect:
    case GradientJoinedRect:
    case StyledRect:
    case StyledJoinedRect:
      // ok
    break;
    default:
      kDebug() << "Invalid theme group header background style";
      return false; // b0rken
    break;
  }

  stream >> val;
  mViewHeaderPolicy = (ViewHeaderPolicy)val;
  switch(mViewHeaderPolicy)
  {
    case ShowHeaderAlways:
    case NeverShowHeader:
      // ok
    break;
    default:
      kDebug() << "Invalid theme view header policy";
      return false; // b0rken
    break;
  }

  if ( themeVersion >= gThemeMinimumVersionWithIconSizeField )
  {
    // icon size parameter
    stream >> mIconSize;
    if ( ( mIconSize < 8 ) || ( mIconSize > 64 ) )
      mIconSize = gThemeDefaultIconSize; // limit insane values
  } else {
    mIconSize = gThemeDefaultIconSize;
  }

  // column count
  stream >> val;
  if ( val < 1 || val > 50 )
    return false; // plain b0rken ( negative, zero or more than 50 columns )

  for ( int i = 0; i < val ; i++ )
  {
    Column * col = new Column();
    if ( !col->load( stream, themeVersion ) )
    {
      kDebug() << "Column loading failed";
      delete col;
      return false;
    }
    addColumn( col );
  }  

  return true;
}

void Theme::save( QDataStream &stream ) const
{
  stream << (int)gThemeCurrentVersion;

  stream << (int)mGroupHeaderBackgroundMode;
  stream << mGroupHeaderBackgroundColor;
  stream << (int)mGroupHeaderBackgroundStyle;
  stream << (int)mViewHeaderPolicy;
  stream << mIconSize;

  stream << (int)mColumns.count();

  int cnt = mColumns.count();

  for ( int i = 0; i < cnt ; i++ )
  {
    Column * col = mColumns.at( i );
    col->save( stream );
  }
}

} // namespace Core

} // namespace MessageListView

} // namespace KMail

