/* ====================================================================
 * Copyright (c) 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 "WcViewItemProxyModel.h"
//#include "WcViewItemModel.h"
//#include "TextStatusFilter.h"
//#include "util/String.h"

// qt
#include <QtCore/QList>
#include <QtCore/QSet>
//#include <QtGui/QModelIndex>
//#include <QtGui/QAbstractProxyModel>


#include <boost/multi_index_container.hpp>
#include <boost/multi_index/member.hpp>
#include <boost/multi_index/ordered_index.hpp>
#include <utility>

using boost::multi_index_container;
using namespace boost::multi_index;

typedef std::pair<QModelIndex,QModelIndex> value_type;

struct proxy{};
struct source{};

typedef multi_index_container<
  value_type,
    indexed_by<
      ordered_unique<
        tag<proxy>, member<value_type,QModelIndex,&value_type::first> >,
      ordered_unique<
        tag<source>, member<value_type,QModelIndex,&value_type::second> >
    >
  > TIndexMap;


class IndexData;
typedef QList<IndexData*> Childs;

class IndexData
{
public:
  IndexData( const QModelIndex& parent )
    : _parent(parent)
  {
  }

  ~IndexData()
  {
    while (!_childs.isEmpty())
      delete _childs.takeFirst();
  }

  IndexData* getChildData( int row )
  {
    if( _childs.size() < row+1 )
      return NULL;

    return _childs.at(row);
  }

  const QModelIndex& getParent() const
  {
    return _parent;
  }

  int getChildCount() const
  {
    return _childs.count();
  }

  void addChild( int row, IndexData* data )
  {
    _childs.insert( row, data );
  }

  void delChild( int row )
  {
    delete _childs.takeAt(row);
  }

private:
  QModelIndex _parent;
  Childs      _childs;
};



class Index
{
private:
  TIndexMap  _idx;
  IndexData  _root;

public:
  Index() : _root(QModelIndex())
  {
  }

  ~Index()
  {
  }

  IndexData* getData( const QModelIndex& idx )
  {
    if( ! idx.isValid() )
      return &_root;

    return static_cast<IndexData*>(idx.internalPointer());
  }

  struct CompareIndex
  {
    bool operator()( const QModelIndex& lh, const QModelIndex& rh ) const
    {
      if( lh.row() < rh.row() )
        return true;

      if( lh.row() == rh.row() )
      {
        if( lh.internalPointer() < rh.internalPointer() )
          return true;

        if( lh.internalPointer() == rh.internalPointer() )
        {
          return lh.model() < rh.model();
        }
      }
      return false;
    }
  };

  QModelIndex mapFromSource( const QModelIndex& index )
  {
    TIndexMap::index_iterator<source>::type it = _idx.get<source>()
      .find( index, CompareIndex() );

    if( it == _idx.get<source>().end() )
      return QModelIndex();

    return (*it).first;
  }

  QModelIndex mapToSource( const QModelIndex& index )
  {
    TIndexMap::index_iterator<proxy>::type it = _idx.get<proxy>()
      .find( index, CompareIndex() );
    
    if( it == _idx.get<proxy>().end() )
      return QModelIndex();

    return (*it).second;
  }

  void mapProxyToSource( const QModelIndex& proxy, const QModelIndex&
    source )
  {
    _idx.insert(TIndexMap::value_type(proxy,source));
  }

  void unmapProxyToSource( const QModelIndex& proxy )
  {
    _idx.erase(proxy);
  }
};



WcViewItemProxyModel::WcViewItemProxyModel()
{
  _index = new Index();
}

WcViewItemProxyModel::~WcViewItemProxyModel()
{
  delete _index;
}

QModelIndex WcViewItemProxyModel::index( int row, int column, const 
  QModelIndex& parent ) const
{
  IndexData* data = _index->getData(parent);  
  IndexData* child = data->getChildData(row);

  if(!child)
    return QModelIndex();

  return createIndex( row, column, child );
}

QModelIndex WcViewItemProxyModel::parent( const QModelIndex& index )
  const
{
  if( ! index.isValid() )
    assert(false);

  IndexData* data = _index->getData(index);
  return data->getParent();
}

int WcViewItemProxyModel::columnCount( const QModelIndex& parent )
  const
{
  return sourceModel()->columnCount(mapToSource(parent));
}

int WcViewItemProxyModel::rowCount( const QModelIndex& parent ) const
{
  IndexData* data = _index->getData(parent);
  return data->getChildCount();
}

void WcViewItemProxyModel::setSourceModel( QAbstractItemModel*
  sourceModel )
{
    disconnectModel();
    QAbstractProxyModel::setSourceModel(sourceModel);
    connectModel();
    reset();
}

QModelIndex WcViewItemProxyModel::mapFromSource( const QModelIndex&
  sourceIndex ) const
{
  QModelIndex prxIndex = _index->mapFromSource(sourceIndex);
  if( !prxIndex.isValid() )
    return prxIndex;

  QModelIndex result = prxIndex.sibling( prxIndex.row(), sourceIndex.column() );
  return result;
}

QModelIndex WcViewItemProxyModel::mapToSource( const QModelIndex&
  proxyIndex ) const
{
  QModelIndex srcIndex = _index->mapToSource(proxyIndex);
  if( !srcIndex.isValid() )
    return QModelIndex();

  QModelIndex result = srcIndex.sibling( srcIndex.row(), proxyIndex.column() );
  return result;
}

QModelIndex WcViewItemProxyModel::index( const sc::String& name ) const
{
  return mapFromSource(sourceViewItemModel()->index(name));
}

void WcViewItemProxyModel::insert( const sc::String& path, const
  WcViewItems& items )
{
  sourceViewItemModel()->insert( path, items );
}

void WcViewItemProxyModel::remove( const sc::String& path )
{
  sourceViewItemModel()->remove( path );
}

void WcViewItemProxyModel::srcColumnsAboutToBeInserted(
  const QModelIndex& srcParent, int start, int end )
{
  assert(false);
  //beginInsertColumns( srcParent, start, end );
}

void WcViewItemProxyModel::srcColumnsAboutToBeRemoved(
  const QModelIndex& srcParent, int start, int end )
{
  assert(false);
  //beginRemoveColumns( srcParent, start, end );
}

void WcViewItemProxyModel::srcColumnsInserted(
  const QModelIndex& srcParent, int start, int end )
{
  assert(false);
  //endInsertColumns();
}

void WcViewItemProxyModel::srcColumnsRemoved(
  const QModelIndex& srcParent, int start, int end )
{
  assert(false);
  //endRemoveColumns();
}

void WcViewItemProxyModel::srcDataChanged(
  const QModelIndex& srcTopLeft, const QModelIndex& srcBottomRight )
{
  emit dataChanged( mapFromSource(srcTopLeft), mapFromSource(
    srcBottomRight) );
}

void WcViewItemProxyModel::srcHeaderDataChanged(
  Qt::Orientation orientation, int start, int end )
{
  emit headerDataChanged( orientation, start, end );
}

void WcViewItemProxyModel::srcLayoutAboutToBeChanged()
{
  emit layoutAboutToBeChanged();
}

void WcViewItemProxyModel::srcLayoutChanged()
{
  emit layoutChanged();
}

void WcViewItemProxyModel::srcModelReset()
{
  reset();
}

void WcViewItemProxyModel::srcRowsAboutToBeInserted(
  const QModelIndex& srcParent, int start, int end )
{
  beginInsertRows( mapFromSource(srcParent), start, end );
}

void WcViewItemProxyModel::srcRowsAboutToBeRemoved(
  const QModelIndex& srcParent, int start, int end )
{
  beginRemoveRows( mapFromSource(srcParent), start, end );
}

void WcViewItemProxyModel::srcRowsInserted(
  const QModelIndex& srcParent, int start, int end )
{
  QModelIndex parentIndex = _index->mapFromSource(srcParent);
  IndexData* parentData = _index->getData(parentIndex);

  for( int row = start; row <= end; row++ )
  {
    parentData->addChild( row, new IndexData(parentIndex) );
  }

  for( int row = start; row <= end; row++ )
  {
    _index->mapProxyToSource( index(row,0,parentIndex), 
      sourceModel()->index(row,0,srcParent) );
  }

  endInsertRows();
}

void WcViewItemProxyModel::srcRowsRemoved(
  const QModelIndex& srcParent, int start, int end )
{
  QModelIndex parentIndex = _index->mapFromSource(srcParent);
  IndexData* parentData = _index->getData(parentIndex);

  for( int row = start; row <= end; row++ )
  {
    _index->unmapProxyToSource( index(row,0,parentIndex) );
  }

  for( int row = start; row <= end; row++ )
  {
    parentData->delChild( row );
  }

  endRemoveRows();
}

void WcViewItemProxyModel::disconnectModel()
{
  if( !sourceModel() )
    return;

  disconnect(
    sourceModel(), SIGNAL(dataChanged(QModelIndex,QModelIndex)),
    this,          SLOT(srcDataChanged(QModelIndex,QModelIndex)));
  disconnect(
    sourceModel(), SIGNAL(headerDataChanged(Qt::Orientation,int,int)),
    this,          SLOT(srcHeaderDataChanged(Qt::Orientation,int,int)));
  disconnect(
    sourceModel(), SIGNAL(rowsAboutToBeInserted(QModelIndex,int,int)),
    this,          SLOT(srcRowsAboutToBeInserted(QModelIndex,int,int)));
  disconnect(
    sourceModel(), SIGNAL(rowsInserted(QModelIndex,int,int)),
    this,          SLOT(srcRowsInserted(QModelIndex,int,int)));
#if 0
  disconnect(
    sourceModel(), SIGNAL(columnsAboutToBeInserted(QModelIndex,int,int))
    ,this,         SLOT(srcColumnsAboutToBeInserted(QModelIndex,int,int)
    ));
  disconnect(
    sourceModel(), SIGNAL(columnsInserted(QModelIndex,int,int)),
    this,          SLOT(srcColumnsInserted(QModelIndex,int,int)));
#endif
  disconnect(
    sourceModel(), SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)),
    this,          SLOT(srcRowsAboutToBeRemoved(QModelIndex,int,int)));
  disconnect(
    sourceModel(), SIGNAL(rowsRemoved(QModelIndex,int,int)),
    this,          SLOT(srcRowsRemoved(QModelIndex,int,int)));
#if 0
  disconnect(
    sourceModel(), SIGNAL(columnsAboutToBeRemoved(QModelIndex,int,int)),
    this,          SLOT(srcColumnsAboutToBeRemoved(QModelIndex,int,int)
    ));
  disconnect(
    sourceModel(), SIGNAL(columnsRemoved(QModelIndex,int,int)),
    this,          SLOT(srcColumnsRemoved(QModelIndex,int,int)));
#endif
  disconnect(
    sourceModel(), SIGNAL(layoutAboutToBeChanged()),
    this,          SLOT(srcLayoutAboutToBeChanged()));
  disconnect(
    sourceModel(), SIGNAL(layoutChanged()),
    this,          SLOT(srcLayoutChanged()));
  disconnect(
    sourceModel(), SIGNAL(modelReset()),
    this,          SLOT(srcModelReset()));
}

void WcViewItemProxyModel::connectModel()
{
  connect(
    sourceModel(), SIGNAL(dataChanged(QModelIndex,QModelIndex)),
    this,          SLOT(srcDataChanged(QModelIndex,QModelIndex)));
  connect(
    sourceModel(), SIGNAL(headerDataChanged(Qt::Orientation,int,int)),
    this,          SLOT(srcHeaderDataChanged(Qt::Orientation,int,int)));
  connect(
    sourceModel(), SIGNAL(rowsAboutToBeInserted(QModelIndex,int,int)),
    this,          SLOT(srcRowsAboutToBeInserted(QModelIndex,int,int)));
  connect(
    sourceModel(), SIGNAL(rowsInserted(QModelIndex,int,int)),
    this,          SLOT(srcRowsInserted(QModelIndex,int,int)));
#if 0
  connect(
    sourceModel(), SIGNAL(columnsAboutToBeInserted(QModelIndex,int,int))
    ,this,         SLOT(srcColumnsAboutToBeInserted(QModelIndex,int,int)
    ));
  connect(
    sourceModel(), SIGNAL(columnsInserted(QModelIndex,int,int)),
    this,          SLOT(srcColumnsInserted(QModelIndex,int,int)));
#endif
  connect(
    sourceModel(), SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)),
    this,          SLOT(srcRowsAboutToBeRemoved(QModelIndex,int,int)));
  connect(
    sourceModel(), SIGNAL(rowsRemoved(QModelIndex,int,int)),
    this,          SLOT(srcRowsRemoved(QModelIndex,int,int)));
#if 0
  connect(
    sourceModel(), SIGNAL(columnsAboutToBeRemoved(QModelIndex,int,int)),
    this,          SLOT(srcColumnsAboutToBeRemoved(QModelIndex,int,int)
    ));
  connect(
    sourceModel(), SIGNAL(columnsRemoved(QModelIndex,int,int)),
    this,          SLOT(srcColumnsRemoved(QModelIndex,int,int)));
#endif
  connect(
    sourceModel(), SIGNAL(layoutAboutToBeChanged()),
    this,          SLOT(srcLayoutAboutToBeChanged()));
  connect(
    sourceModel(), SIGNAL(layoutChanged()),
    this,          SLOT(srcLayoutChanged()));
  connect(
    sourceModel(), SIGNAL(modelReset()),
    this,          SLOT(srcModelReset()));
}

WcViewItemModel* WcViewItemProxyModel::sourceViewItemModel() const
{
  return dynamic_cast<WcViewItemModel*>(sourceModel());
}