/* ====================================================================
 * Copyright (c) 2006-2008  Martin Hauner
 *                          http://subcommander.tigris.org
 *
 * Subcommander is licensed as described in the file doc/COPYING, which
 * you should have received as part of this distribution.
 * ====================================================================
 */

// sc
#include "config.h"
#include "BookmarkView.h"
#include "BookmarkViewModel.h"
#include "BookmarkViewItemModel.h"
#include "BookmarkViewProxyModel.h"
#include "Bookmark.h"
#include "BookmarkDragObject.h"
#include "DragDropMimeTypes.h"
#include "DragInfo.h"
#include "Actions.h"
#include "events/LviOnItemEvent.h"
#include "events/EventSupport.h"
#include "sublib/ActionStorage.h"
#include "sublib/DragDrop.h"
#include "sublib/Utility.h"
#include "util/String.h"

// qt
#include <QtCore/QModelIndex>
#include <QtCore/QTimer>
#include <QtGui/QApplication>
#include <QtGui/QLayout>
#include <QtGui/QScrollBar>
#include <QtGui/QHeaderView>
#include <QtGui/QItemDelegate>
#include <QtGui/QMouseEvent>
#include <QtGui/QStyleOptionViewItem>
#include <QtGui/QMovie>
#include <QtGui/QLabel>
#include <Qt3Support/Q3PopupMenu>

// sys
#include <cassert>


/**
 * ItemDelegate for the bookmark view.
 */
class BookmarkItemDelegate : public QItemDelegate
{
  typedef QItemDelegate super;

public:
  BookmarkItemDelegate( BookmarkView* parent ) : super(parent), _view(parent)
  {
  }

  /** Creates a text editor with frame. */
  QWidget* createEditor( QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index ) const
  {
    QWidget* editor = super::createEditor(parent,option,index);
    editor->setStyleSheet( "border: 1px solid black" );
    return editor;
  }

  void setEditorData( QWidget* editor, const QModelIndex& index ) const
  {
    super::setEditorData(editor,index);
  }

  void paint( QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex &index ) const
  {
    _index = index;

    QStyleOptionViewItem style(option);
    if( _index.data(CurrentWcRole).asBool() )
    {
      style.font.setBold(true);
    }
    super::paint(painter,style,_index);

    //_index = QModelIndex();
  }

  /** Draw expanded item icon.  */
  void drawDecoration( QPainter* painter, const QStyleOptionViewItem& option, const QRect& rect, const QPixmap& pixmap ) const
  {
    QPixmap decoration = pixmap;

//#ifndef Q_WS_MAC
    // on MacOSX we sometimes draw the expanded icon on the wrong item.
    // looks like the current index and _index are out of sync. !!?!?
    if( _view->isExpanded(_index) )
    {
      decoration = _index.data(ExpandedDecorationRole).value<QPixmap>();
    }
    else if( _index.data(ActiveRole).asBool() )
    {
      decoration = _index.data(ActiveDecorationRole).value<QPixmap>();
    }
//#endif // Q_WS_MAC

    super::drawDecoration(painter,option,rect,decoration);
  }

  QSize sizeHint( const QStyleOptionViewItem& option, const QModelIndex& index ) const
  {
#ifdef Q_WS_MAC
    return super::sizeHint(option,index);
#else 
    QSize result = super::sizeHint(option,index);
    result.setHeight( result.height() + 2 * _view->style()->pixelMetric(QStyle::PM_FocusFrameVMargin) );
    return result;
#endif // Q_WS_MAC
  }

private:
  BookmarkView*       _view;
  mutable QModelIndex _index;
};


/**
 * Project drop menu items:
 */
enum DropType
{
  DropNone = -1,
  DropTrunk,
  DropBranches,
  DropTags,
  DropWorkingCopy,
  DropRepository
};

BookmarkView::BookmarkView( BookmarkViewModel* model, ActionStorage* as,
  QWidget* parent ) : super(parent), _model(model), _actions(as),
  _dragTimerId(0)
{
  setMouseTracking(true);
  setDragEnabled(true);
  setAcceptDrops(true);
  setDropIndicatorShown(true);
  setDragDropMode(DragDrop);
  setEditTriggers(SelectedClicked);
  //setVerticalScrollMode(ScrollPerPixel);
  //setHorizontalScrollMode(ScrollPerPixel);
  //setIndentation(10);
  setRootIsDecorated(true);
  setItemsExpandable(true);
  setAnimated(false);
  setAllColumnsShowFocus(true);
  setSelectionMode( QAbstractItemView::SingleSelection );

  setItemDelegate(new BookmarkItemDelegate(this));

  _itemModel = new BookmarkViewItemModel();
  _proxyModel = new BookmarkViewProxyModel();
  _proxyModel->setSourceModel(_itemModel);
  setModel( _proxyModel );

  connect(
    this, SIGNAL(entered(const QModelIndex&)),
    this, SLOT(entered(const QModelIndex&)) );

  _prjMenu = new Q3PopupMenu(this);
  {
    QAction* action;

    action = _actions->getAction( ActionBookmarkProjectNew );
    action->addTo(_prjMenu);

    action = _actions->getAction( ActionBookmarkProjectNewWizard );
    action->addTo(_prjMenu);

    action = _actions->getAction( ActionBookmarkProjectRemove );
    action->addTo(_prjMenu);

    _prjMenu->insertSeparator();

    action = _actions->getAction( ActionBookmarkProjectNewRepository );
    action->addTo(_prjMenu);

    action = _actions->getAction( ActionBookmarkProjectNewWorkingCopy );
    action->addTo(_prjMenu);

    _prjMenu->insertSeparator();
    
    action = _actions->getAction( ActionBookmarkProjectEditTrunk );
    action->addTo(_prjMenu);

    action = _actions->getAction( ActionBookmarkProjectEditBranches );
    action->addTo(_prjMenu);

    action = _actions->getAction( ActionBookmarkProjectEditTags );
    action->addTo(_prjMenu);
  }

  _wcMenu = new Q3PopupMenu(this);
  {
    QAction* action;

    action = _actions->getAction( ActionBookmarkWcUpdate );
    action->addTo(_wcMenu);

    action = _actions->getAction( ActionBookmarkWcUpdateRev );
    _wcMenu->addAction(action); addAction(action);

    _wcMenu->insertSeparator();

    action = _actions->getAction( ActionBookmarkWcCommit );
    action->addTo(_wcMenu);

    action = _actions->getAction( ActionBookmarkLog );
    action->addTo(_wcMenu);

    action = _actions->getAction( ActionBookmarkBranchTag );
    action->addTo(_wcMenu);

    _wcMenu->insertSeparator();

    action = _actions->getAction( ActionBookmarkWcCurrent );
    action->addTo(_wcMenu);

    action = _actions->getAction( ActionBookmarkEdit );
    action->addTo(_wcMenu);

    action = _actions->getAction( ActionBookmarkRemove );
    action->addTo(_wcMenu);
  }

  _rpMenu = new Q3PopupMenu(this);
  {
    QAction* action;

    action = _actions->getAction( ActionBookmarkLog );
    action->addTo(_rpMenu);

    action = _actions->getAction( ActionBookmarkBranchTag );
    action->addTo(_rpMenu);

    action = _actions->getAction( ActionBookmarkRpCheckout );
    action->addTo(_rpMenu);

    action = _actions->getAction( ActionBookmarkRpSwitch );
    action->addTo(_rpMenu);

    _rpMenu->insertSeparator();

    action = _actions->getAction( ActionBookmarkEdit );
    action->addTo(_rpMenu);

    action = _actions->getAction( ActionBookmarkRemove );
    action->addTo(_rpMenu);
  }

  _prjDropMenu = new Q3PopupMenu(this);
  {
    _prjDropMenu->insertItem( _q("insert as url: trunk"), DropTrunk );
    _prjDropMenu->insertItem( _q("insert as url: branches"), DropBranches );
    _prjDropMenu->insertItem( _q("insert as url: tags"), DropTags );
    _prjDropMenu->insertItem( _q("insert as url: repository"), DropRepository );
    _prjDropMenu->insertItem( _q("insert as working copy"), DropWorkingCopy );
  }

  connect(
    _model,     SIGNAL(bookmarkAdded(Bookmark*)),
    _itemModel, SLOT(add(Bookmark*)) );
  connect(
    _model, SIGNAL(bookmarkRemoved(Bookmark*)),
    _itemModel, SLOT(remove(Bookmark*)) );
  connect(
    _model, SIGNAL(bookmarksModified()),  // bookmarksMoved???
    _itemModel, SLOT(sort()) );

  connect(
    _model, SIGNAL(bookmarkCurrent(Bookmark*)),
    this, SLOT(currentBookmark(Bookmark*)) );
  connect(
    _model, SIGNAL(bookmarkModified(Bookmark*)),
    this, SLOT(modifiedBookmark(Bookmark*)) );
  connect(
    _model, SIGNAL(showRenameBookmark(Bookmark*)),
    this, SLOT(renameBookmark(Bookmark*)) );

  _activeTimer = new QTimer(this);
  connect( _activeTimer, SIGNAL(timeout()), this, SLOT(activeTimer()) );
  _activeTimer->start(100);
}


BookmarkView::~BookmarkView()
{
}

void BookmarkView::activeTimer()
{
  _itemModel->advanceDecoration();
}

void BookmarkView::renameBookmark( Bookmark* bm )
{
  // @todo fix broken editor placement
  QModelIndex index = _proxyModel->index(bm);

  setCurrentIndex(index);
  scrollTo(index);
  edit(index);
}

void BookmarkView::currentBookmark( Bookmark* bm )
{
  // works without explicit refresh..
}

void BookmarkView::modifiedBookmark( Bookmark* bm )
{
  // direct slot connect..??
  _proxyModel->invalidate();
}

void BookmarkView::mousePressEvent( QMouseEvent* e )
{
  super::mousePressEvent(e);

  Bookmark* bookmark = getBookmark(indexAt(e->pos()));
  if( ! bookmark )
    return;

  if( e->button() == Qt::RightButton )
  {
    _model->setBookmarkMenu(bookmark);
  }
  else if( e->button() == Qt::LeftButton )
  {
    _dragPosMouseLeft = e->pos();
    _model->setBookmark(bookmark);

    if( bookmark->isWorkingCopy() )
    {
      QAction* action;
      action = _actions->getAction(ActionBookmarkOptionUpdates);
      action->setOn(bookmark->getUpdate());

      action = _actions->getAction(ActionBookmarkOptionRecursive);
      action->setOn(bookmark->getRecursive());

      action = _actions->getAction(ActionBookmarkOptionAutoRefresh);
      action->setOn(bookmark->getAutoRefresh());
    }
  }
}

void BookmarkView::mouseMoveEvent( QMouseEvent* e )
{
  if( !(e->buttons() & Qt::LeftButton) )
    return;

  // dragging?
  if( (_dragPosMouseLeft - e->pos()).manhattanLength() < QApplication::startDragDistance() )
    return;

  // bookmark?
  Bookmark* bookmark = getBookmark(indexAt(_dragPosMouseLeft));
  if( ! bookmark )
    return;

  QDrag*     drag = new QDrag(this);
  QMimeData* data = new QMimeData();
  data->setData( ScMimeTypeBookmark, QByteArray::number((int)bookmark->getId()) );
  drag->setMimeData(data);

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

void BookmarkView::contextMenuEvent( QContextMenuEvent* e )
{
  Bookmark* bookmark = getBookmark(indexAt(e->pos()));
  if( ! bookmark )
    return;

  if( bookmark->isProject() )
  {
    _prjMenu->exec(e->globalPos());
  }
  else if( bookmark->isRemote() )
  {
    _rpMenu->exec(e->globalPos());
  }
  else if( bookmark->isWorkingCopy() )
  {
    _wcMenu->exec(e->globalPos());
  }
}

void BookmarkView::selectionChanged( const QItemSelection& selected, const QItemSelection& deselected )
{
  if( selected.indexes().isEmpty() )
    return;

  QModelIndex index    = selected.indexes().first();
  Bookmark*   bookmark = getBookmark(index);

  _actions->enableAction( ActionCmdReload, false );
  _actions->enableAction( ActionBookmarkOptionUpdates, bookmark->isWorkingCopy() );
  _actions->enableAction( ActionBookmarkOptionRecursive, bookmark->isWorkingCopy() );
  _actions->enableAction( ActionBookmarkOptionAutoRefresh, bookmark->isWorkingCopy() );

  _actions->enableAction( ActionBookmarkWcUpdate, bookmark->isWorkingCopy() );
  _actions->enableAction( ActionBookmarkWcUpdateRev, bookmark->isWorkingCopy() );
  _actions->enableAction( ActionBookmarkWcCommit, bookmark->isWorkingCopy() );
  _actions->enableAction( ActionBookmarkLog, bookmark->isWorkingCopy() || bookmark->isRemote() );
  _actions->enableAction( ActionBookmarkBranchTag, true );
  _actions->enableAction( ActionBookmarkRpCheckout, bookmark->isRemote() );
  _actions->enableAction( ActionBookmarkRpSwitch, bookmark->isRemote() );
  _actions->enableAction( ActionBookmarkWcCurrent, bookmark->isWorkingCopy() && ! bookmark->isCurrent() );

  _actions->enableAction( ActionBookmarkEdit, true );
  _actions->enableAction( ActionBookmarkRemove,
    ! bookmark->isTrunk() && ! bookmark->isBranches() && ! bookmark->isTags() );
}

void BookmarkView::entered( const QModelIndex & index )
{
  QString text = index.data(OnItemRole).asString();
  emit onItem(text);
}

void BookmarkView::dataChanged( const QModelIndex& topLeft, const QModelIndex& bottomRight )
{
  ///< itemRenamed
  ///< @todo trigger project save:  _model->renameBookmark( bm, sc::String(text.utf8()) );
  super::dataChanged( topLeft, bottomRight );
}

void BookmarkView::dragEnterEvent( QDragEnterEvent* e )
{
  _dragTimerId = startTimer(100);

  // accept to receive dragMoveEvents
  e->setAccepted(true);
}

void BookmarkView::dragMoveEvent( QDragMoveEvent* e )
{
  e->setAccepted(false);

  _lastPosMouse = e->pos();
  _lastPosTime  = QTime::currentTime();

  if( e->mimeData()->hasFormat(ScMimeTypeBookmark) )
  {
    ID sourceId = e->mimeData()->data(ScMimeTypeBookmark).toULong();
    Bookmark* sourceBookmark = _model->getBookmark(sourceId);
    assert(sourceBookmark);

    // dropped on bookmark?
    Bookmark* targetBookmark = getBookmark(indexAt(e->pos()));
    if( ! targetBookmark )
      return;

    // dropped on itself?
    if( sourceBookmark->getId() == targetBookmark->getId() )
      return;

    // project on project or child on child in same parent?
    if( sourceBookmark->getParentId() != targetBookmark->getParentId() )
      return;

    e->acceptProposedAction();
  }
  else if( e->mimeData()->hasUrls() || e->mimeData()->hasText() )
  {
    e->acceptProposedAction();
  }
}

void BookmarkView::dragLeaveEvent( QDragLeaveEvent* e )
{
  if( _dragTimerId )
    killTimer(_dragTimerId);

  _dragTimerId = 0;
}

void BookmarkView::dropEvent( QDropEvent* e )
{
  if( _dragTimerId )
    killTimer(_dragTimerId);

  _dragTimerId = 0;

  // dropped on item, it may be NULL.
  Bookmark* targetBookmark = getBookmark(indexAt(e->pos()));

  if( e->mimeData()->hasFormat(ScMimeTypeBookmark) )
  {
    ID sourceId = e->mimeData()->data(ScMimeTypeBookmark).toULong();
    Bookmark* sourceBookmark = _model->getBookmark(sourceId);
    assert(sourceBookmark);

    _model->moveBookmark( sourceBookmark, targetBookmark );
  }
  else if( e->mimeData()->hasUrls() || e->mimeData()->hasText() )
  {
    QStringList list = decodeDropData( e->mimeData() );

    if( list.size() == 0 )
      return;

    // dropped on item..
    if( targetBookmark )
    {
      handleDropOnBookmark(targetBookmark,list);
    }
    // not dropped on item..
    else
    {
      Bookmark* project = _model->createProject();
      assert(project);

      setExpanded( _proxyModel->index(project), true );

      handleDropOnBookmark(project,list);
    }
  }
}

int calcScrollDelta( const QPoint& pos, int height, int visible )
{
  // down fast
  if( pos.y() > visible - 10 )
  {
    return 2 /*15*/;
  }
  // down slow
  else if( pos.y() > visible - 20 )
  {
    return 1 /*5*/;
  }
  // up fast
  else if( height > 0 && pos.y() < 10 )
  {
    return -2 /*-15*/;
  }
  // up slow
  else if( height > 0 && pos.y() < 20 )
  {
    return -1 /*-5*/;
  }
  
  return 0;    
}

void BookmarkView::timerEvent( QTimerEvent* e )
{
  super::timerEvent(e);

  if( e->timerId() != _dragTimerId )
    return;

    // auto expand
  QPoint pos = viewport()->mapFromGlobal(QCursor::pos());
  if( pos == _lastPosMouse && _lastPosTime.elapsed() > 1000 )
  {
    QModelIndex index = indexAt(_lastPosMouse);
    if( index.isValid() )
    {
      setExpanded( index, !isExpanded(index) );
      _lastPosTime = QTime::currentTime();
    }
  }

  // auto scroll, max + pageStep = rows
  int maximum = (verticalScrollBar()->maximum() + verticalScrollBar()->pageStep()) * sizeHintForRow(0);
  int visible = geometry().height()             - header()->geometry().height();

  //printf( "sb min %d max %d \n", verticalScrollBar()->minimum(), verticalScrollBar()->maximum() );
  //printf( "sb single %d page %d \n", verticalScrollBar()->singleStep(), verticalScrollBar()->pageStep());
  //printf( "v %d height %d value %d\n", visible, sizeHintForRow(0), verticalScrollBar()->value() );
  
  if( visible < maximum )
  {
    int delta = calcScrollDelta(_lastPosMouse,maximum,visible);
    if( delta != 0 )
    {
      verticalScrollBar()->setValue( verticalScrollBar()->value() + delta );
    }
  }
}

void BookmarkView::handleDropOnBookmark( Bookmark* bm, const QStringList& list )
{
  int item = _prjDropMenu->exec( QCursor::pos() );
  switch(item)
  {
  case DropNone:
    return;
  case DropTrunk:
    _model->setTrunkUrl( bm, list );
    break;
  case DropBranches:
    _model->setBranchesUrl( bm, list );
    break;
  case DropTags:
    _model->setTagsUrl( bm, list );
    break;
  case DropRepository:
    _model->addRepositories( bm, list );
    break;
  case DropWorkingCopy:
    _model->addWorkingCopies( bm, list );
    break;
  }
}

Bookmark* BookmarkView::getBookmark( const QModelIndex& index )
{
  return index.data(BookmarkRole).value<Bookmark*>();
}

void BookmarkView::drawRow( QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index ) const
{
  super::drawRow(painter,option,index);
}
