/*
    Copyright (c) 2009 Stephen Kelly <steveire@gmail.com>

    This library is free software; you can redistribute it and/or modify it
    under the terms of the GNU Library General Public License as published by
    the Free Software Foundation; either version 2 of the License, or (at your
    option) any later version.

    This library 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 Library General Public
    License for more details.

    You should have received a copy of the GNU Library General Public License
    along with this library; see the file COPYING.LIB.  If not, write to the
    Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
    02110-1301, USA.
*/

#include "dragdropmanager_p.h"
#include "specialcollectionattribute_p.h"
#include "collectionutils_p.h"

#include <QApplication>
#include <QDropEvent>
#include <QMenu>
#include <QDrag>

#include <KDE/KIcon>
#include <KDE/KLocalizedString>
#include <KDE/KUrl>

#include "akonadi/collection.h"
#include "akonadi/entitytreemodel.h"

using namespace Akonadi;

DragDropManager::DragDropManager( QAbstractItemView *view )
  : mShowDropActionMenu( true ), mIsManualSortingActive( false ), m_view( view )
{
}

Akonadi::Collection DragDropManager::currentDropTarget( QDropEvent *event ) const
{
  const QModelIndex index = m_view->indexAt( event->pos() );
  Collection collection = m_view->model()->data( index, EntityTreeModel::CollectionRole ).value<Collection>();
  if ( !collection.isValid() ) {
    const Item item = m_view->model()->data( index, EntityTreeModel::ItemRole ).value<Item>();
    if ( item.isValid() ) {
      collection = m_view->model()->data( index.parent(), EntityTreeModel::CollectionRole ).value<Collection>();
    }
  }

  return collection;
}

bool DragDropManager::dropAllowed( QDragMoveEvent *event ) const
{
  // Check if the collection under the cursor accepts this data type
  const Collection targetCollection = currentDropTarget( event );
  if ( targetCollection.isValid() ) {
    const QStringList supportedContentTypes = targetCollection.contentMimeTypes();

    const QMimeData *data = event->mimeData();
    const KUrl::List urls = KUrl::List::fromMimeData( data );
    foreach ( const KUrl &url, urls ) {
      const Collection collection = Collection::fromUrl( url );
      if ( collection.isValid() ) {
        if ( !supportedContentTypes.contains( Collection::mimeType() ) &&
             !supportedContentTypes.contains( Collection::virtualMimeType() ) ) {
          break;
        }

        // Check if we don't try to drop on one of the children
        if ( hasAncestor( m_view->indexAt( event->pos() ), collection.id() ) ) {
          break;
        }
      } else { // This is an item.
        const QString type = url.queryItems()[ QString::fromLatin1( "type" ) ];
        if ( !supportedContentTypes.contains( type ) ) {
          break;
        }
      }

      return true;
    }
  }

  return false;
}

bool DragDropManager::hasAncestor( const QModelIndex &_index, Collection::Id parentId ) const
{
  QModelIndex index( _index );
  while ( index.isValid() ) {
    if ( m_view->model()->data( index, EntityTreeModel::CollectionIdRole ).toLongLong() == parentId ) {
      return true;
    }

    index = index.parent();
  }

  return false;
}

bool DragDropManager::processDropEvent( QDropEvent *event, bool &menuCanceled, bool dropOnItem )
{
  const Collection targetCollection = currentDropTarget( event );
  if ( !targetCollection.isValid() ) {
    return false;
  }

  if ( !mIsManualSortingActive && !dropOnItem ) {
    return false;
  }

  const QStringList supportedContentTypes = targetCollection.contentMimeTypes();

  const QMimeData *data = event->mimeData();
  const KUrl::List urls = KUrl::List::fromMimeData( data );
  foreach ( const KUrl &url, urls ) {
    const Collection collection = Collection::fromUrl( url );
    if ( !collection.isValid() ) {
      if ( !dropOnItem ) {
        return false;
      }
    }
  }

  int actionCount = 0;
  Qt::DropAction defaultAction;
  // TODO check if the source supports moving

  bool moveAllowed, copyAllowed, linkAllowed;
  moveAllowed = copyAllowed = linkAllowed = false;

  if ( ( targetCollection.rights() & ( Collection::CanCreateCollection | Collection::CanCreateItem ) ) &&
       ( event->possibleActions() & Qt::MoveAction ) ) {
    moveAllowed = true;
  }
  if ( ( targetCollection.rights() & ( Collection::CanCreateCollection | Collection::CanCreateItem ) ) &&
       ( event->possibleActions() & Qt::CopyAction ) ) {
    copyAllowed = true;
  }

  if ( ( targetCollection.rights() & Collection::CanLinkItem ) && ( event->possibleActions() & Qt::LinkAction ) ) {
    linkAllowed = true;
  }

  if ( mIsManualSortingActive && !dropOnItem ) {
    moveAllowed = true;
    copyAllowed = false;
    linkAllowed = false;
  }

  if ( !moveAllowed && !copyAllowed && !linkAllowed ) {
    kDebug() << "Cannot drop here:" << event->possibleActions() << m_view->model()->supportedDragActions() << m_view->model()->supportedDropActions();
    return false;
  }

  // first check whether the user pressed a modifier key to select a specific action
  if ( ( QApplication::keyboardModifiers() & Qt::ControlModifier ) &&
       ( QApplication::keyboardModifiers() & Qt::ShiftModifier ) ) {
    if ( linkAllowed ) {
      defaultAction = Qt::LinkAction;
      actionCount = 1;
    } else {
      return false;
    }
  } else if ( ( QApplication::keyboardModifiers() & Qt::ControlModifier ) ) {
    if ( copyAllowed ) {
      defaultAction = Qt::CopyAction;
      actionCount = 1;
    } else {
      return false;
    }
  } else if ( ( QApplication::keyboardModifiers() & Qt::ShiftModifier ) ) {
    if ( moveAllowed ) {
      defaultAction = Qt::MoveAction;
      actionCount = 1;
    } else {
      return false;
    }
  }

  if ( actionCount == 1 ) {
    kDebug() << "Selecting drop action" << defaultAction << ", there are no other possibilities";
    event->setDropAction( defaultAction );
    return true;
  }

  if ( !mShowDropActionMenu ) {
    if ( moveAllowed ) {
      defaultAction = Qt::MoveAction;
    } else if ( copyAllowed ) {
      defaultAction = Qt::CopyAction;
    } else if ( linkAllowed ) {
      defaultAction = Qt::LinkAction;
    } else {
      return false;
    }
    event->setDropAction( defaultAction );
    return true;
  }

  // otherwise show up a menu to allow the user to select an action
  QMenu popup( m_view );
  QAction* moveDropAction = 0;
  QAction* copyDropAction = 0;
  QAction* linkAction = 0;
  QString sequence;

  if ( moveAllowed ) {
    sequence = QKeySequence( Qt::ShiftModifier ).toString();
    sequence.chop( 1 ); // chop superfluous '+'
    moveDropAction = popup.addAction( KIcon( QString::fromLatin1( "go-jump" ) ), i18n( "&Move Here" ) + QLatin1Char( '\t' ) + sequence );
  }

  if ( copyAllowed ) {
    sequence = QKeySequence( Qt::ControlModifier ).toString();
    sequence.chop( 1 ); // chop superfluous '+'
    copyDropAction = popup.addAction( KIcon( QString::fromLatin1( "edit-copy" ) ), i18n( "&Copy Here" ) + QLatin1Char( '\t' ) + sequence );
  }

  if ( linkAllowed ) {
    sequence = QKeySequence( Qt::ControlModifier + Qt::ShiftModifier ).toString();
    sequence.chop( 1 ); // chop superfluous '+'
    linkAction = popup.addAction( KIcon( QLatin1String( "edit-link" ) ), i18n( "&Link Here" ) + QLatin1Char( '\t' ) + sequence );
  }

  popup.addSeparator();
  popup.addAction( KIcon( QString::fromLatin1( "process-stop" ) ), i18n( "C&ancel" ) + QLatin1Char( '\t' ) + QKeySequence( Qt::Key_Escape ).toString() );

  QAction *activatedAction = popup.exec( QCursor::pos() );
  if ( !activatedAction ) {
    menuCanceled = true;
    return false;
  } else if ( activatedAction == moveDropAction ) {
    event->setDropAction( Qt::MoveAction );
  } else if ( activatedAction == copyDropAction ) {
    event->setDropAction( Qt::CopyAction );
  } else if ( activatedAction == linkAction ) {
    event->setDropAction( Qt::LinkAction );
  } else {
    menuCanceled = true;
    return false;
  }
  return true;
}

void DragDropManager::startDrag( Qt::DropActions supportedActions )
{
  QModelIndexList indexes;
  bool sourceDeletable = true;
  foreach ( const QModelIndex &index, m_view->selectionModel()->selectedRows() ) {
    if ( !m_view->model()->flags( index ).testFlag( Qt::ItemIsDragEnabled ) ) {
      continue;
    }

    if ( sourceDeletable ) {
      Collection source = index.data( EntityTreeModel::CollectionRole ).value<Collection>();
      if ( !source.isValid() ) {
        // index points to an item
        source = index.data( EntityTreeModel::ParentCollectionRole ).value<Collection>();
        sourceDeletable = source.rights() & Collection::CanDeleteItem;
      } else {
        // index points to a collection
        sourceDeletable = ( source.rights() & Collection::CanDeleteCollection ) && !source.hasAttribute<SpecialCollectionAttribute>() && !source.isVirtual();
      }
    }
    indexes.append( index );
  }

  if ( indexes.isEmpty() ) {
    return;
  }

  QMimeData *mimeData = m_view->model()->mimeData( indexes );
  if ( !mimeData ) {
    return;
  }

  QDrag *drag = new QDrag( m_view );
  drag->setMimeData( mimeData );
  if ( indexes.size() > 1 ) {
    drag->setPixmap( KIcon( QLatin1String( "document-multiple" ) ).pixmap( QSize( 22, 22 ) ) );
  } else {
    QPixmap pixmap = indexes.first().data( Qt::DecorationRole ).value<QIcon>().pixmap( QSize( 22, 22 ) );
    if ( pixmap.isNull() ) {
      pixmap = KIcon( QLatin1String( "text-plain" ) ).pixmap( QSize( 22, 22 ) );
    }
    drag->setPixmap( pixmap );
  }

  if ( !sourceDeletable ) {
    supportedActions &= ~Qt::MoveAction;
  }

  Qt::DropAction defaultAction = Qt::IgnoreAction;
  if ( ( QApplication::keyboardModifiers() & Qt::ControlModifier ) &&
       ( QApplication::keyboardModifiers() & Qt::ShiftModifier ) ) {
    defaultAction = Qt::LinkAction;
  } else if ( ( QApplication::keyboardModifiers() & Qt::ControlModifier ) ) {
    defaultAction = Qt::CopyAction;
  } else if ( ( QApplication::keyboardModifiers() & Qt::ShiftModifier ) ) {
    defaultAction = Qt::MoveAction;
  }

  drag->exec( supportedActions, defaultAction );
}

bool DragDropManager::showDropActionMenu() const
{
  return mShowDropActionMenu;
}

void DragDropManager::setShowDropActionMenu( bool show )
{
  mShowDropActionMenu = show;
}

bool DragDropManager::isManualSortingActive() const
{
  return mIsManualSortingActive;
}

void DragDropManager::setManualSortingActive(bool active)
{
  mIsManualSortingActive = active;
}

