/* ====================================================================
 * Copyright (c) 2007-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 "RpViewTree.h"
#include "RpViewModel.h"
#include "RpViewState.h"
#include "RpViewEntry.h"
#include "RpViewTreeItemModel.h"
#include "RpViewTreeProxyModel.h"
#include "RpViewColumnTooltips.h"
#include "ListViewColumnTooltips.h"
#include "DirEntryLvi.h"
#include "RpSelection.h"
#include "Actions.h"
#include "Bookmark.h"
#include "DragInfo.h"
#include "SortSafe.h"
#include "CursorSupport.h"
#include "DragDropMimeTypes.h"
#include "Settings.h"
#include "ListViewHeaderHandler.h"
#include "sublib/ActionStorage.h"
#include "sublib/settings/LayoutSettings.h"
#include "svn/WcStatus.h"
#include "svn/Path.h"

// qt
#include <QtCore/QTimer>
#include <QtGui/QAction>
#include <QtGui/QLineEdit>
#include <QtGui/QHeaderView>
#include <QtGui/QItemDelegate>
#include <QtGui/QDragMoveEvent>
#include <Qt3Support/Q3DragObject>
#include <Qt3Support/Q3PopupMenu>


static ListViewHeaderHandler _headerHandler;


/**
 * RpViewTree item delegate.
 */
class RpViewTreeItemDelegate : public QItemDelegate
{
  typedef QItemDelegate super;

public:
  RpViewTreeItemDelegate( RpViewTree* 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 setModelData( QWidget* editor, QAbstractItemModel* model, const QModelIndex& index ) const
  {
    QLineEdit* edit = static_cast<QLineEdit*>(editor);
    
    // not changed?
    if( edit->text() == index.data() )
      return;

    _view->renameItem(edit->text());
  }

  void paint( QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex &index ) const
  {
    //if( index.parent() == _view->rootIndex() && !_view->isExpanded(index) )
      //_view->setExpanded(index,true);

    super::paint(painter,option,index);
  }

  void drawDecoration( QPainter* painter, const QStyleOptionViewItem& option, const QRect& rect, const QPixmap& pixmap ) const
  {
    super::drawDecoration(painter,option,rect,pixmap);
  }

  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:
  RpViewTree* _view;
};



RpViewTree::RpViewTree( const sc::String& root, RpViewState* state, ActionStorage* as, QWidget* parent )
: super(parent), _state(state), _actions(as), _itemData(new RpViewEntryData())
{
  setTextElideMode(Qt::ElideMiddle);
  setAllColumnsShowFocus(true);
  setUniformRowHeights(true);  // said to improve performance
  setMouseTracking(true);
  setAnimated(false);

  setDragEnabled(true);
  setAcceptDrops(true);
  setDragDropMode(DragDrop);
  setDropIndicatorShown(true);

  setEditTriggers(SelectedClicked);
  setSelectionBehavior(QAbstractItemView::SelectRows);
  setSelectionMode(QAbstractItemView::ExtendedSelection);

  setRootIsDecorated(true);
  setItemsExpandable(true);

  setItemDelegate( new RpViewTreeItemDelegate(this) );

  setSortingEnabled(true);
  header()->setSortIndicator( 0, Qt::AscendingOrder );
  header()->setResizeMode( QHeaderView::ResizeToContents );
  header()->setStretchLastSection(true);


  _itemModel  = new RpViewTreeItemModel(root,_itemData);
  _proxyModel = new RpViewTreeProxyModel();
  _proxyModel->setSourceModel(_itemModel);
  _proxyModel->setCurrentPath( _state->getPath() );
  setModel( _proxyModel );

#if 0
  new ListViewColumnTooltips(this, RpViewColumnTooltips::getTreeTooltips() );
#endif

  connect( this, SIGNAL(expanded(const QModelIndex&)), this, SLOT(storeState(const QModelIndex&)) );
  connect( this, SIGNAL(collapsed(const QModelIndex&)), this, SLOT(storeState(const QModelIndex&)) );

  // setup menu
  _menu = new Q3PopupMenu(this);
  {
    QAction* action;

    action = _actions->getAction( ActionRpLog );
    action->addTo(_menu);
    action = _actions->getAction( ActionRpLogGraph );
    action->addTo(_menu);
    action = _actions->getAction( ActionRpBlame );
    action->addTo(_menu);

    _menu->insertSeparator();

    action = _actions->getAction( ActionRpBranchTag );
    action->addTo(_menu);

    _menu->insertSeparator();

    action = _actions->getAction( ActionRpCheckout );
    action->addTo(_menu);
    action = _actions->getAction( ActionRpSwitch );
    action->addTo(_menu);
    action = _actions->getAction( ActionRpDiff );
    action->addTo(_menu);
    action = _actions->getAction( ActionRpMerge );
    action->addTo(_menu);

    _menu->insertSeparator();

    action = _actions->getAction( ActionRpMkdir );
    action->addTo(_menu);
    action = _actions->getAction( ActionRpRemove );
    action->addTo(_menu);
    action = _actions->getAction( ActionRpImport );
    action->addTo(_menu);
    action = _actions->getAction( ActionRpExport );
    action->addTo(_menu);
  }

#if 0
  setRootIsDecorated(true);
  setShowToolTips( true );
#endif
}

RpViewTree::~RpViewTree()
{
  delete _itemData;
}

void RpViewTree::contextMenuEvent( QContextMenuEvent* e )
{
  _menu->exec(e->globalPos());
}

void RpViewTree::showEvent( QShowEvent* )
{
  svn::DirEntries entries;
  getSelection(entries);
  
  RpSelection sel(entries);
  emit selectionChanged(sel);

  updateMenu(sel);
}

void RpViewTree::mousePressEvent( QMouseEvent* e )
{
  super::mousePressEvent(e);
}

void RpViewTree::mouseMoveEvent( QMouseEvent* e )
{
  super::mouseMoveEvent(e);

  if( !(e->buttons() & Qt::LeftButton) )
    return;

  // start drag?
  if( ! dragStartable() )
    return;

  QMimeData* data = mimeData( selectedIndexes() );
  if( !data )
    return;

  // move mime data item model
  QDrag* drag = new QDrag(this);
  drag->setMimeData(data);

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

void RpViewTree::dragEnterEvent( QDragEnterEvent* e )
{
  super::dragEnterEvent(e);

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

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

  //printf( "action: %s\n", e->proposedAction() == Qt::MoveAction ? "move" : "copy" );

  if( e->mimeData()->hasFormat(ScMimeTypeRpFilesItem) )
  {
    QModelIndex dropIndex = indexAt(e->pos());

    // no dir? Then dissallow drop.
    if( ! dropIndex.data(RpViewTreeItemModel::DirRole).asBool() )
      return;

    e->acceptProposedAction();
  }
}

void RpViewTree::dragLeaveEvent( QDragLeaveEvent* e )
{
  super::dragLeaveEvent(e);
}

void RpViewTree::dropEvent( QDropEvent* e )
{
  super::dropEvent(e);

  if( e->mimeData()->hasFormat(ScMimeTypeRpFilesItem) )
  {
    QModelIndex dropIndex = indexAt(e->pos());
    sc::String  target = dropIndex.data(RpViewTreeItemModel::NameRole).value<sc::String>();

    if( e->proposedAction() == Qt::CopyAction )
    {
      e->accept();
      emit copy(target);
    }
    else if( e->proposedAction() == Qt::MoveAction )
    {
      e->accept();
      emit move(target,false);
    }
  }
}

void RpViewTree::timerEvent( QTimerEvent* e )
{
  super::timerEvent(e);
  super::autoExpand(e);
}

void RpViewTree::renameItem( const QString& text )
{
  emit move( svn::Path::getBaseName(sc::String(text.toUtf8())), true );
}

void RpViewTree::storeState( const QModelIndex& index )
{
  sc::String name = index.data( RpViewTreeItemModel::NameRole ).value<sc::String>();
  _state->setExpanded( name, isExpanded(index) );
}

void RpViewTree::updateOld( const sc::String& path, const svn::DirEntries& entries )
{
  _itemModel->remove(path);
}

class LessDirEntry
{
public:
  bool operator()( RpViewItemPtr a, RpViewItemPtr b )
  {
    return a->path() < b->path();
  }
};

void RpViewTree::updateNew( const sc::String& path, const svn::DirEntries& entries )
{
  RpViewItems items;
  for( svn::DirEntries::const_iterator it = entries.begin(); it != entries.end(); it++ )
  {
    items.push_back( RpViewItemPtr(new RpViewEntry(*it)) );
  }
  std::sort( items.begin(), items.end(), LessDirEntry() );

  _itemModel->add(path,items);

  for( svn::DirEntries::const_iterator it = entries.begin(); it != entries.end(); it++ )
  {
    const sc::String& name = (*it)->getName();

    QModelIndex srcIndex = _itemModel->index(name);
    QModelIndex prxIndex = _proxyModel->mapFromSource(srcIndex);
    
    if(!prxIndex.isValid())
      continue;

    // restore selection
    if(_state->isSelected(name))
    {
      selectionModel()->select(
        prxIndex, QItemSelectionModel::Rows | QItemSelectionModel::Select );
      setCurrentIndex(prxIndex);
      
      // we would like to scroll only if the item is not visible.
      // problem is to find out if it is not visible...
      scrollTo(prxIndex);
    }

    if( _state->isExpanded(name) )
      setExpanded( prxIndex, true );
  }
}

void RpViewTree::mouseDoubleClickEvent( QMouseEvent* e )
{
  QModelIndex clickIndex = indexAt(e->pos());

  if( ! clickIndex.isValid() )
    return;
  
  bool dir = clickIndex.data(RpViewTreeItemModel::DirRole).asBool();
  if( !dir )
    return;

  // ignore clicks on ourself
  sc::String target = clickIndex.data(RpViewTreeItemModel::NameRole).value<sc::String>();
  if( target == _state->getPath() )
    return; 

  _state->addPath(target);
  setCurrentDir(target);
}

void RpViewTree::setCurrentDir( const sc::String& current )
{
  //setWaitCursor();
  setEnabled(false);

  // set new root index.
  QModelIndex modelIndex = _itemModel->index(current);
  QModelIndex proxyIndex = _proxyModel->mapFromSource(modelIndex);
  QModelIndex parentIndex = proxyIndex.parent();

  // we need to expand the root item. If not the tree view does not
  // display any child items after returning to a parent folder (!?)
  setRootIndex( parentIndex );
  setExpanded( proxyIndex, true );

  // after changing the root we need to invalidate so restoreState
  // will work with recalculated indexes. Otherwise the proxy model
  // index mapping is invalid and we restore the wrong items.
  // todo: invaldate triggers a second list after a double click.
  _proxyModel->setCurrentPath(current);
  _proxyModel->invalidate();

  proxyIndex = _proxyModel->mapFromSource(modelIndex);
  restoreState(proxyIndex);  

  emit currentChanged(current);
  setEnabled(true);
  //restoreCursor();
}

void RpViewTree::restoreState( const QModelIndex& index )
{
  if(_proxyModel->hasChildren(index))
  {
    int numRows = _proxyModel->rowCount(index);
    
    for( int row = 0; row < numRows; ++row )
    {
      QModelIndex idx = index.child(row,0);

      sc::String name = idx.data(RpViewTreeItemModel::NameRole).value<sc::String>();
      bool dir = idx.data(RpViewTreeItemModel::DirRole).asBool();

      if( dir && _state->isExpanded(name) )
      {
        if(!isExpanded(idx))
        {
          setExpanded(idx,true);
          restoreState(idx);
        }
      }
      else if( dir && !_state->isExpanded(name) )
      {
        if(isExpanded(idx))
          setExpanded(idx,false);        
      }
    }
  }
}

void RpViewTree::refresh()
{
  _proxyModel->invalidate();
}

void RpViewTree::selectionChanged( const QItemSelection& selected, const QItemSelection& deselected )
{
  svn::DirEntries entries;
  getSelection(entries);
  
  RpSelection sel(entries);
  emit selectionChanged(sel);

  _state->clearSelected();
  for( svn::DirEntries::iterator it = entries.begin(); it != entries.end(); it++ )
  {
    const sc::String& name = (*it)->getName();
    _state->setSelected(name,true);
  }

  updateMenu(sel);

  // properly refresh selection changes..
  super::selectionChanged(selected,deselected);
}

void RpViewTree::updateMenu( const RpSelection& sel )
{
  // enable/disable actions based on current selection

  _actions->enableAction( ActionCmdReload, sel.isSingle() && sel.isDir() );

  _actions->enableAction( ActionRpLog, sel.isSingle() );
  _actions->enableAction( ActionRpLogGraph, sel.isSingle() );
  _actions->enableAction( ActionRpBlame, sel.isSingle() && sel.isFile() );
  _actions->enableAction( ActionRpBranchTag, sel.isSingle() && sel.isDir() );
  _actions->enableAction( ActionRpCheckout, sel.isSingle() && sel.isDir() );
  _actions->enableAction( ActionRpSwitch, sel.isSingle() && sel.isDir() );
  _actions->enableAction( ActionRpDiff, sel.size() > 0 && sel.size() < 3 );
  _actions->enableAction( ActionRpMerge, sel.size() > 0 && sel.size() < 3 );
  _actions->enableAction( ActionRpMkdir, sel.isSingle() && sel.isDir() );
  _actions->enableAction( ActionRpRemove, !sel.isEmpty() );
}

void RpViewTree::getSelection( svn::DirEntries& entries )
{
  QModelIndexList indexList = selectedIndexes();

  for( QModelIndexList::Iterator it = indexList.begin(); it != indexList.end(); it++ )
  {
    QModelIndex index = _proxyModel->mapToSource(*it);

    if( index.column() != 0 )
      continue;

    const RpViewItem* item = index.data(RpViewTreeItemModel::RpViewItemRole).value<const RpViewItem*>();
    svn::DirEntryPtr entry = static_cast<const RpViewEntry*>(item)->entry();

    entries.push_back(entry);
  }
}

QMimeData* RpViewTree::mimeData( const QModelIndexList& list )
{
  QMimeData *mimeData = new QMimeData();
  QByteArray encodedData;

  QDataStream stream(&encodedData, QIODevice::WriteOnly);

  foreach( QModelIndex index, list )
  {
    QModelIndex modelIndex = _proxyModel->mapToSource(index);
    if( modelIndex.isValid() )
    {
      QString value = _itemModel->data(modelIndex, RpViewTreeItemModel::DragRole).toString();
      stream << value;
    }
  }

  mimeData->setData( ScMimeTypeRpFilesItem, encodedData );
  return mimeData;
}
