/**
 * ==========================================================================
 * Copyright (c) 2007-2008, 2010  Martin Hauner
 *                                http://subcommander.tigris.org
 *
 * This file is part of Subcommander.
 *
 * Subcommander  is free software: you  can redistribute  it and/or modify it
 * under the terms of the GNU General Public License as published by the Free
 * Software Foundation, either version 2 of the License,  or (at your option)
 * any later version.
 *
 * Subcommander  is  distributed  in  the  hope  that  it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS  FOR A  PARTICULAR PURPOSE. See the  GNU General Public License
 * for more details.
 *
 * You should have  received a copy of the  GNU General Public License  along
 * with Subcommander. If not, see <http://www.gnu.org/licenses/>.
 * ==========================================================================
 */

// sc
#include "config.h"
#include "BookmarkViewItemModel.h"
#include "Bookmark.h"
#include "sublib/Utility.h"

// qt
#include <QtCore/QtAlgorithms>
#include <QtGui/QPixmap>
#include <QtGui/QMovie>

// boost
#include <boost/function.hpp>
#include <boost/lambda/bind.hpp>


/** # of frames in the decoration animation. QMovie::frameCount() returns 0. */
const int ActiveDecorationFrames = 6;

/** the child item list of a @a BookmarkItem. */
typedef QList<BookmarkItem*> BookmarkItemChilds;

/** iterator for BookmarkItemChilds. */
typedef QListIterator<BookmarkItem*> BookmarkItemChildsIterator;

/**
 * A helper class used by BookmarkViewItemModel to store the tree hirarchy
 * of Bookmarks.
 */
class BookmarkItem
{
public:
  BookmarkItem( Bookmark* bookmark, BookmarkItem* parent )
    : _bookmark(bookmark), _parent(parent), _frame(0)
  {
    _flags = Qt::ItemIsEnabled
      | Qt::ItemIsSelectable
      | Qt::ItemIsDragEnabled
      | Qt::ItemIsDropEnabled
      | Qt::ItemIsEditable;
  }

  ~BookmarkItem()
  {
    qDeleteAll(_childs);
  }

  void add( BookmarkItem* item )
  {
    _childs.append(item);
  }

  void remove( BookmarkItem* item )
  {
    _childs.remove(item);
  }

  BookmarkItem* child( int row )
  {
    return _childs.value(row);
  }

  int childCount()
  {
    return _childs.count();
  }

  int columnCount()
  {
    return 1;
  }

  int row()
  {
    if(!_parent)
      return 0;

    return _parent->_childs.indexOf(this);
  }

  BookmarkItem* parent()
  {
    return _parent;
  }

  QVariant data()
  {
    if( isRoot() )
      return _q("projects");

    return QString::fromUtf8(_bookmark->getName());
  }

  bool isRoot()
  {
    return _bookmark == NULL && _parent == NULL;
  }

  const BookmarkItemChilds& childs()
  {
    return _childs;
  }

  Bookmark* bookmark()
  {
    return _bookmark;
  }

  Qt::ItemFlags flags()
  {
    return _flags;
  }

  QString tooltip()
  {
    QString tip = QString::fromUtf8(_bookmark->getSource());

    if( _bookmark->isProject() )
      tip = _bookmark->getName();

    else if( _bookmark->isWorkingCopy() )
      tip += QString(" (%1)").arg(QString::fromUtf8(_bookmark->getUrl()));

    return tip;
  }

  const QPixmap& standardDecoration()
  {
    static QPixmap closed( getIconDir() + "BookmarkProjectClosed.png" );
    static QPixmap remote( getIconDir() + "BookmarkRepository.png" );
    static QPixmap working( getIconDir() + "BookmarkWorkingCopy.png" );

    if( _bookmark->isWorkingCopy() )
      return working;
    else if( _bookmark->isRemote() )
      return remote;
    else
      return closed;
  }

  const QPixmap& expandedDecoration()
  {
    static QPixmap opened( getIconDir() + "BookmarkProjectOpened.png" );

    return opened;
  }

  void setName( const QString& name )
  {
    _bookmark->setName(sc::String(name.toUtf8()));
  }

  bool operator<( const BookmarkItem& rh ) const
  {
    return _bookmark->getSortPos() < rh._bookmark->getSortPos();
  }

  
  class less
  {
  public:
      inline bool operator()(const BookmarkItem* t1, const BookmarkItem* t2) const
      {
          return (*t1 < *t2);
      }
  };

  class greater
  {
  public:
      inline bool operator()(const BookmarkItem* t1, const BookmarkItem* t2) const
      {
          return (*t2 < *t1);
      }
  };

  void sortChilds( Qt::SortOrder order )
  {
    if( order == Qt::AscendingOrder )
      qStableSort( _childs.begin(), _childs.end(), less() );
    else
      qStableSort( _childs.begin(), _childs.end(), greater() );
  }

  bool active()
  {
    return _bookmark->isRunning() || _frame != 0;
  }

  QPixmap activePixmap()
  {
    QMovie* movie = select();
    movie->jumpToFrame(_frame);
    return movie->currentPixmap();
  }

  QMovie* select()
  {
    if( _bookmark->isWorkingCopy() )
      return _wcMovie;
    else
      return _rpMovie;
  }

  bool advanceFrame()
  {
    bool advance = active();

    if( !advance )
      return false;

    _frame = ++_frame % ActiveDecorationFrames;
    return true;
  }

  static void setupDecoration()
  {
    _wcMovie = new QMovie( getIconDir() + "anim/BookmarkWorkingCopy.mng" );
    _rpMovie = new QMovie( getIconDir() + "anim/BookmarkRepository.mng"  );
    _wcMovie->setCacheMode(QMovie::CacheAll);
    _rpMovie->setCacheMode(QMovie::CacheAll);
  }

private:
  static QMovie*     _wcMovie;
  static QMovie*     _rpMovie;

private:
  BookmarkItem( const BookmarkItem& );
  
  Bookmark*          _bookmark;

  BookmarkItem*      _parent;
  BookmarkItemChilds _childs;

  Qt::ItemFlags      _flags;
  int                _frame;
};

QMovie* BookmarkItem::_wcMovie = NULL;
QMovie* BookmarkItem::_rpMovie = NULL;


BookmarkItem* getBookmarkItem( const QModelIndex& index )
{
  return static_cast<BookmarkItem*>(index.internalPointer());
}


BookmarkViewItemModel::BookmarkViewItemModel()
{
  _root = new BookmarkItem( NULL, NULL );
  BookmarkItem::setupDecoration();
}

BookmarkViewItemModel::~BookmarkViewItemModel()
{
  delete _root;
}

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

  BookmarkItem* parentItem = getParentItem(parent);
  BookmarkItem* childItem  = parentItem->child(row);

  if( childItem )
    return createIndex(row, column, childItem);
  else
    return QModelIndex();
}

QModelIndex BookmarkViewItemModel::index( Bookmark* bookmark )
{
  BookmarkItem* parent = getParentItem(bookmark);

  const BookmarkItemChilds& childs = parent->childs();
  BookmarkItemChildsIterator it(childs);

  BookmarkItem* curItem = NULL;
  int           row     = 0;

  while(it.hasNext())
  {
    curItem = it.next();

    if( curItem->bookmark()->getId() == bookmark->getId() )
      break;

    ++row;
  }

  return createIndex(row, 0, curItem);
}

QModelIndex BookmarkViewItemModel::parent( const QModelIndex& index ) const
{
  if( ! index.isValid() )
    return QModelIndex();

  BookmarkItem* childItem  = getBookmarkItem(index);
  BookmarkItem* parentItem = childItem->parent();

  if( parentItem == _root )
    return QModelIndex();

  return createIndex(parentItem->row(), 0, parentItem);
}

int BookmarkViewItemModel::columnCount( const QModelIndex& parent ) const
{
  BookmarkItem* parentItem = getParentItem(parent);
  return parentItem->columnCount();
}

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

  BookmarkItem* parentItem;

  if( parent.isValid() )
    parentItem = getBookmarkItem(parent);
  else
    parentItem = _root;

  return parentItem->childCount();
}

QVariant BookmarkViewItemModel::headerData( int section, Qt::Orientation orientation, int role ) const
{
  if( section == 0 && orientation == Qt::Horizontal && role == Qt::DisplayRole )
    return _root->data();

  return QVariant();
}

QVariant BookmarkViewItemModel::data( const QModelIndex& index, int role ) const
{
  if( ! index.isValid() || index.column() > 0 )
    return QVariant();

  BookmarkItem* item = getBookmarkItem(index);

  if( role == Qt::DisplayRole || role == Qt::EditRole )
    return item->data();

  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();

  else if( role == BookmarkRole )
    return QVariant::fromValue(item->bookmark());

  else if( role == ActiveRole )
    return item->active();

  else if( role == ActiveDecorationRole )
    return item->activePixmap();

  return QVariant();
}

bool BookmarkViewItemModel::setData( const QModelIndex& index, const QVariant& value, int role )
{
  if( ! index.isValid() )
    return false;

  BookmarkItem* item = getBookmarkItem(index);
  if( role == Qt::EditRole )
    item->setName(value.toString());

  else
    return false;

  emit dataChanged( index, index );
  return true;
}

Qt::ItemFlags BookmarkViewItemModel::flags( const QModelIndex &index ) const
{
  if( ! index.isValid() )
    return 0;

  BookmarkItem* item = getBookmarkItem(index);
  return item->flags();
}

Qt::DropActions BookmarkViewItemModel::supportedDragActions () const
{
  return Qt::MoveAction;
}

Qt::DropActions BookmarkViewItemModel::supportedDropActions () const
{
  return Qt::MoveAction;
}

void BookmarkViewItemModel::sort( int column, Qt::SortOrder sortOrder )
{
  _root->sortChilds(sortOrder);

  foreach( BookmarkItem* it, _root->childs() )
  {
    it->sortChilds(sortOrder);
  }

  emit layoutChanged();
}

void BookmarkViewItemModel::sort()
{
  sort(0,Qt::AscendingOrder);
}

void BookmarkViewItemModel::add( Bookmark* bookmark )
{
  BookmarkItem* parent = getParentItem(bookmark);
  QModelIndex   pidx   = parent->isRoot() ? QModelIndex() : index(parent->bookmark());

  beginInsertRows( pidx, parent->childCount(), parent->childCount() );
  parent->add( new BookmarkItem(bookmark,parent) );
  endInsertRows();

  sort();
  //emit layoutChanged();
}

void BookmarkViewItemModel::remove( Bookmark* bookmark )
{
  BookmarkItem* pItem = getParentItem(bookmark);
  BookmarkItem* item  = getItem(bookmark);
  QModelIndex   idx   = index(bookmark);

  beginRemoveRows( parent(idx), idx.row() , idx.row() );
  pItem->remove(item);
  endRemoveRows();
}

Bookmark* BookmarkViewItemModel::bookmark( const QModelIndex& index )
{
  if( ! index.isValid() )
    return NULL;

  BookmarkItem* item = getBookmarkItem(index);
  return item->bookmark();
}

void recurse( const QModelIndex& parent, boost::function<void (const QModelIndex&)> f )
{
  QModelIndex idx = parent;
  while( idx.isValid() )
  {
    f(idx);

    recurse( idx.child(0,0), f );

    idx = idx.sibling( idx.row()+1, 0 );
  }
}

void BookmarkViewItemModel::advanceDecoration()
{
  recurse( index( 0, 0, QModelIndex() ),
    boost::lambda::bind(&BookmarkViewItemModel::advanceFrame,this,boost::lambda::_1) );
}

void BookmarkViewItemModel::advanceFrame( const QModelIndex& idx )
{
  BookmarkItem* item = getBookmarkItem(idx);
  bool advanced = item->advanceFrame();

  if( advanced )
    emit dataChanged( idx, idx );
}

BookmarkItem* BookmarkViewItemModel::getParentItem(const QModelIndex& index) const
{
  if( index.isValid() )
    return getBookmarkItem(index);
  else
    return _root;
}

BookmarkItem* BookmarkViewItemModel::getParentItem(Bookmark* bookmark) const
{
  if( bookmark->isProject() )
    return _root;

  const BookmarkItemChilds& childs = _root->childs();
  BookmarkItemChildsIterator it(childs);

  while(it.hasNext())
  {
    BookmarkItem* curItem = it.next();

    if( curItem->bookmark()->getId() == bookmark->getParentId() )
      return curItem;
  }

  return NULL;
}

BookmarkItem* BookmarkViewItemModel::getItem(Bookmark* bookmark) const
{
  BookmarkItem* parent = getParentItem(bookmark);

  if(!parent)
    return NULL;

  const BookmarkItemChilds& childs = parent->childs();
  BookmarkItemChildsIterator it(childs);

  while(it.hasNext())
  {
    BookmarkItem* curItem = it.next();

    if( curItem->bookmark()->getId() == bookmark->getId() )
      return curItem;
  }

  return NULL;
}


 
