/*
    KNode, the KDE newsreader
    Copyright (c) 2004-2005 Volker Krause <vkrause@kde.org>
    Copyright (c) 2009 Olivier Trichet <nive@nivalis.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.
    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, US
*/

#include "kncollectionview.h"

#include "knglobals.h"
#include "knconfig.h"
#include "knnntpaccount.h"
#include "knaccountmanager.h"
#include "kngroup.h"
#include "kngroupmanager.h"
#include "knfolder.h"
#include "knfoldermanager.h"
#include "kncollectionviewitem.h"
#include "settings.h"

#include <kiconloader.h>
#include <klocale.h>

#include <QDropEvent>
#include <QPainter>



KNCollectionView::KNCollectionView( QWidget *parent ) :
  FolderTreeWidget( parent ),
  mActiveItem( 0 ),
  mDragTargetItem( 0 )
{
  setDragEnabled(true);
  setDropIndicatorShown( false ); // We draw our own (see paintEvent)

  // add unread and total columns if necessary
  loadLayout();

  readConfig();

  // load accounts and folders
  reloadAccounts();
  reloadFolders();

  // connect to the account manager
  KNAccountManager* am = knGlobals.accountManager();
  connect( am, SIGNAL(accountAdded(KNNntpAccount::Ptr)), SLOT(addAccount(KNNntpAccount::Ptr)) );
  connect( am, SIGNAL(accountRemoved(KNNntpAccount::Ptr)), SLOT(removeAccount(KNNntpAccount::Ptr)) );
  connect( am, SIGNAL(accountModified(KNNntpAccount::Ptr)), SLOT(updateAccount(KNNntpAccount::Ptr)) );

  // connect to the group manager
  KNGroupManager* gm = knGlobals.groupManager();
  connect( gm, SIGNAL(groupAdded(KNGroup::Ptr)), SLOT(addGroup(KNGroup::Ptr)) );
  connect( gm, SIGNAL(groupRemoved(KNGroup::Ptr)), SLOT(removeGroup(KNGroup::Ptr)) );
  connect( gm, SIGNAL(groupUpdated(KNGroup::Ptr)), SLOT(updateGroup(KNGroup::Ptr)) );

  // connect to the folder manager
  KNFolderManager* fm = knGlobals.folderManager();
  connect( fm, SIGNAL(folderAdded(KNFolder::Ptr)), SLOT(addPendingFolders()) );
  connect( fm, SIGNAL(folderRemoved(KNFolder::Ptr)), SLOT(removeFolder(KNFolder::Ptr)) );
  connect( fm, SIGNAL(folderActivated(KNFolder::Ptr)), SLOT(activateFolder(KNFolder::Ptr)) );

  // Edition of label
  setEditTriggers( QAbstractItemView::NoEditTriggers );
}


KNCollectionView::~KNCollectionView()
{
  writeConfig();
}


void KNCollectionView::loadLayout()
{
  addLabelColumn( i18n("Name") );
  addUnreadColumn( i18n("Unread") );
  addTotalColumn( i18n("Total") );

  restoreLayout( knGlobals.config(), "GroupView" );

  if ( sortColumn() == -1 ) {
    sortByColumn( labelColumnIndex(), Qt::AscendingOrder );
  }
}

void KNCollectionView::readConfig()
{
  // font
  setFont( knGlobals.settings()->groupListFont() );
}


void KNCollectionView::writeConfig()
{
  saveLayout( knGlobals.config(), "GroupView" );
}



void KNCollectionView::addAccount( KNNntpAccount::Ptr a )
{
  // add account item
  KNCollectionViewItem* item = new KNCollectionViewItem( this, FolderTreeWidgetItem::News );
  a->setListItem( item );
  item->setExpanded( a->wasOpen() );

  // add groups for this account
  KNGroup::List groups = knGlobals.groupManager()->groupsOfAccount( a );
  for ( KNGroup::List::Iterator it = groups.begin(); it != groups.end(); ++it ) {
    KNCollectionViewItem *gitem = new KNCollectionViewItem( item, FolderTreeWidgetItem::News );
    (*it)->setListItem( gitem );
    (*it)->updateListItem();
  }
}


void KNCollectionView::removeAccount( KNNntpAccount::Ptr a )
{
  if(!a->listItem())
    return;
  KNCollectionViewItem *child = 0;
  KNCollectionViewItem *aitem = a->listItem();
  while ( ( child = static_cast<KNCollectionViewItem*>( aitem->takeChild( 0 ) ) ) ) {
    removeGroup( boost::static_pointer_cast<KNGroup>( child->collection() ) );
  }

  delete aitem;
  a->setListItem(0);
}


void KNCollectionView::updateAccount( KNNntpAccount::Ptr a )
{
  a->updateListItem();
}


void KNCollectionView::reloadAccounts()
{
  KNNntpAccount::List list = knGlobals.accountManager()->accounts();
  for ( KNNntpAccount::List::Iterator it = list.begin(); it != list.end(); ++it ) {
    removeAccount( *it );
    addAccount( *it );
  }
}



void KNCollectionView::addGroup( KNGroup::Ptr g )
{
  if (!g->account()->listItem())
    return;

  KNCollectionViewItem *gitem =
      new KNCollectionViewItem( g->account()->listItem(), FolderTreeWidgetItem::News );
  g->setListItem(gitem);
  updateGroup(g);
}


void KNCollectionView::removeGroup( KNGroup::Ptr g )
{
  if (!g->listItem())
    return;

  KNCollectionViewItem *item = g->listItem();
  item->setDisabled(true);
  item->setHidden( true ); // work around bug 248256
  g->setListItem(0);
  updateGroup(g);
  // after deactivating, hiding the item, removing it from the group and updating the group,
  // we let Qt process any events that may be pending because of those actions or because
  // the context menu was used to unsubscribe from this group. We do it here rather than
  // leaving them for later in KNMainWidget::secureProcessEvents(), when events concerning
  // the group item would be sent to it after it was deleted. Occurs on Mac OS X.
  qApp->processEvents();
  // now it should be safe to delete item.
  delete item;
}


void KNCollectionView::updateGroup( KNGroup::Ptr g )
{
  g->updateListItem();
}



void KNCollectionView::addFolder( KNFolder::Ptr f )
{
  KNCollectionViewItem *it;

  if (!f->parent()) {
    // root folder
    it = new KNCollectionViewItem(this, FolderTreeWidgetItem::Local);
  } else {
    // make sure the parent folder has already been added
    if (!f->parent()->listItem())
      addFolder( boost::static_pointer_cast<KNFolder>( f->parent() ) );
    // handle special folders
    FolderTreeWidgetItem::FolderType type = FolderTreeWidgetItem::Other;
    switch ( f->id() ) {
      case 1:
        type = FolderTreeWidgetItem::Drafts; break;
      case 2:
        type = FolderTreeWidgetItem::Outbox; break;
      case 3:
        type = FolderTreeWidgetItem::SentMail; break;
    }
    it = new KNCollectionViewItem( f->parent()->listItem(), FolderTreeWidgetItem::Local, type );
  }
  f->setListItem( it );
  updateFolder( f );
}


void KNCollectionView::removeFolder( KNFolder::Ptr f)
{
  if(!f->listItem())
    return;
  KNCollectionViewItem *child = 0;
  KNCollectionViewItem *it = f->listItem();
  while ( ( child = static_cast<KNCollectionViewItem*>( it->takeChild( 0 ) ) ) ) {
    removeFolder( boost::static_pointer_cast<KNFolder>( child->collection() ) );
  }

  f->listItem()->setHidden( true ); // work around bug 248256
  delete f->listItem();
  f->setListItem(0);
}


void KNCollectionView::reloadFolders()
{
  // remove existing folder items
  removeFolder(knGlobals.folderManager()->root());

  // add folder items
  addPendingFolders();
}


void KNCollectionView::addPendingFolders()
{
  KNFolder::List folders = knGlobals.folderManager()->folders();
  for ( KNFolder::List::Iterator it = folders.begin(); it != folders.end(); ++it )
    if ( !(*it)->listItem() )
      addFolder( (*it) );
  // now open the folders if they were open in the last session
  for ( KNFolder::List::Iterator it = folders.begin(); it != folders.end(); ++it ) {
    if ( (*it)->listItem()) {
      (*it)->listItem()->setExpanded( (*it)->wasOpen() );
    }
  }
}


void KNCollectionView::activateFolder( KNFolder::Ptr f )
{
  if(f->listItem())
    setActive( f->listItem() );
}


void KNCollectionView::updateFolder( KNFolder::Ptr f )
{
  f->updateListItem();
}


void KNCollectionView::reload()
{
  reloadAccounts();
  reloadFolders();
}

void KNCollectionView::setActive( QTreeWidgetItem *i )
{
  if (!i || mActiveItem == i)
    return;

  i->setSelected( true );
  setCurrentItem( i );
  mActiveItem = i;
}


void KNCollectionView::nextGroup()
{
  incCurrentFolder();
  setActive( currentItem() );
}


void KNCollectionView::prevGroup()
{
  decCurrentFolder();
  setActive( currentItem() );
}


void KNCollectionView::decCurrentFolder()
{
  QTreeWidgetItemIterator it( currentItem() );
  --it;
  FolderTreeWidgetItem* fti = static_cast<FolderTreeWidgetItem*>( *it );
  if (fti) {
    setFocus();
    setCurrentItem( fti );
  }
}


void KNCollectionView::incCurrentFolder()
{
  QTreeWidgetItemIterator it( currentItem() );
  ++it;
  FolderTreeWidgetItem* fti = static_cast<FolderTreeWidgetItem*>( *it );
  if (fti) {
    setFocus();
    setCurrentItem( fti );
  }
}


void KNCollectionView::selectCurrentFolder()
{
  FolderTreeWidgetItem* fti = static_cast<FolderTreeWidgetItem*>( currentItem() );
  if (fti) {
    setActive( fti );
  }
}

void KNCollectionView::contextMenuEvent( QContextMenuEvent *event )
{
  QTreeWidgetItem *item = itemAt( event->pos() );
  if(item) {
    emit contextMenu( item, event->globalPos() );
  }
}



void KNCollectionView::paintEvent( QPaintEvent *event )
{
  FolderTreeWidget::paintEvent( event );

  if ( mDragTargetItem ) {
    QRect rect = visualItemRect( mDragTargetItem );
    if ( rect.isValid() ) {
      QPainter p( viewport() );
      QBrush brush = KColorScheme( QPalette::Active, KColorScheme::Selection ).decoration( KColorScheme::HoverColor ).color();
      p.setPen( QPen( brush, 2 ) );
      p.drawRect( rect );
    }
  }
}


void KNCollectionView::dragEnterEvent( QDragEnterEvent *event )
{
  if ( event->mimeData() && event->mimeData()->hasFormat( "x-knode-drag/folder" ) ) {
    event->accept();
  } else {
    event->ignore();
  }
}

void KNCollectionView::dragLeaveEvent( QDragLeaveEvent *event )
{
  FolderTreeWidget::dragLeaveEvent( event );

  mDragTargetItem = 0;
  viewport()->update(); // repaint
}

void KNCollectionView::dragMoveEvent( QDragMoveEvent *event )
{
  // Needed for auto-scrolling
  FolderTreeWidget::dragMoveEvent( event );

  handleDragNDropEvent( event, false );
}

void KNCollectionView::dropEvent( QDropEvent *event )
{
  handleDragNDropEvent( event, true );

  mDragTargetItem = 0;
  viewport()->update(); // repaint
}

void KNCollectionView::handleDragNDropEvent( QDropEvent *event, bool enforceDrop )
{
  QTreeWidgetItem *item = itemAt( event->pos() );
  KNCollectionViewItem *fti = static_cast<KNCollectionViewItem*>( item );
  bool accepted = false;
  if ( fti && fti->collection() && fti->collection()->type()==KNCollection::CTfolder ) {
    const QMimeData *md = event->mimeData();
    if( md && md->hasFormat( "x-knode-drag/folder" ) ) {
      KNFolder::Ptr dest = boost::static_pointer_cast<KNFolder>( fti->collection() );
      KNFolderManager *folderManager = KNGlobals::self()->folderManager();
      if ( !enforceDrop ) {
        // Notify that the move is possible.
        accepted = folderManager->canMoveFolder( folderManager->currentFolder(), dest );
      } else {
        // Informs whether the move succeeded.
        accepted = folderManager->moveFolder( folderManager->currentFolder(), dest );
      }
    }
    /* TODO: redo drag and drop of articles.
    else if ( md && md->hasFormat( "message/news" ) ) {
      if ( !static_cast<KNFolder*>( fti->collection() )->isRootFolder() ) {   // don't drop articles on the root folder
        if ( !enforceDrop ) {
          accepted = true;
        } else if(f_olManager->currentFolder()) {
            if (e->dropAction() == Qt::MoveAction) {
              KNLocalArticle::List l;
              getSelectedArticles(l);
              a_rtManager->moveIntoFolder(l, dest);
              accepted = true;
            } else {
              KNArticle::List l;
              getSelectedArticles(l);
              a_rtManager->copyIntoFolder(l, dest);
              accepted = true;
            }
          }
          else if(g_rpManager->currentGroup()) {
            KNArticle::List l;
            getSelectedArticles(l);
            a_rtManager->copyIntoFolder(l, dest);
            accepted = true;
          }
      }
    }
    */
  }

  // Trigger an update of the target item (drawing of target's borders)
  bool repaintTarget =    ( mDragTargetItem == item )       /* target changes */
                       || ( !accepted && mDragTargetItem ); /* just entered an invalid target */
  mDragTargetItem = accepted ? item : 0;
  if ( repaintTarget ) {
    viewport()->update();
  }

  event->setAccepted( accepted );
}

QStringList KNCollectionView::mimeTypes() const
{
  QStringList l;
  l << "x-knode-drag/folder";
  return l;
}

void KNCollectionView::startDrag( Qt::DropActions supportedActions )
{
  Q_UNUSED( supportedActions );

  KNCollectionViewItem *item = static_cast<KNCollectionViewItem*>( currentItem() );
  // Only folders can be dragged
  if ( !item || !item->collection() || item->collection()->type()!=KNCollection::CTfolder ) {
    return;
  }

  KNFolder::Ptr folder = boost::static_pointer_cast<KNFolder>( item->collection() );

  // Cannot drag special folders
  if ( folder->isRootFolder() || folder->isStandardFolder() ) {
    return;
  }

  QMimeData *mimeData = new QMimeData();
  const QByteArray id( QString::number( folder->id() ).toLatin1() );
  mimeData->setData( "x-knode-drag/folder", id );

  QDrag *drag = new QDrag( this );
  drag->setMimeData( mimeData );
  drag->setPixmap( SmallIcon( "folder" ) );

  drag->exec( Qt::MoveAction );
}


