/*
  Copyright (c) 2012 Sérgio Martins <iamsergio@gmail.com>

  This program 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.

  This program 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 this program; if not, write to the Free Software Foundation, Inc.,
  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.

  As a special exception, permission is given to link this program
  with any edition of Qt, and distribute the resulting executable,
  without including the source code for Qt in the source distribution.
*/

#include "incidencetreemodel_p.h"

#include <Akonadi/EntityTreeModel>
#include <QElapsedTimer>

using namespace Akonadi;
QDebug operator<<( QDebug s, const Node::Ptr &node );

static void calculateDepth( const Node::Ptr &node )
{
  Q_ASSERT( node );
  node->depth = node->parentNode ? 1+node->parentNode->depth : 0;
  foreach( const Node::Ptr &child, node->directChilds )
    calculateDepth( child );
}

bool lessThan( const Node::Ptr &node1, const Node::Ptr &node2 )
{
    return node1->depth > node2->depth;
}

bool greaterThan( const PreNode::Ptr &node1, const PreNode::Ptr &node2 )
{
    return node1->depth < node2->depth || node2->depth == -1;
}

static PreNode::List sortedPrenodes( const PreNode::List &nodes )
{
  const int count = nodes.count();
  QHash<QString,PreNode::Ptr> prenodeByUid;
  PreNode::List remainingNodes = nodes;

  while ( prenodeByUid.count() < count ) {
    bool foundAtLeastOne = false; // this bool saves us from infinit looping if the parent doesn't exist
    foreach( const PreNode::Ptr &node, remainingNodes ) {
      Q_ASSERT( node );
      const QString uid = node->incidence->instanceIdentifier();
      const QString parentUid = node->incidence->relatedTo();
      if ( parentUid.isEmpty() ) { // toplevel todo
        prenodeByUid.insert( uid, node );
        remainingNodes.removeAll( node );
        node->depth = 0;
        foundAtLeastOne = true;
      } else {
        if ( prenodeByUid.contains( parentUid ) ) {
          node->depth = 1 + prenodeByUid.value( parentUid )->depth;
          remainingNodes.removeAll( node );
          prenodeByUid.insert( uid, node );
          foundAtLeastOne = true;
        }
      }
    }

    if ( !foundAtLeastOne ) {
      break;
    }
  }

  PreNode::List sorted = nodes;
  qSort( sorted.begin(), sorted.end(), greaterThan );
  return sorted;
}

IncidenceTreeModel::Private::Private( IncidenceTreeModel *qq,
                                      const QStringList &mimeTypes ) : QObject()
                                                                     , m_mimeTypes( mimeTypes )
                                                                     , q( qq )

{
}

int IncidenceTreeModel::Private::rowForNode( const Node::Ptr &node ) const
{
  // Returns it's row number
  const int row = node->parentNode ? node->parentNode->directChilds.indexOf( node )
                                   : m_toplevelNodeList.indexOf( node );
  Q_ASSERT ( row != -1 );
  return row;
}

void IncidenceTreeModel::Private::assert_and_dump( bool condition, const QString &message )
{
  if ( !condition ) {
    kError() << "This should never happen: " << message;
    dumpTree();
    Q_ASSERT( false );
  }
}

void IncidenceTreeModel::Private::dumpTree()
{
  foreach( const Node::Ptr &node, m_toplevelNodeList )
    qDebug() << node;
}

QModelIndex IncidenceTreeModel::Private::indexForNode( const Node::Ptr &node ) const
{
  if ( !node ) {
    return QModelIndex();
  }
  const int row = node->parentNode ? node->parentNode->directChilds.indexOf( node )
                                   : m_toplevelNodeList.indexOf( node );

  Q_ASSERT( row != -1 );
  return q->createIndex( row, 0, node.data() );
}

void IncidenceTreeModel::Private::reset( bool silent )
{
  if ( !silent ) {
    q->beginResetModel();
  }
  m_toplevelNodeList.clear();
  m_nodeMap.clear();
  m_itemByUid.clear();
  m_waitingForParent.clear();
  m_uidMap.clear();
  if ( q->sourceModel() ) {
    const int sourceCount = q->sourceModel()->rowCount();
    for ( int i=0; i<sourceCount; ++i ) {
      PreNode::Ptr prenode = prenodeFromSourceRow( i );
      if ( prenode && ( m_mimeTypes.isEmpty() || m_mimeTypes.contains( prenode->incidence->mimeType() ) ) )
        insertNode( prenode, /**silent=*/true );
    }
  }
  if ( !silent ) {
    q->endResetModel();
  }
}

void IncidenceTreeModel::Private::onHeaderDataChanged( Qt::Orientation orientation, int first, int last )
{
  emit q->headerDataChanged( orientation, first, last );
}

void IncidenceTreeModel::Private::onDataChanged( const QModelIndex &begin, const QModelIndex &end )
{
  Q_ASSERT( begin.isValid() );
  Q_ASSERT( end.isValid() );
  Q_ASSERT( q->sourceModel() );
  Q_ASSERT( !begin.parent().isValid() );
  Q_ASSERT( !end.parent().isValid() );
  Q_ASSERT( begin.row() <= end.row() );
  const int first_row = begin.row();
  const int last_row = end.row();

  for( int i=first_row; i<=last_row; ++i ) {
    QModelIndex sourceIndex = q->sourceModel()->index( i, 0 );
    Q_ASSERT( sourceIndex.isValid() );
    QModelIndex index = q->mapFromSource( sourceIndex );
    // Index might be invalid if we filter by incidence type.
    if ( index.isValid() ) {
      Q_ASSERT( index.internalPointer() );

      // Did we this node change parent? If no, just emit dataChanged(), if
      // yes, we must emit rowsMoved(), so we see a visual effect in the view.
      Node *rawNode = reinterpret_cast<Node*>( index.internalPointer() );
      Node::Ptr node = m_uidMap.value( rawNode->uid ); // Looks hackish but it's safe
      Q_ASSERT( node );
      Node::Ptr oldParentNode = node->parentNode;
      Akonadi::Item item = q->data( index, Akonadi::EntityTreeModel::ItemRole ).value<Akonadi::Item>();
      Q_ASSERT( item.isValid() );
      KCalCore::Incidence::Ptr incidence = !item.hasPayload<KCalCore::Incidence::Ptr>() ? KCalCore::Incidence::Ptr() :
                                                                                         item.payload<KCalCore::Incidence::Ptr>();
      if ( !incidence ) {
        kError() << "Incidence shouldn't be invalid." << item.hasPayload() << item.id();
        Q_ASSERT( false );
        return;
      }
      m_itemByUid.insert( incidence->instanceIdentifier(), item );

      Node::Ptr newParentNode;
      const QString newParentUid = incidence->relatedTo();
      if ( !newParentUid.isEmpty() ) {
        Q_ASSERT( m_uidMap.contains( newParentUid ) );
        newParentNode = m_uidMap.value( newParentUid );
        Q_ASSERT( newParentNode );
      }

      const bool parentChanged = newParentNode.data() != oldParentNode.data();

      if ( parentChanged ) {
        const int fromRow = rowForNode( node );
        int toRow = -1;
        QModelIndex newParentIndex;

        // Calculate parameters for beginMoveRows()
        if ( newParentNode ) {
          newParentIndex = q->mapFromSource( newParentNode->sourceIndex );
          Q_ASSERT( newParentIndex.isValid() );
          toRow = newParentNode->directChilds.count();
        } else {
          // New parent is 0, it's son of root now
          newParentIndex = QModelIndex();
          toRow = m_toplevelNodeList.count();
        }

        const bool res = q->beginMoveRows( /**fromParent*/index.parent(), fromRow,
                                           fromRow, newParentIndex, toRow );
        Q_ASSERT( res );
        Q_UNUSED( res );

        // Now that beginmoveRows() was called, we can do the actual moving:
        if ( newParentNode ) {
          newParentNode->directChilds.append( node ); // Add to new parent
          node->parentNode = newParentNode;

          if ( oldParentNode ) {
            oldParentNode->directChilds.remove( fromRow ); // Remove from parent
            Q_ASSERT( oldParentNode->directChilds.indexOf( node ) == -1 );
          } else {
            m_toplevelNodeList.remove( fromRow ); // Remove from root
            Q_ASSERT( m_toplevelNodeList.indexOf( node ) == -1 );
          }
        } else {
          // New parent is 0, it's son of root now
          m_toplevelNodeList.append( node );
          node->parentNode = Node::Ptr();
          oldParentNode->directChilds.remove( fromRow );
          Q_ASSERT( oldParentNode->directChilds.indexOf( node ) == -1 );
        }

        q->endMoveRows();

        // index is rotten after the move, retrieve it again
        index = indexForNode( node );
        Q_ASSERT( index.isValid() );

        if ( newParentNode )
          emit q->indexChangedParent( index.parent() );
      } else {
        emit q->dataChanged( index, index );
      }
    }
  }
}

void IncidenceTreeModel::Private::onRowsAboutToBeInserted( const QModelIndex &parent, int, int )
{
  // We are a reparenting proxy, the source proxy is flat
  Q_ASSERT( !parent.isValid() );
  Q_UNUSED( parent );
  // Nothing to do yet. We don't know if all the new incidences in this range belong to the same
  // parent yet.
}

PreNode::Ptr IncidenceTreeModel::Private::prenodeFromSourceRow( int row ) const
{
  PreNode::Ptr node = PreNode::Ptr( new PreNode() );
  node->sourceIndex = q->sourceModel()->index( row, 0, QModelIndex() );
  Q_ASSERT( node->sourceIndex.isValid() );
  Q_ASSERT( node->sourceIndex.model() == q->sourceModel() );
  const Akonadi::Item item = node->sourceIndex.data( EntityTreeModel::ItemRole ).value<Akonadi::Item>();

  if ( !item.isValid() ) {
    // It's a Collection, ignore that, we only want items.
    return PreNode::Ptr();
  }

  node->item = item;
  node->incidence = item.payload<KCalCore::Incidence::Ptr>();
  Q_ASSERT( node->incidence );

  return node;
}

void IncidenceTreeModel::Private::onRowsInserted( const QModelIndex &parent, int begin, int end )
{
  //QElapsedTimer timer;
  //timer.start();
  Q_ASSERT( !parent.isValid() );
  Q_UNUSED( parent );
  Q_ASSERT( begin <= end );
  PreNode::List nodes;
  for ( int i=begin; i<=end; ++i ) {
    PreNode::Ptr node = prenodeFromSourceRow( i );
    // if m_mimeTypes is empty, we ignore this feature
    if ( !node || ( !m_mimeTypes.isEmpty() && !m_mimeTypes.contains( node->incidence->mimeType() ) ) )
      continue;
    nodes << node;
  }

  PreNode::List sortedNodes = sortedPrenodes( nodes );

  foreach( const PreNode::Ptr &node, sortedNodes ) {
    insertNode( node );
  }

  // view can now call KViewStateSaver::restoreState(), to expand nodes.
  if ( end > begin )
    emit q->batchInsertionFinished();
  //kDebug() << "Took " << timer.elapsed() << " to insert " << end-begin+1;
}

void IncidenceTreeModel::Private::insertNode( const PreNode::Ptr &prenode, bool silent )
{
  KCalCore::Incidence::Ptr incidence = prenode->incidence;
  Akonadi::Item item = prenode->item;
  Node::Ptr node( new Node() );
  node->sourceIndex = prenode->sourceIndex;
  node->id = item.id();
  node->uid = incidence->instanceIdentifier();
  m_itemByUid.insert( node->uid, item );
  //kDebug() << "New node " << node.data() << node->uid << node->id;
  node->parentUid = incidence->relatedTo();
  if ( node->uid == node->parentUid ) {
    kWarning() << "Incidence with itself as parent!" << node->uid << "Akonadi item" << item.id() << "remoteId=" << item.remoteId();
    node->parentUid.clear();
  }

  if (m_uidMap.contains(node->uid)) {
    kWarning() << "Duplicate incidence detected. File a bug against the resource. collection=" << item.storageCollectionId();
    return;
  }

  Q_ASSERT( !m_nodeMap.contains( node->id ) );
  m_uidMap.insert( node->uid, node );
  m_nodeMap.insert( item.id(), node );

  int rowToUse = -1;
  bool mustInsertIntoParent = false;

  const bool hasParent = !node->parentUid.isEmpty();
  if ( hasParent ) {
    // We have a parent, did he arrive yet ?
    if ( m_uidMap.contains( node->parentUid ) ) {
      node->parentNode = m_uidMap.value( node->parentUid );

      // We can only insert after beginInsertRows(), because it affects rowCounts
      mustInsertIntoParent = true;
      rowToUse = node->parentNode->directChilds.count();
    } else {
      // Parent unknown, we are orphan for now
      Q_ASSERT( !m_waitingForParent.contains( node->parentUid, node ) );
      m_waitingForParent.insert( node->parentUid, node );
    }
  }

  if ( !node->parentNode ) {
    rowToUse = m_toplevelNodeList.count();
  }

  // Lets insert the row:
  const QModelIndex &parent = indexForNode( node->parentNode );
  if ( !silent )
    q->beginInsertRows( parent, rowToUse, rowToUse );

  if ( !node->parentNode ) {
    m_toplevelNodeList.append( node );
  }

  if ( mustInsertIntoParent ) {
    node->parentNode->directChilds.append( node );
  }

  if ( !silent )
    q->endInsertRows();

  // Are we a parent?
  if ( m_waitingForParent.contains( node->uid ) ) {
    Q_ASSERT( m_waitingForParent.count( node->uid ) > 0 );
    QList<Node::Ptr> childs = m_waitingForParent.values( node->uid );
    m_waitingForParent.remove( node->uid );
    Q_ASSERT( !childs.isEmpty() );
    const QModelIndex fromParent = QModelIndex();

    foreach( const Node::Ptr &child, childs ) {
      const int fromRow = m_toplevelNodeList.indexOf( child );
      Q_ASSERT( fromRow != -1 );
      const QModelIndex toParent = indexForNode( node );
      Q_ASSERT( toParent.isValid() );
      Q_ASSERT( toParent.model() == q );
      //const int toRow = node->directChilds.count();

      if ( !silent ) {
        //const bool res = q->beginMoveRows( /**fromParent*/QModelIndex(), fromRow,
          //                                 fromRow, toParent, toRow );
        //emit q->layoutAboutToBeChanged();
        q->beginResetModel();
        //Q_ASSERT( res );
      }
      child->parentNode = node;
      node->directChilds.append( child );
      m_toplevelNodeList.remove( fromRow );

      if ( !silent ) {
        //q->endMoveRows();
        q->endResetModel();
        //emit q->layoutChanged();
      }
    }
  }
}

// Sorts childs first parents last
Node::List IncidenceTreeModel::Private::sorted( const Node::List &nodes ) const
{
  if ( nodes.isEmpty() ) {
    return nodes;
  }

  // Initialize depths
  foreach( const Node::Ptr &topLevelNode, m_toplevelNodeList )
    calculateDepth( topLevelNode );

  Node::List sorted = nodes;
  qSort( sorted.begin(), sorted.end(), lessThan );

  return sorted;
}

void IncidenceTreeModel::Private::onRowsAboutToBeRemoved( const QModelIndex &parent, int begin, int end )
{
  //QElapsedTimer timer;
  //timer.start();
  Q_ASSERT( !parent.isValid() );
  Q_UNUSED( parent );
  Q_ASSERT( begin <= end );

  // First, gather nodes to remove
  Node::List nodesToRemove;
  for ( int i=begin; i<=end; ++i ) {
    QModelIndex sourceIndex = q->sourceModel()->index( i, 0, QModelIndex() );
    Q_ASSERT( sourceIndex.isValid() );
    Q_ASSERT( sourceIndex.model() == q->sourceModel() );
    const Akonadi::Item::Id id = sourceIndex.data( EntityTreeModel::ItemIdRole ).toLongLong();
    Q_ASSERT( id != -1 );
    if ( !m_nodeMap.contains( id ) ) {
      // We don't know about this one because we're ignoring it's mime type.
      Q_ASSERT( m_mimeTypes.count() != 3 );
      continue;
    }
    Node::Ptr node = m_nodeMap.value( id );
    Q_ASSERT( node->id == id );
    nodesToRemove << node;
  }

  // We want to remove childs first, to avoid row moving
  Node::List nodesToRemoveSorted = sorted( nodesToRemove );

  foreach( const Node::Ptr &node, nodesToRemoveSorted ) {
    // Go ahead and remove it now. We don't do it in ::onRowsRemoved(), because
    // while unparenting childs with moveRows() the view might call data() on the
    // item that is already removed from ETM.
    removeNode( node );
    //kDebug() << "Just removed a node, here's the tree";
    //dumpTree();
  }

  m_removedNodes.clear();
  //kDebug() << "Took " << timer.elapsed() << " to remove " << end-begin+1;
}

void IncidenceTreeModel::Private::removeNode( const Node::Ptr &node )
{
  Q_ASSERT( node );
  //kDebug() << "Dealing with parent: " << node->id << node.data()
  //         << node->uid << node->directChilds.count() << indexForNode( node );

  // First, unparent the children
  if ( !node->directChilds.isEmpty() ) {
    Node::List childs = node->directChilds;
    const QModelIndex fromParent = indexForNode( node );
    Q_ASSERT( fromParent.isValid() );
//    const int firstSourceRow = 0;
  //  const int lastSourceRow  = node->directChilds.count() - 1;
    //const int toRow = m_toplevelNodeList.count();
    //q->beginMoveRows( fromParent, firstSourceRow, lastSourceRow,
    //                  /**toParent is root*/QModelIndex(), toRow );
    q->beginResetModel();
    node->directChilds.clear();
    foreach( const Node::Ptr &child, childs ) {
      //kDebug() << "Dealing with child: " << child.data() << child->uid;
      m_toplevelNodeList.append( child );
      child->parentNode = Node::Ptr();
      m_waitingForParent.insert( node->uid, child );
    }
    //q->endMoveRows();
    q->endResetModel();
  }

  const QModelIndex parent = indexForNode( node->parentNode );

  const int rowToRemove = rowForNode( node );

  // Now remove the row
  Q_ASSERT( !( parent.isValid() && parent.model() != q ) );
  q->beginRemoveRows( parent, rowToRemove, rowToRemove );
  m_itemByUid.remove( node->uid );

  if ( parent.isValid() ) {
    node->parentNode->directChilds.remove( rowToRemove );
    node->parentNode = Node::Ptr();
  } else {
    m_toplevelNodeList.remove( rowToRemove );
  }

  if ( !node->parentUid.isEmpty() ) {
    m_waitingForParent.remove( node->parentUid, node );
  }

  m_uidMap.remove( node->uid );
  m_nodeMap.remove( node->id );

  q->endRemoveRows();
  m_removedNodes << node.data();
}

void IncidenceTreeModel::Private::onRowsRemoved( const QModelIndex &parent, int begin, int end )
{
  Q_UNUSED( parent );
  Q_UNUSED( begin );
  Q_UNUSED( end );
  // Nothing to do here, see comment on ::onRowsAboutToBeRemoved()
}

void IncidenceTreeModel::Private::onModelAboutToBeReset()
{
  q->beginResetModel();
}

void IncidenceTreeModel::Private::onModelReset()
{
  reset( /**silent=*/false );
  q->endResetModel();
}

void IncidenceTreeModel::Private::onLayoutAboutToBeChanged()
{
  Q_ASSERT( q->persistentIndexList().isEmpty() );
  emit q->layoutAboutToBeChanged();
}

void IncidenceTreeModel::Private::onLayoutChanged()
{
  reset( /**silent=*/true );
  Q_ASSERT( q->persistentIndexList().isEmpty() );
  emit q->layoutChanged();
}

void IncidenceTreeModel::Private::onRowsMoved( const QModelIndex &, int, int, const QModelIndex &, int )
{
  // Not implemented yet
  Q_ASSERT( false );
}

IncidenceTreeModel::IncidenceTreeModel( QObject *parent ) : QAbstractProxyModel( parent )
                                                          , d( new Private( this, QStringList() ) )
{
  setObjectName( QLatin1String("IncidenceTreeModel") );
}

IncidenceTreeModel::IncidenceTreeModel( const QStringList &mimeTypes,
                                        QObject *parent ) : QAbstractProxyModel( parent )
                                                          , d( new Private( this, mimeTypes ) )
{
  setObjectName( QLatin1String("IncidenceTreeModel") );
}

IncidenceTreeModel::~IncidenceTreeModel()
{
  delete d;
}

QVariant IncidenceTreeModel::data( const QModelIndex &index, int role ) const
{
  Q_ASSERT( index.isValid() );
  if ( !index.isValid() || !sourceModel() ) {
    return QVariant();
  }

  QModelIndex sourceIndex = mapToSource( index );
  Q_ASSERT( sourceIndex.isValid() );

  return sourceModel()->data( sourceIndex, role );
}

int IncidenceTreeModel::rowCount( const QModelIndex &parent ) const
{
  if ( parent.isValid() ) {
    Q_ASSERT( parent.model() == this );
    Node *parentNode = reinterpret_cast<Node*>( parent.internalPointer() );
    Q_ASSERT( parentNode );
    d->assert_and_dump( !d->m_removedNodes.contains( parentNode ),
                        QString::number( (quintptr)parentNode, 16 ) + QLatin1String(" was already deleted") );

    const int count = parentNode->directChilds.count();
    return count;
  }

  return d->m_toplevelNodeList.count();
}

int IncidenceTreeModel::columnCount( const QModelIndex &parent ) const
{
  if ( parent.isValid() ) {
    Q_ASSERT( parent.model() == this );
  }
  return sourceModel() ? sourceModel()->columnCount() : 1;
}

void IncidenceTreeModel::setSourceModel( QAbstractItemModel *model )
{
  if ( model == sourceModel() ) {
    return;
  }

  beginResetModel();

  if ( sourceModel() ) {
    disconnect( sourceModel(), SIGNAL(dataChanged(QModelIndex,QModelIndex)),
                d, SLOT(onDataChanged(QModelIndex,QModelIndex)) );

    disconnect( sourceModel(), SIGNAL(headerDataChanged(Qt::Orientation,int,int)),
                d, SLOT(onHeaderDataChanged(Qt::Orientation,int,int)) );

    disconnect( sourceModel(), SIGNAL(rowsInserted(QModelIndex,int,int)),
                d, SLOT(onRowsInserted(QModelIndex,int,int)) );

    disconnect( sourceModel(), SIGNAL(rowsRemoved(QModelIndex,int,int)),
                d, SLOT(onRowsRemoved(QModelIndex,int,int)) );

    disconnect( sourceModel(), SIGNAL(rowsMoved(QModelIndex,int,int,QModelIndex,int)),
                d, SLOT(onRowsMoved(QModelIndex,int,int,QModelIndex,int)) );

    disconnect( sourceModel(), SIGNAL(rowsAboutToBeInserted(QModelIndex,int,int)),
                d, SLOT(onRowsAboutToBeInserted(QModelIndex,int,int)) );

    disconnect( sourceModel(), SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)),
                d, SLOT(onRowsAboutToBeRemoved(QModelIndex,int,int)) );

    disconnect( sourceModel(), SIGNAL(modelAboutToBeReset()),
                d, SLOT(onModelAboutToBeReset()) );

    disconnect( sourceModel(), SIGNAL(modelReset()),
                d, SLOT(onModelReset()) );

    disconnect( sourceModel(), SIGNAL(layoutAboutToBeChanged()),
                d, SLOT(onLayoutAboutToBeChanged()) );

    disconnect( sourceModel(), SIGNAL(layoutChanged()),
                d, SLOT(onLayoutChanged()) );
  }

  QAbstractProxyModel::setSourceModel( model );

  if ( sourceModel() ) {
    connect( sourceModel(), SIGNAL(dataChanged(QModelIndex,QModelIndex)),
             d, SLOT(onDataChanged(QModelIndex,QModelIndex)) );

    connect( sourceModel(), SIGNAL(headerDataChanged(Qt::Orientation,int,int)),
             d, SLOT(onHeaderDataChanged(Qt::Orientation,int,int)) );

    connect( sourceModel(), SIGNAL(rowsAboutToBeInserted(QModelIndex,int,int)),
             d, SLOT(onRowsAboutToBeInserted(QModelIndex,int,int)) );

    connect( sourceModel(), SIGNAL(rowsInserted(QModelIndex,int,int)),
             d, SLOT(onRowsInserted(QModelIndex,int,int)) );

    connect( sourceModel(), SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)),
             d, SLOT(onRowsAboutToBeRemoved(QModelIndex,int,int)) );

    connect( sourceModel(), SIGNAL(rowsRemoved(QModelIndex,int,int)),
             d, SLOT(onRowsRemoved(QModelIndex,int,int)) );

    connect( sourceModel(), SIGNAL(rowsMoved(QModelIndex,int,int,QModelIndex,int)),
             d, SLOT(onRowsMoved(QModelIndex,int,int,QModelIndex,int)) );

    connect( sourceModel(), SIGNAL(modelAboutToBeReset()),
             d, SLOT(onModelAboutToBeReset()) );

    connect( sourceModel(), SIGNAL(modelReset()),
             d, SLOT(onModelReset()) );

    connect( sourceModel(), SIGNAL(layoutAboutToBeChanged()),
             d, SLOT(onLayoutAboutToBeChanged()) );

    connect( sourceModel(), SIGNAL(layoutChanged()),
             d, SLOT(onLayoutChanged()) );
  }

  d->reset( /**silent=*/true );
  endResetModel();
}

QModelIndex IncidenceTreeModel::mapFromSource( const QModelIndex &sourceIndex ) const
{
  if ( !sourceIndex.isValid() ) {
    kWarning() << "IncidenceTreeModel::mapFromSource() source index is invalid";
    // Q_ASSERT( false );
    return QModelIndex();
  }

  if ( !sourceModel() ) {
    return QModelIndex();
  }
  Q_ASSERT( sourceIndex.column() < sourceModel()->columnCount() );
  Q_ASSERT( sourceModel() == sourceIndex.model() );
  const Akonadi::Item::Id id = sourceIndex.data( Akonadi::EntityTreeModel::ItemIdRole ).toLongLong();

  if ( id == -1 || !d->m_nodeMap.contains( id ) )
    return QModelIndex();

  const Node::Ptr node = d->m_nodeMap.value( id );
  Q_ASSERT( node );

  return d->indexForNode( node );
}

QModelIndex IncidenceTreeModel::mapToSource( const QModelIndex &proxyIndex ) const
{
  if ( !proxyIndex.isValid() || !sourceModel() ) {
    return QModelIndex();
  }

  Q_ASSERT( proxyIndex.column() < columnCount() );
  Q_ASSERT( proxyIndex.internalPointer() );
  Q_ASSERT( proxyIndex.model() == this );
  Node *node = reinterpret_cast<Node*>( proxyIndex.internalPointer() );

  /*
   This code is slow, using a persistent model index instead.
  QModelIndexList indexes = EntityTreeModel::modelIndexesForItem( sourceModel(), Akonadi::Item( node->id ) );
  if ( indexes.isEmpty() ) {
    Q_ASSERT( sourceModel() );
    kError() << "IncidenceTreeModel::mapToSource() no indexes."
             << proxyIndex << node->id << "; source.rowCount() = "
             << sourceModel()->rowCount() << "; source=" << sourceModel()
             << "rowCount()" << rowCount();
    Q_ASSERT( false );
    return QModelIndex();
  }
  QModelIndex index = indexes.first();*/
  QModelIndex index = node->sourceIndex;
  if ( !index.isValid() ) {
    kWarning() << "IncidenceTreeModel::mapToSource(): sourceModelIndex is invalid";
    Q_ASSERT( false );
    return QModelIndex();
  }
  Q_ASSERT( index.model() == sourceModel() );

  return index.sibling( index.row(), proxyIndex.column() );
}

QModelIndex IncidenceTreeModel::parent( const QModelIndex &child ) const
{
  if ( !child.isValid() ) {
    kWarning() << "IncidenceTreeModel::parent(): child is invalid";
    Q_ASSERT( false );
    return QModelIndex();
  }

  Q_ASSERT( child.model() == this );
  Q_ASSERT( child.internalPointer() );
  Node *childNode = reinterpret_cast<Node*>( child.internalPointer() );
  if ( d->m_removedNodes.contains( childNode ) ) {
    kWarning() << "IncidenceTreeModel::parent() Node already removed.";
    return QModelIndex();
  }

  if ( !childNode->parentNode ) {
    return QModelIndex();
  }

  const QModelIndex parentIndex = d->indexForNode( childNode->parentNode );

  if ( !parentIndex.isValid() ) {
    kWarning() << "IncidenceTreeModel::parent(): proxyModelIndex is invalid.";
    Q_ASSERT( false );
    return QModelIndex();
  }

  Q_ASSERT( parentIndex.model() == this );
  Q_ASSERT( childNode->parentNode.data() );

  // Parent is always at row 0
  return parentIndex;
}

QModelIndex IncidenceTreeModel::index( int row, int column, const QModelIndex &parent ) const
{
  if ( row < 0 || row >= rowCount( parent ) ) {
    // This is ok apparently
    /*kWarning() << "IncidenceTreeModel::index() parent.isValid()" << parent.isValid()
               << "; row=" << row << "; column=" << column
               << "; rowCount() = " << rowCount( parent ); */
    // Q_ASSERT( false );
    return QModelIndex();
  }

  Q_ASSERT( column >= 0 );
  Q_ASSERT( column < columnCount() );

  if ( parent.isValid() ) {
    Q_ASSERT( parent.model() == this );
    Q_ASSERT( parent.internalPointer() );
    Node *parentNode = reinterpret_cast<Node*>( parent.internalPointer() );

    if ( row >= parentNode->directChilds.count() ) {
      kError() << "IncidenceTreeModel::index() row=" << row << "; column=" << column;
      Q_ASSERT( false );
      return QModelIndex();
    }

    return createIndex( row, column,
                        parentNode->directChilds.at( row ).data() );
  } else {
    Q_ASSERT( row < d->m_toplevelNodeList.count() );
    Node::Ptr node = d->m_toplevelNodeList.at( row );
    Q_ASSERT( node );
    return createIndex( row, column, node.data() );
  }
}

bool IncidenceTreeModel::hasChildren( const QModelIndex &parent ) const
{
  if ( parent.isValid() ) {
    Q_ASSERT( parent.column() < columnCount() );
    if ( parent.column() != 0 ) {
      // Indexes at column >0 don't have parent, says Qt documentation
      return false;
    }
    Node *parentNode = reinterpret_cast<Node*>( parent.internalPointer() );
    Q_ASSERT( parentNode );
    return !parentNode->directChilds.isEmpty();
  } else {
    return !d->m_toplevelNodeList.isEmpty();
  }
}

Akonadi::Item IncidenceTreeModel::item( const QString &uid ) const
{
  Akonadi::Item item;
  if ( uid.isEmpty() ) {
    kWarning() << "Called with an empty uid";
  } else {
    if ( d->m_itemByUid.contains( uid ) ) {
      item = d->m_itemByUid.value( uid );
    } else {
      kWarning() << "There's no incidence with uid " << uid;
    }
  }

  return item;
}

QDebug operator<<( QDebug s, const Node::Ptr &node )
{
  Q_ASSERT( node );
  static int level = 0;
  ++level;
  QString padding = QString(level-1, QLatin1Char(' ') );
  s << padding+QLatin1String("node") << node.data() << QLatin1String(";uid=") << node->uid << QLatin1String(";id=") << node->id << QLatin1String(";parentUid=") << node->parentUid << QLatin1String(";parentNode=") << (void*)(node->parentNode.data()) << '\n';

  foreach( const Node::Ptr &child, node->directChilds ) {
    s << child;
  }

  --level;
  return s;
}
