/* ====================================================================
 * 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 "WcViewTreeItemModel.h"
#include "DragDropMimeTypes.h"
#include "ColorId.h"
#include "sublib/QSupportAlgorithms.h"
#include "sublib/Utility.h"
#include "svn/WcStatus.h"
#include "svn/WcEntry.h"
#include "svn/Path.h"

// qt
#include <QtCore/QtAlgorithms>
#include <QtCore/QMimeData>
#include <QtCore/QList>
#include <QtGui/QPixmap>


class WcViewTreeItem;

/** the child item list of a @a WcViewTreeItem. */
typedef QList<WcViewItemPtr> WcViewTreeItems;

/** WcViewTreeItems iterator. */
typedef QListIterator<WcViewItemPtr> WcViewTreeItemsIterator;


/**
 * Entry in @a WcViewTreeItemIndex. It represents a @a WcViewItem 
 * path and its direct children. */
class WcViewTreeItemFolder
{
  /** compare class. */
  class equal
  {
  public:
    bool operator()( WcViewItemPtr item, const sc::String& path )
    {
      return item->path() < path;
    }

    bool operator()( const sc::String& path, WcViewItemPtr item )
    {
      return path < item->path();
    }
  };

public:
  WcViewTreeItemFolder( const sc::String& path )
    : _path(path), _deep(false)
  {
  }

  /** construct entry for @a path, its children @a items with deep state @a deep. */
  WcViewTreeItemFolder( const sc::String& path, const WcViewItems& items, bool deepStatus )
    : _path(path), _deep(false/*deepStatus*/)
  {
    if( items.size() <= 1 )
      return;

    // skip first element: "self" 
    for( WcViewItems::const_iterator it = ++items.begin(); it != items.end(); it++ )
    {
      _childs.append(*it);
      _deep |= (*it)->isChanged();
    }
  }

  ~WcViewTreeItemFolder()
  {
  }

  /** Get this items path. */
  const sc::String& path() const
  {
    return _path;
  }

  /** Get the deep status. */
  bool deepStatus() const
  {
    return _deep;
  }

  /* Set the deep status to @a deep. */
  void setDeepStatus( bool deep )
  {
    _deep = deep;
  }

  /** Get the child item at row @a row. */
  WcViewItemPtr child( int row ) const
  {
    return _childs[row];
  }

  /** Get the number of child items. */
  int childCount() const
  {
    return _childs.size();
  }

  /** Get row index for @a child. Returns -1 if @a child is not a child. */
  int row( const sc::String& child ) const
  {
    WcViewTreeItems::const_iterator it = sc::binaryFind( _childs.begin(), _childs.end(), child, equal() );

    if( it == _childs.end() )
      return -1;

    return it - _childs.begin();
  }

  /** Get row index for @a child. Returns -1 if @a child is not a child. */ 
  int row( WcViewItemPtr child ) const
  {
    return _childs.indexOf(child);
  }

  /** Returns the row at which @a child should be added. */
  int insertRow( const sc::String& child )
  {
    return sc::lowerBound( _childs.begin(), _childs.end(), child, equal() ) - _childs.begin();
  }

  /** Insert @a item at @a row. */
  void insert( int row, WcViewItemPtr item )
  {
    _childs.insert( row, item );
  }

  /** Remove @a row. */
  void remove( int row )
  {
    _childs.removeAt(row);
  }

private:
  sc::String      _path;
  bool            _deep;

  WcViewTreeItems _childs;
};



/** The main index. */
typedef QMap< sc::String, WcViewTreeItemFolder* > WcViewTreeItemIndex;


/** WcViewTreeItemModel member class. */
class WcViewTreeItemModelMember
{
public:
  /** Construct a WcViewTreeItemModelMember with the provided @a root path. */
  WcViewTreeItemModelMember( const sc::String& root )
    : _invisible(parent(root))
  {
  }

  /** Get parent path. */
  sc::String parent( const sc::String& path ) const
  {
    return svn::Path::getDirName(path);
  }

  /** get the WcViewTreeItemFolder for @a path. Returns NULL if @ path is unknown. */
  WcViewTreeItemFolder* getItemFolder( const sc::String& path )
  {
    if( path == _invisible.path() )
      return &_invisible;

    WcViewTreeItemIndex::iterator it = _index.find(path);
    if( it == _index.end() )
      return NULL;

    return *it;
  }

  /** get the parent WcViewTreeItemFolder for @a path. Returns NULL if the
    * parent of @a path is unknown. */
  WcViewTreeItemFolder* getParentFolder( const sc::String& path )
  {
//    if( path == _invisible.path() )
//      return &_invisible;

    return getItemFolder(parent(path));
  }

  /** get folder item of @a index. */
  WcViewTreeItemFolder* getItemFolder( const QModelIndex& index )
  {
    if( !index.isValid() )
      return &_invisible;

    const WcViewItem* item = getItem(index);
    assert(item);

    return getItemFolder(item->path());
  }

  /** return number of childs of @a index. */
  int rowCount( const QModelIndex& index )
  {
    WcViewTreeItemFolder* folder = getItemFolder(index);

    if( folder )
      return folder->childCount();
    else
      return 0;
  }

  /** Extract a WcViewTreeItem* from @a index. */
  const WcViewItem* getItem( const QModelIndex& index )
  {
    return static_cast<WcViewItem*>(index.internalPointer());
  }

  WcViewTreeItemFolder* invisible()
  {
    return &_invisible;
  }

  WcViewTreeItemIndex& index()
  {
    return _index;
  }

private:
  WcViewTreeItemFolder _invisible;
  WcViewTreeItemIndex  _index;
};


///////////////////////////////////////////////////////////////////////////////


WcViewTreeItemModel::WcViewTreeItemModel( const sc::String& root,
  const WcViewItemData* data ) : _data(data)
{
  _m = new WcViewTreeItemModelMember(root);
}
  
WcViewTreeItemModel::~WcViewTreeItemModel()
{
  delete _m;
}

QModelIndex WcViewTreeItemModel::index( int row, int column, const QModelIndex& parent ) const
{
  if( ! hasIndex(row,column,parent) )
    return QModelIndex();

  WcViewTreeItemFolder* folder = _m->getItemFolder(parent);
  
  return createIndex( row, column, folder->child(row).get() );
}

QModelIndex WcViewTreeItemModel::parent( const QModelIndex& idx ) const
{
  // index is invisible root?
  if( !idx.isValid() )
    return QModelIndex();

  const WcViewItem* item = _m->getItem(idx);

  return index(_m->parent(item->path()));
}

int WcViewTreeItemModel::columnCount( const QModelIndex& parent ) const
{
  return _data->columns();
}

int WcViewTreeItemModel::rowCount( const QModelIndex& parent ) const
{
  if( parent.isValid() && parent.column() != 0 )
    return 0;

  return _m->rowCount(parent);
}

QVariant WcViewTreeItemModel::headerData( int section, Qt::Orientation orientation, int role ) const
{
  if( orientation == Qt::Horizontal && role == Qt::DisplayRole )
    return _data->header(section);
  
  else if( role == Qt::TextAlignmentRole )
    return (uint)_data->alignment(section);

  return QVariant();
}

QVariant WcViewTreeItemModel::data( const QModelIndex& index, int role ) const
{
  if( ! index.isValid() )
    return QVariant();

  const WcViewItem* item = _m->getItem(index);

  if( role == Qt::DisplayRole || role == Qt::EditRole )
  {
    if( item->isDir() && _data->deep(index.column()) )
    {
      WcViewTreeItemFolder* folder = _m->getItemFolder(item->path());

      if( folder && folder->deepStatus() )
        return "*";
      else
        return "";
    }
    return _data->data(index.column(),item);
  }
  else if( role == Qt::BackgroundRole )
    return _data->color(index.column(),item);

  else if( role == Qt::TextAlignmentRole )
    return (uint)_data->alignment(index.column());

  else if( role == WcViewItemRole )
    return QVariant::fromValue(item);

  else if( role == DeepRole )
  {
    if( ! item->isDir() )
      return QVariant(false);
    
    WcViewTreeItemFolder* folder = _m->getItemFolder(item->path());
    if(folder)
      return QVariant(folder->deepStatus());
    else
      return QVariant(false);
  }
  else if( role == NameRole )
    return QVariant::fromValue(item->path());

  else if( role == DragRole )
    return _data->data(0,item);

  else if( role == DirRole )
    return QVariant::fromValue(item->isDir());

  else if( role == ChangedRole )
    return QVariant::fromValue(item->isChanged());

  else if( role == TextStatusRole )
    return QVariant::fromValue(item->getTextStatus());

  else if( role == IgnoredRole )
    return QVariant(item->isIgnored());

  else if( role == SwitchedRole )
    return QVariant(item->isSwitched());

  else if( role == OutOfDateRole )
    return QVariant(item->isOutOfDate());

  //else if( role == SortRole )
  //  return _data->dataSort(0,item);

  return QVariant();

#if 0
  else if( role == Qt::DecorationRole )
    return item->standardDecoration();

  else if( role == ExpandedDecorationRole )
    return item->expandedDecoration();

  else if( role == Qt::ToolTipRole )
    return item->tooltip();

  else if( role == OnItemRole )
    return item->tooltip();

  else if( role == CurrentWcRole )
    return item->bookmark()->isCurrent();
#endif
}

Qt::ItemFlags WcViewTreeItemModel::flags( const QModelIndex &index ) const
{
  Qt::ItemFlags flags;

  if( ! index.isValid() )
    return flags;

  flags = Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsDragEnabled;

  const WcViewItem* item = _m->getItem(index);

  if( index.column() == 0 /*WcStatusDisplayData::ColName*/ )
  {
    flags |= Qt::ItemIsEditable;
  }

  if( item->isDir() )
  {
    flags |= Qt::ItemIsDropEnabled;
  }

  return flags;
}

QModelIndex WcViewTreeItemModel::index( const sc::String& path ) const
{
  if( path == _m->invisible()->path() )
    return QModelIndex();

  WcViewTreeItemFolder* parentFolder = _m->getParentFolder(path);
  if(!parentFolder)
  {
    assert(parentFolder);
    return QModelIndex();
  }

  int row = parentFolder->row(path);
  if( row == -1 )
    return QModelIndex();

  WcViewItemPtr child = parentFolder->child(row);

  return createIndex( row, 0, child.get() );
}

void WcViewTreeItemModel::updateDeepStatus( const sc::String& path )
{
  WcViewTreeItemFolder* pathFolder = _m->getItemFolder(path);
  if(!pathFolder)
    return;

  bool deepStatus = false;
  for( int i = 0; i < pathFolder->childCount(); i++ )
  {
    WcViewItemPtr item = pathFolder->child(i);
    
    deepStatus |= item->isChanged();

    if( deepStatus )
        break;

    if( item->isDir() )
    {
      WcViewTreeItemFolder* me = _m->getItemFolder(item->path());
      if(!me)
        continue;

      deepStatus |= me->deepStatus();
    }
  }

  // no change...?
  if( pathFolder->deepStatus() == deepStatus )
    return;
  
  pathFolder->setDeepStatus(deepStatus);

  // notify deep status change
  QModelIndex idx = index(path);
  dataChanged(idx,idx);

  // recurse up...
  WcViewTreeItemFolder* parent = _m->getParentFolder(path);
  if( !parent )
    return;

  updateDeepStatus(parent->path());

  emit deepStatusChanged(path);
}

void WcViewTreeItemModel::insert( const sc::String& path, const WcViewItems& items )
{
  if( items.size() < 1 )
    return;

  WcViewTreeItemFolder* parent = _m->getParentFolder(path);
  if( !parent )
  {
    assert(parent);
    return;
  }

//  emit layoutAboutToBeChanged();

  QModelIndex pidx = index(parent->path());
  int row = parent->insertRow(path);

  beginInsertRows( pidx, row, row );

  parent->insert( row, *(items.begin()) );

  WcViewTreeItemFolder* item = new WcViewTreeItemFolder(path,items,false);
  _m->index().insert( path, item );

  endInsertRows();

  updateDeepStatus(parent->path());

  // children?
  if( items.size() >= 2 )
  {
    // 0 based and skip first (ie. the "." folder)
    beginInsertRows( index(path), 0, items.size()-2 );
    endInsertRows();
    
    if( item->deepStatus() )
      emit deepStatusChanged(path);
  }

  QModelIndex idx = index(path);
  dataChanged(idx,idx);
  //emit layoutChanged();
}

void WcViewTreeItemModel::remove( const sc::String& path )
{
  WcViewTreeItemFolder* parent = _m->getParentFolder(path);
  if(!parent)
    return;

  // it is ok if we don't find a row (initial load)
  int row = parent->row(path);
  if( row == -1 )
    return;

  beginRemoveRows( index(path).parent(), row, row );

  // it is ok if we don't find a folder (intial load)
  WcViewTreeItemFolder* folder = _m->getItemFolder(path);
  if(folder)       
    remove(folder);

  parent->remove(row);

  endRemoveRows();
}

void WcViewTreeItemModel::remove( WcViewTreeItemFolder* folder )
{
  if(!folder)
    return;

  for( int i = 0; i < folder->childCount(); i++ )
  {
    WcViewItemPtr item = folder->child(i);
    
    if( item->isDir() )
    {
      WcViewTreeItemFolder* me = _m->getItemFolder(item->path());
      remove(me);
    }
  }
  delete _m->index().take(folder->path());
}
