/* ====================================================================
 * 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 "RpViewTreeItemModel.h"
#include "DragDropMimeTypes.h"
#include "ColorId.h"
#include "sublib/QSupportAlgorithms.h"
#include "sublib/Utility.h"
#include "RpViewEntry.h"
#include "svn/DirEntry.h"
#include "svn/Path.h"

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


class RpViewTreeItem;

/** the child item list of a @a RpViewTreeItem. */
typedef QList<RpViewItemPtr> RpViewTreeItems;

/** RpViewTreeItems iterator. */
typedef QListIterator<RpViewItemPtr> RpViewTreeItemsIterator;


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

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

public:
  RpViewTreeItemFolder( const sc::String& path )
    : _path(path)
  {
  }

  /** construct entry for @a path, its children @a items. */
  RpViewTreeItemFolder( const sc::String& path, const RpViewItems& items )
    : _path(path)
  {
    for( RpViewItems::const_iterator it = items.begin(); it != items.end(); it++ )
    {
      _childs.append(*it);
    }
  }

  ~RpViewTreeItemFolder()
  {
  }

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

  /** Get the child item at row @a row. */
  RpViewItemPtr 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
  {
    RpViewTreeItems::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( RpViewItemPtr 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, RpViewItemPtr item )
  {
    _childs.insert( row, item );
  }

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

private:
  sc::String      _path;

  RpViewTreeItems _childs;
};



/** The main index. */
typedef QMap< sc::String, RpViewTreeItemFolder* > RpViewTreeItemIndex;


/** RpViewTreeItemModel member class. */
class RpViewTreeItemModelMember
{
public:
  /** Construct a WcViewTreeItemModelMember with the provided @a root path. */
  RpViewTreeItemModelMember( 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 RpViewTreeItemFolder for @a path. Returns NULL if @ path is unknown. */
  RpViewTreeItemFolder* getItemFolder( const sc::String& path )
  {
    if( path == _invisible.path() )
      return &_invisible;

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

    return *it;
  }

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

    return getItemFolder(parent(path));
  }

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

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

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

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

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

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

  RpViewTreeItemFolder* invisible()
  {
    return &_invisible;
  }

  RpViewTreeItemIndex& index()
  {
    return _index;
  }

private:
  RpViewTreeItemFolder _invisible;
  RpViewTreeItemIndex  _index;
};


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


RpViewTreeItemModel::RpViewTreeItemModel( const sc::String& root,
  const RpViewItemData* data ) : _data(data)
{
  _m = new RpViewTreeItemModelMember(root);
  _m->invisible()->insert( 0,
    RpViewItemPtr(new RpViewEntry(svn::DirEntryPtr(new svn::DirEntry(root))))
    );
}
  
RpViewTreeItemModel::~RpViewTreeItemModel()
{
  delete _m;
}

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

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

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

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

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

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

int RpViewTreeItemModel::rowCount( const QModelIndex& parent ) const
{
  if( parent.isValid() && parent.column() > 0 )
    return 0;

  return _m->rowCount(parent);
}

QVariant RpViewTreeItemModel::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 RpViewTreeItemModel::data( const QModelIndex& index, int role ) const
{
  if( ! index.isValid() )
    return QVariant();

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

  if( role == Qt::DisplayRole || role == Qt::EditRole )
    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 == RpViewItemRole )
    return QVariant::fromValue(item);

  else if( role == NameRole )
    return QVariant::fromValue(item->path());

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

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

  return QVariant();
}

bool RpViewTreeItemModel::canFetchMore( const QModelIndex& parent ) const
{
  RpViewTreeItemFolder* folder = _m->getItemFolder(parent);
  if( folder == NULL )
    return true;
  
  if( folder && folder->childCount() >= 0 )
    return false;

  return hasChildren(parent);
}

void RpViewTreeItemModel::fetchMore( const QModelIndex& parent )
{
  if( !parent.isValid() )
    return;

  if( !canFetchMore(parent) )
    return;

  sc::String path = parent.data(NameRole).value<sc::String>();
  emit fetch(path);
}

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

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

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

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

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

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

  return flags;
}

bool RpViewTreeItemModel::hasChildren( const QModelIndex& parent ) const
{
  if( !parent.isValid() )
    return true;

  if( parent.column() != 0 )
    return false;

  return parent.data(RpViewTreeItemModel::DirRole).asBool();
}

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

  RpViewTreeItemFolder* parentFolder = _m->getParentFolder(path);
  assert(parentFolder);

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

  RpViewItemPtr child = parentFolder->child(row);

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

void RpViewTreeItemModel::add( const sc::String& path, const RpViewItems& items )
{
  // no childs?
  bool hasChilds = items.size() > 0;

  if( hasChilds )
    beginInsertRows( index(path), 0, items.size()-1 );

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

  if( hasChilds )
    endInsertRows();
}

void RpViewTreeItemModel::remove( const sc::String& path )
{
  RpViewTreeItemFolder* folder = _m->getItemFolder(path);

  // it is ok if we don't find a row (initial load)
  if(!folder)
    return;
  
  if(folder->childCount() == 0 )
    return;

  beginRemoveRows( index(path), 0, folder->childCount()-1 );
  
  remove(folder);

  endRemoveRows();
}

void RpViewTreeItemModel::remove( RpViewTreeItemFolder* folder )
{
  if(!folder)
    return;

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