/*  This file is part of the KDE project
    Copyright (C) 2010 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.net
    Author: Kevin Krammer, krake@kdab.com

    This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Library General Public
    License as published by the Free Software Foundation; either
    version 2 of the License, or (at your option) any later version.

    This library 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
    Library General Public License for more details.

    You should have received a copy of the GNU Library General Public License
    along with this library; see the file COPYING.LIB.  If not, write to
    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
    Boston, MA 02110-1301, USA.
*/

#include "emptyresourcecleaner.h"

#include <akonadi/agentinstance.h>
#include <akonadi/agentmanager.h>
#include <akonadi/collection.h>
#include <akonadi/collectiondeletejob.h>
#include <akonadi/collectionfetchjob.h>
#include <akonadi/collectionfetchscope.h>
#include <akonadi/collectionstatistics.h>
#include <akonadi/itemfetchjob.h>

#include <KDebug>

#include <QHash>
#include <QVariant>

using namespace Akonadi;

typedef QHash<Collection::Id, Collection> CollectionHash;

class EmptyResourceCleaner::Private
{
  EmptyResourceCleaner *const q;

  public:
    Private( EmptyResourceCleaner *parent, const AgentInstance &resource )
      : q( parent ), mResource( resource ), mOptions( CheckOnly )
    {
    }

    void deleteCollections();
    void deleteResource();

    void cleaupFinished();

  public:
    AgentInstance mResource;

    CleaningOptions mOptions;

    Collection::List mAllCollections;
    CollectionHash mDeletableCollections;
    CollectionHash mDeletedCollections;

  public: // slots
    void collectionFetchResult( KJob *job );
    void collectionDeleteResult( KJob *job );
};

void EmptyResourceCleaner::Private::deleteCollections()
{
  CollectionHash::iterator it = mDeletableCollections.begin();
  while ( it != mDeletableCollections.end() ) {
    const Collection collection = it.value();
    const Collection parent = collection.parentCollection();
    if ( mDeletedCollections.contains( parent.id() ) ) {
      mDeletedCollections.insert( collection.id(), collection );
      it = mDeletableCollections.erase( it );
    } else {
      kDebug( KDE_DEFAULT_DEBUG_AREA ) << "Removing collection: id=" << collection.id()
                                       << "remoteId=" << collection.remoteId();
      CollectionDeleteJob *deleteJob = new CollectionDeleteJob( collection );
      deleteJob->setProperty( "collection", QVariant::fromValue<Collection>( collection ) );
      connect( deleteJob, SIGNAL(result(KJob*)), q, SLOT(collectionDeleteResult(KJob*)) );
      return;
    }
  }

  Q_ASSERT( mDeletableCollections.isEmpty() );

  if ( mOptions.testFlag( DeleteEmptyResource ) &&
       mDeletedCollections.count() == mAllCollections.count() ) {
    deleteResource();
  } else {
    emit q->cleanupFinished( mResource );
  }
}

void EmptyResourceCleaner::Private::deleteResource()
{
  kDebug( KDE_DEFAULT_DEBUG_AREA ) << "Removing resource" << mResource.identifier();

  AgentManager::self()->removeInstance( mResource );

  emit q->cleanupFinished( mResource );
}

void EmptyResourceCleaner::Private::collectionFetchResult( KJob *job )
{
  if ( job->error() != 0 ) {
    kError() << job->errorString();
    emit q->cleanupFinished( mResource );
    return;
  }

  CollectionFetchJob *fetchJob = qobject_cast<CollectionFetchJob*>( job );
  Q_ASSERT( fetchJob != 0 );

  mAllCollections = fetchJob->collections();
  CollectionHash nonEmptyCollections;
  Q_FOREACH ( const Collection &collection, mAllCollections ) {
    qint64 itemCount = collection.statistics().count();
    // TODO should be async as well
    if ( itemCount == -1 ) {
      ItemFetchJob *itemFetch = new ItemFetchJob( collection );
      if ( itemFetch->exec() ) {
        itemCount = itemFetch->items().count();
      }
    }

    kDebug( KDE_DEFAULT_DEBUG_AREA ) << "collection" << collection.name()
                                     << "has" << itemCount
                                     << "items";
    if ( itemCount == 0 ) {
      mDeletableCollections.insert( collection.id(), collection );
    } else {
      nonEmptyCollections.insert( collection.id(), collection );
    }
  }

  kDebug( KDE_DEFAULT_DEBUG_AREA ) << mDeletableCollections.count()
                                   << "potenially deletable collections"
                                   << nonEmptyCollections.count() << "not empty";

  Q_FOREACH ( const Collection &collection, nonEmptyCollections ) {
    Collection parent = collection.parentCollection();
    while ( parent.isValid() && parent != Collection::root() ) {
      mDeletableCollections.remove( parent.id() );
      parent = parent.parentCollection();
    }
  }

  kDebug( KDE_DEFAULT_DEBUG_AREA ) << mDeletableCollections.count() << "of"
                                   << mAllCollections.count() << "are empty";

  if ( mOptions.testFlag( DeleteEmptyCollections ) ) {
    deleteCollections();
  } else if ( mOptions.testFlag( DeleteEmptyResource ) &&
              mDeletableCollections.count() == mAllCollections.count() ) {
    deleteResource();
  } else {
    emit q->cleanupFinished( mResource );
  }
}

void EmptyResourceCleaner::Private::collectionDeleteResult( KJob *job )
{
  if ( job->error() != 0 ) {
    kError() << job->errorString();
    emit q->cleanupFinished( mResource );
    return;
  }

  CollectionDeleteJob *deleteJob = qobject_cast<CollectionDeleteJob*>( job );
  Q_ASSERT( deleteJob != 0 );

  const Collection collection = deleteJob->property( "collection" ).value<Collection>();
  mDeletableCollections.remove( collection.id() );
  mDeletedCollections.insert( collection.id(), collection );

  deleteCollections();
}

EmptyResourceCleaner::EmptyResourceCleaner( const AgentInstance &resource, QObject *parent )
  : QObject( parent ), d( new Private( this, resource ) )
{
  kDebug( KDE_DEFAULT_DEBUG_AREA ) << "Creating cleaner for resource"
                                   << d->mResource.identifier();
  connect( this, SIGNAL(cleanupFinished(Akonadi::AgentInstance)), SLOT(deleteLater()) );

  CollectionFetchScope scope;
  scope.setResource( d->mResource.identifier() );
  scope.setAncestorRetrieval( CollectionFetchScope::All );
  scope.setIncludeStatistics( true );

  CollectionFetchJob *fetchJob = new CollectionFetchJob( Collection::root(), CollectionFetchJob::Recursive );
  fetchJob->setFetchScope( scope );
  connect( fetchJob, SIGNAL(result(KJob*)), SLOT(collectionFetchResult(KJob*)) );
}

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

void EmptyResourceCleaner::setCleaningOptions( CleaningOptions options )
{
  d->mOptions = options;
}

EmptyResourceCleaner::CleaningOptions EmptyResourceCleaner::cleaningOptions() const
{
  return d->mOptions;
}

Collection::List EmptyResourceCleaner::deletableCollections() const
{
  return d->mDeletableCollections.values();
}

bool EmptyResourceCleaner::isResourceDeletable() const
{
  return ( d->mDeletedCollections.count() + d->mDeletableCollections.count() )
           == d->mAllCollections.count();
}

#include "moc_emptyresourcecleaner.cpp"

// kate: space-indent on; indent-width 2; replace-tabs on;
