/* ====================================================================
 * Copyright (c) 2007-2009  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 "WcViewTree.h"
#include "WcViewModel.h"
#include "WcViewIndex.h"
#include "WcViewTreeItemModel.h"
#include "WcViewTreeProxyModel.h"
#include "WcViewItemProxyModel.h"
#include "WcViewColumnTooltips.h"
#include "ListViewColumnTooltips.h"
#include "TextStatusFilterDialog.h"
#include "WcViewStatus.h"
#include "WcSelection.h"
#include "CursorSupport.h"
#include "Actions.h"
#include "Bookmark.h"
#include "DragInfo.h"
#include "DragDropMimeTypes.h"
#include "Settings.h"
#include "ListViewHeaderHandler.h"
#include "sublib/ActionStorage.h"
#include "sublib/settings/HeaderSettings.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;


/**
 * WcViewTree item delegate.
 */
class WcViewTreeItemDelegate : public QItemDelegate
{
  typedef QItemDelegate super;

public:
  WcViewTreeItemDelegate( WcViewTree* parent, WcViewViewState* state )
    : super(parent), _view(parent), _state(state)
  {
  }

  /** 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
  {
    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:
  WcViewTree*      _view;
  WcViewViewState* _state;
};



WcViewTree::WcViewTree( 
#ifdef SC_NEWPROXY
  WcViewItemProxyModel* model,
#else // SC_NEWPROXY
  WcViewTreeProxyModel* model,
#endif // SC_NEWPROXY
  WcViewViewState* state, ActionStorage* as, QWidget* parent )
: super(parent), _proxyModel(model), _state(state), _actions(as),
  _restoringExpandedState(false)
{
  //setVerticalScrollMode(ScrollPerPixel);
  //setHorizontalScrollMode(ScrollPerPixel);
  setTextElideMode(Qt::ElideLeft);
  setAllColumnsShowFocus(true);
  setUniformRowHeights(true);
  setMouseTracking(true);
  setAnimated(false);

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

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

  setItemDelegate( new WcViewTreeItemDelegate(this,_state) );

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

  setModel( _proxyModel/*->sourceModel()*/ );

  sc::String h = _state->getViewHeader();
  if( !h.isEmpty() )
  {
    HeaderSettings hs(header());
    hs.fromString(h);
  }
  else
  {
    header()->resizeSection( 0, 300 );
    for( int col = 1; col < 12 /*ouch*/; col++ )
      header()->resizeSection( col, header()->sectionSizeHint(col) );
  }

  connect( header(), SIGNAL(sectionResized(int,int,int)), this, SLOT(headerChanged(int,int,int)) );

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

  connect( this, SIGNAL(expanded(const QModelIndex&)), this,
    SLOT(storeExpandedState(const QModelIndex&)) );
  connect( this, SIGNAL(collapsed(const QModelIndex&)), this,
    SLOT(storeCollapsedState(const QModelIndex&)) );
  connect(
    _proxyModel->sourceModel(), SIGNAL(deepStatusChanged(const sc::String&)),
    this, SLOT(deepStatusChanged(const sc::String&)) 
    );

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

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

    _menu->insertSeparator();

    action = _actions->getAction( ActionWcCommit );
    action->addTo(_menu);
    action = _actions->getAction( ActionWcLog );
    action->addTo(_menu);
    action = _actions->getAction( ActionWcLogGraph );
    action->addTo(_menu);
    action = _actions->getAction( ActionWcBlame );
    action->addTo(_menu);

    _menu->insertSeparator();

    action = _actions->getAction( ActionWcAdd );
    action->addTo(_menu);
    action = _actions->getAction( ActionWcRevert );
    action->addTo(_menu);
    action = _actions->getAction( ActionWcRemove );
    action->addTo(_menu);
    action = _actions->getAction( ActionWcUpdateRev );
    action->addTo(_menu);

    _menu->insertSeparator();

    action = _actions->getAction( ActionWcLock );
    action->addTo(_menu);
    action = _actions->getAction( ActionWcUnlock );
    action->addTo(_menu);

    _menu->insertSeparator();

    action = _actions->getAction( ActionWcProperties );
    action->addTo(_menu);
    action = _actions->getAction( ActionWcIgnore );
    action->addTo(_menu);

    _menu->insertSeparator();

    action = _actions->getAction( ActionWcEditConflict );
    action->addTo(_menu);
    action = _actions->getAction( ActionWcResolved );
    action->addTo(_menu);

    _menu->insertSeparator();

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

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

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

    action = _actions->getAction( ActionWcCleanup );
    action->addTo(_menu);
  }

  refreshType();
}

void WcViewTree::deepStatusChanged( const sc::String& path )
{
  _deepStatusPaths.push_back(path);
}

WcViewTree::~WcViewTree()
{
}

void WcViewTree::refreshType()
{
  static int indent = indentation();

  setWaitCursor();

  bool flat = _state->isFlat();

#ifndef SC_NEWPROXY
  if(flat)
  {
    setIndentation(0);
    setStyleSheet(
      "QTreeView::branch:open { border-image: none; image: none;} "
      "QTreeView::branch:closed { border-image: none; image: none;}"
    );
  }
  else
  {
    setIndentation(indent);
    setStyleSheet("");
  }

  setItemsExpandable(!flat);
  setRootIsDecorated(!flat);

  _proxyModel->setFilterFlat(flat);
  _proxyModel->setFilterTextStatus( _state->getTextStatusFilter() );
  _proxyModel->invalidate();

  if(flat)
    expandAll();
  else
    restoreState(_proxyModel->index(0,0,QModelIndex()));
#endif // SC_NEWPROXY

  restoreCursor();
}

void WcViewTree::headerChanged(int,int,int)
{
  HeaderSettings hs(header());
  _state->setViewHeader( hs.toString() );
}

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

void WcViewTree::showEvent( QShowEvent* )
{
  svn::WcStatuss statuss;
  getSelection(statuss);
  
  WcSelection sel(statuss);
  emit selectionChanged(sel);

  updateMenu(sel);
}

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

void WcViewTree::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 WcViewTree::dragEnterEvent( QDragEnterEvent* e )
{
  super::dragEnterEvent(e);

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

}

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

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

  if( e->mimeData()->hasFormat(ScMimeTypeWcFilesItem) )
    e->acceptProposedAction();
}

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

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

  if( e->mimeData()->hasFormat(ScMimeTypeWcFilesItem) )
  {
    QModelIndex dropIndex = indexAt(e->pos());
    sc::String  target = dropIndex.data(WcViewTreeItemModel::NameRole).value<sc::String>();
    bool        folder = dropIndex.data(WcViewTreeItemModel::DirRole).asBool();

    if(!folder)
      target = dropIndex.parent().data(WcViewTreeItemModel::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 WcViewTree::timerEvent( QTimerEvent* e )
{
  super::timerEvent(e);

  if( _state->isFlat() )
    return;

  super::autoExpand(e);
}

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

void WcViewTree::storeExpandedState( const QModelIndex& index )
{
  storeExpandedState( index, true );
}

void WcViewTree::storeCollapsedState( const QModelIndex& index )
{
  storeExpandedState( index, false );
}

void WcViewTree::storeExpandedState( const QModelIndex& index,
  bool state )
{
  // if type is flat the item is always open, so remember expanded or
  // collapsed only in tree mode. Also ignore signals while we restore
  // the expanded state.
  if( _state->isFlat() || _restoringExpandedState )
    return;

  WcViewIndex idx(index);
  _state->setExpanded( idx.name(), state );

  printf("stored %s %s\n", state ? "expanded" : "collapsed",
    (const char*)idx.name() );
}

void WcViewTree::updateOld( const sc::String& path, const svn::WcStatuss& statuss )
{
  _proxyModel->remove(path);
}

void WcViewTree::updateNew( const sc::String& path, const svn::WcStatuss& statuss, bool /*deep*/ )
{
  WcViewItems items;
  for( svn::WcStatuss::const_iterator it = statuss.begin(); it != statuss.end(); it++ )
  {
    items.push_back( WcViewItemPtr(new WcViewStatus(*it)) );
  }
  _proxyModel->insert(path,items);

#ifndef SC_NEWPROXY
  //if( _deepStatusPaths.size() > 0 )
  //  _proxyModel->invalidate();
#endif // SC_NEWPROXY

  for( svn::Paths::iterator it = _deepStatusPaths.begin(); it != _deepStatusPaths.end(); it++ )
  {
    QModelIndex idx = _proxyModel->index(*it);

    if(!idx.isValid())
      break;

    if( _state->isFlat() )
      expand(idx);
    else {
      if(_state->isExpanded(*it))
        expand(idx);
      else
        collapse(idx);
    }
  }
  _deepStatusPaths.clear();

#if 0
  // if it has no deep changes we do not care for expanded state and selection.
  QModelIndex pathIdx = _proxyModel->index(path);
  if( ! pathIdx.data(WcViewItemModel::DeepRole).toBool() )
    return;

  // expand parents..
  sc::String base = path;
  while( true )
  {
    QModelIndex idx = _proxyModel->index(base);

    if(!idx.isValid())
      break;

    if( _state->isFlat() )
      expand(idx);
    else {
      if(_state->isExpanded(base))
        expand(idx);
      else
        collapse(idx);
    }
    base = svn::Path::getDirName(base);
  }
#endif

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

    QModelIndex idx = _proxyModel->index(name);
    
    // on first insert, the index will be invalid
    if(!idx.isValid())
      continue;

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

    //if( _state->isFlat() || _state->isExpanded(name) )
      //expand(prxIndex);
  }
}

void WcViewTree::mouseDoubleClickEvent( QMouseEvent* e )
{
#ifdef SC_NEWPROXY
  expandAll();
  return;
#endif // SC_NEWPROXY

  QModelIndex clickIndex = indexAt(e->pos());

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

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

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

void WcViewTree::setCurrentDir( const sc::String& current )
{
  setWaitCursor();

#ifndef SC_NEWPROXY
  _proxyModel->setCurrentPath(current);
  _proxyModel->invalidate();
#endif // SC_NEWPROXY

  // set new root index.
  QModelIndex idx  = _proxyModel->index(current);
  QModelIndex pidx = idx.parent();

  setRootIndex(pidx);
  expand(idx);

  if( _state->isFlat() )
    expandAll();
  else
    restoreState(idx);  

  emit currentChanged(current);
  restoreCursor();
}

void WcViewTree::restoreState( const QModelIndex& index )
{
  setRestoringExpandedState();
  restoreStates(index);
  clrRestoringExpandedState();
}

void WcViewTree::restoreStates( 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);
      assert(idx.isValid());

      bool dir = idx.data(WcViewItemModel::DirRole).asBool();
      if(!dir)
        continue;

      sc::String name = idx.data(WcViewItemModel::NameRole).value<sc::String>();

      if( _state->isExpanded(name) )
      {
        if(!isExpanded(idx)) {
          expand(idx);
          //printf("expanded: %s\n", (const char*)name);
        }

        restoreStates(idx);
      }
      else if( !_state->isExpanded(name) )
      {
        // calling collapse once is not enough to get a correct display
        // with Qt4.4.0!
        if(isExpanded(idx)) {
          collapse(idx);
          collapse(idx);    
          //printf("collapsed: %s\n", (const char*)name);
        }
         
        restoreStates(idx);
      }
    }
  }
}
  
void WcViewTree::refresh()
{
#ifndef SC_NEWPROXY
  _proxyModel->setFilterFlat( _state->isFlat() );
  _proxyModel->setFilterAll( _state->getViewAll() );
  _proxyModel->setFilterOutOfDate( _state->getViewUpdates() );
  _proxyModel->setFilterTextStatus( _state->getTextStatusFilter() );

  _proxyModel->invalidate();
#endif // SC_NEWPROXY

  if( _state->isFlat() )
    expandAll();
  else
    restoreState(_proxyModel->index(0,0,QModelIndex()));
}

bool WcViewTree::shouldStoreExpandedState() const
{
  // if type is flat the item is always open, so remember expanded or
  // collapsed only in tree mode. Also Ignore  
  return _state->isTree() && !_restoringExpandedState;
}

void WcViewTree::setRestoringExpandedState()
{
  _restoringExpandedState = true;
}

void WcViewTree::clrRestoringExpandedState()
{
  _restoringExpandedState = false;
}

void WcViewTree::selectionChanged( const QItemSelection& selected, const QItemSelection& deselected )
{
  svn::WcStatuss statuss;
  getSelection(statuss);
  
  WcSelection sel(statuss);
  emit selectionChanged(sel);

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

  updateMenu(sel);

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

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

  _actions->enableAction( ActionCmdReload, sel.isVersionedDir() );

  _actions->enableAction( ActionWcDiffBase, sel.isDiffable() );
  _actions->enableAction( ActionWcAdd, sel.isAddable() );
  _actions->enableAction( ActionWcLog, sel.isVersioned() );
  _actions->enableAction( ActionWcRevert, sel.isRevertable() );
  _actions->enableAction( ActionWcRemove, sel.isRemoveable() );
  _actions->enableAction( ActionWcEditConflict, sel.isConflicted() );
  _actions->enableAction( ActionWcResolved, sel.isConflicted() || sel.isPropConflicted() );
  _actions->enableAction( ActionWcCommit, sel.isCommitable() );
  _actions->enableAction( ActionWcProperties, sel.isVersioned() );
  _actions->enableAction( ActionWcIgnore, sel.isUnversionedAll() );
  _actions->enableAction( ActionWcBlame, sel.isVersionedFile() );
  _actions->enableAction( ActionWcLock, sel.isVersionedFile() );
  _actions->enableAction( ActionWcUnlock, sel.isVersionedFile() );
  _actions->enableAction( ActionWcCleanup, sel.isVersionedDir() );
  _actions->enableAction( ActionWcExport, sel.isVersionedDir() );
  _actions->enableAction( ActionWcMkdir, sel.isVersionedDir() );
  _actions->enableAction( ActionWcUpdateRev, sel.isVersioned() );
  _actions->enableAction( ActionWcBranchTag, sel.isVersioned() );
  _actions->enableAction( ActionWcLogGraph, sel.isVersioned() );
}

void WcViewTree::getSelection( svn::WcStatuss& statuss )
{
  QModelIndexList indexList = selectedIndexes();

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

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

    const WcViewItem* item = index.data(WcViewTreeItemModel::WcViewItemRole).value<const WcViewItem*>();
    svn::WcStatusPtr status = static_cast<const WcViewStatus*>(item)->status();

    statuss.push_back(status);
  }
}

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

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

  foreach( QModelIndex index, list )
  {
    if( index.column() != 0 )
      continue;

    QString value = index.data(WcViewTreeItemModel::DragRole).toString();
    stream << value;
  }

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