/*
    Copyright (c) 2006 Till Adam <adam@kde.org>
    Copyright (c) 2007 Volker Krause <vkrause@kde.org>
    Copyright (c) 2007 Bruno Virlet <bruno.virlet@gmail.com>
    Copyright (c) 2008 Kevin Krammer <kevin.krammer@gmx.at>

    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 "agentbase.h"
#include "agentbase_p.h"

#include "agentmanager.h"
#include "changerecorder.h"
#include "controladaptor.h"
#include "dbusconnectionpool.h"
#include "itemfetchjob.h"
#include "kdepimlibs-version.h"
#include "monitor_p.h"
#include "servermanager_p.h"
#include "session.h"
#include "session_p.h"
#include "statusadaptor.h"

#include <kaboutdata.h>
#include <kcmdlineargs.h>
#include <kdebug.h>
#include <kglobal.h>
#include <klocalizedstring.h>
#include <kstandarddirs.h>

#include <Solid/PowerManagement>

#include <QtCore/QDir>
#include <QtCore/QSettings>
#include <QtCore/QTimer>
#include <QtDBus/QtDBus>
#include <QApplication>

#include <signal.h>
#include <stdlib.h>
#if defined __GLIBC__
# include <malloc.h> // for dumping memory information
#endif

//#define EXPERIMENTAL_INPROCESS_AGENTS 1

using namespace Akonadi;

static AgentBase *sAgentBase = 0;

AgentBase::Observer::Observer()
{
}

AgentBase::Observer::~Observer()
{
}

void AgentBase::Observer::itemAdded( const Item &item, const Collection &collection )
{
  Q_UNUSED( item );
  Q_UNUSED( collection );
  if ( sAgentBase != 0 ) {
    sAgentBase->d_ptr->changeProcessed();
  }
}

void AgentBase::Observer::itemChanged( const Item &item, const QSet<QByteArray> &partIdentifiers )
{
  Q_UNUSED( item );
  Q_UNUSED( partIdentifiers );
  if ( sAgentBase != 0 ) {
    sAgentBase->d_ptr->changeProcessed();
  }
}

void AgentBase::Observer::itemRemoved( const Item &item )
{
  Q_UNUSED( item );
  if ( sAgentBase != 0 ) {
    sAgentBase->d_ptr->changeProcessed();
  }
}

void AgentBase::Observer::collectionAdded( const Akonadi::Collection &collection, const Akonadi::Collection &parent )
{
  Q_UNUSED( collection );
  Q_UNUSED( parent );
  if ( sAgentBase != 0 ) {
    sAgentBase->d_ptr->changeProcessed();
  }
}

void AgentBase::Observer::collectionChanged( const Collection &collection )
{
  Q_UNUSED( collection );
  if ( sAgentBase != 0 ) {
    sAgentBase->d_ptr->changeProcessed();
  }
}

void AgentBase::Observer::collectionRemoved( const Collection &collection )
{
  Q_UNUSED( collection );
  if ( sAgentBase != 0 ) {
    sAgentBase->d_ptr->changeProcessed();
  }
}

void AgentBase::ObserverV2::itemMoved( const Akonadi::Item &item, const Akonadi::Collection &source, const Akonadi::Collection &dest )
{
  Q_UNUSED( item );
  Q_UNUSED( source );
  Q_UNUSED( dest );
  if ( sAgentBase != 0 ) {
    sAgentBase->d_ptr->changeProcessed();
  }
}

void AgentBase::ObserverV2::itemLinked( const Akonadi::Item& item, const Akonadi::Collection& collection )
{
  Q_UNUSED( item );
  Q_UNUSED( collection );
  if ( sAgentBase != 0 ) {
    // not implementation, let's disconnect the signal to enable optimizations in Monitor
    QObject::disconnect( sAgentBase->changeRecorder(), SIGNAL(itemLinked(Akonadi::Item,Akonadi::Collection)),
                         sAgentBase->d_ptr, SLOT(itemLinked(Akonadi::Item,Akonadi::Collection)) );
    sAgentBase->d_ptr->changeProcessed();
  }
}

void AgentBase::ObserverV2::itemUnlinked( const Akonadi::Item& item, const Akonadi::Collection& collection )
{
  Q_UNUSED( item );
  Q_UNUSED( collection );
  if ( sAgentBase != 0 ) {
    // not implementation, let's disconnect the signal to enable optimizations in Monitor
    QObject::disconnect( sAgentBase->changeRecorder(), SIGNAL(itemUnlinked(Akonadi::Item,Akonadi::Collection)),
                         sAgentBase->d_ptr, SLOT(itemUnlinked(Akonadi::Item,Akonadi::Collection)) );
    sAgentBase->d_ptr->changeProcessed();
  }
}

void AgentBase::ObserverV2::collectionMoved( const Akonadi::Collection &collection, const Akonadi::Collection &source, const Akonadi::Collection &dest )
{
  Q_UNUSED( collection );
  Q_UNUSED( source );
  Q_UNUSED( dest );
  if ( sAgentBase != 0 ) {
    sAgentBase->d_ptr->changeProcessed();
  }
}

void AgentBase::ObserverV2::collectionChanged( const Akonadi::Collection &collection, const QSet<QByteArray> &changedAttributes )
{
  Q_UNUSED( changedAttributes );
  collectionChanged( collection );
}

void AgentBase::ObserverV3::itemsFlagsChanged(const Akonadi::Item::List& items, const QSet< QByteArray >& addedFlags, const QSet< QByteArray >& removedFlags)
{
  Q_UNUSED( items );
  Q_UNUSED( addedFlags );
  Q_UNUSED( removedFlags );

  if ( sAgentBase != 0 ) {
    // not implementation, let's disconnect the signal to enable optimizations in Monitor
    QObject::disconnect( sAgentBase->changeRecorder(), SIGNAL(itemsFlagsChanged(Akonadi::Item::List,QSet<QByteArray>,QSet<QByteArray>)),
                         sAgentBase->d_ptr, SLOT(itemsFlagsChanged(Akonadi::Item::List,QSet<QByteArray>,QSet<QByteArray>)) );
    sAgentBase->d_ptr->changeProcessed();
  }
}

void AgentBase::ObserverV3::itemsMoved(const Akonadi::Item::List& items, const Collection& sourceCollection, const Collection& destinationCollection)
{
  Q_UNUSED( items );
  Q_UNUSED( sourceCollection );
  Q_UNUSED( destinationCollection );

  if ( sAgentBase != 0 ) {
    // not implementation, let's disconnect the signal to enable optimizations in Monitor
    QObject::disconnect( sAgentBase->changeRecorder(), SIGNAL(itemsMoved(Akonadi::Item::List,Akonadi::Collection,Akonadi::Collection)),
                         sAgentBase->d_ptr, SLOT(itemsMoved(Akonadi::Item::List,Akonadi::Collection,Akonadi::Collection)) );
    sAgentBase->d_ptr->changeProcessed();
  }
}

void AgentBase::ObserverV3::itemsRemoved(const Akonadi::Item::List& items)
{
  Q_UNUSED( items );

  if ( sAgentBase != 0 ) {
    // not implementation, let's disconnect the signal to enable optimizations in Monitor
    QObject::disconnect( sAgentBase->changeRecorder(), SIGNAL(itemsRemoved(Akonadi::Item::List)),
                         sAgentBase->d_ptr, SLOT(itemsRemoved(Akonadi::Item::List)) );
    sAgentBase->d_ptr->changeProcessed();
  }
}

void AgentBase::ObserverV3::itemsLinked(const Akonadi::Item::List& items, const Collection& collection)
{
  Q_UNUSED( items );
  Q_UNUSED( collection );

  if ( sAgentBase != 0 ) {
    // not implementation, let's disconnect the signal to enable optimizations in Monitor
    QObject::disconnect( sAgentBase->changeRecorder(), SIGNAL(itemsLinked(Akonadi::Item::List,Akonadi::Collection)),
                         sAgentBase->d_ptr, SLOT(itemsLinked(Akonadi::Item::List,Akonadi::Collection)) );
    sAgentBase->d_ptr->changeProcessed();
  }
}

void AgentBase::ObserverV3::itemsUnlinked(const Akonadi::Item::List& items, const Collection& collection)
{
  Q_UNUSED( items );
  Q_UNUSED( collection )

  if ( sAgentBase != 0 ) {
    // not implementation, let's disconnect the signal to enable optimizations in Monitor
    QObject::disconnect( sAgentBase->changeRecorder(), SIGNAL(itemsUnlinked(Akonadi::Item::List,Akonadi::Collection)),
                         sAgentBase->d_ptr, SLOT(itemsUnlinked(Akonadi::Item::List,Akonadi::Collection)) );
    sAgentBase->d_ptr->changeProcessed();
  }
}

void AgentBase::ObserverV4::tagAdded( const Tag &tag )
{
  Q_UNUSED( tag );

  if ( sAgentBase != 0 ) {
    // not implementation, let's disconnect the signal to enable optimization in Monitor
    QObject::disconnect( sAgentBase->changeRecorder(), SIGNAL(tagAdded(Akonadi::Tag)),
                         sAgentBase->d_ptr, SLOT(tagAdded(Akonadi::Tag)) );
    sAgentBase->d_ptr->changeProcessed();
  }
}

void AgentBase::ObserverV4::tagChanged( const Tag &tag )
{
  Q_UNUSED( tag );

  if ( sAgentBase != 0 ) {
    // not implementation, let's disconnect the signal to enable optimization in Monitor
    QObject::disconnect( sAgentBase->changeRecorder(), SIGNAL(tagChanged(Akonadi::Tag)),
                         sAgentBase->d_ptr, SLOT(tagChanged(Akonadi::Tag)) );
    sAgentBase->d_ptr->changeProcessed();
  }
}

void AgentBase::ObserverV4::tagRemoved( const Tag &tag )
{
  Q_UNUSED( tag );

  if ( sAgentBase != 0 ) {
    // not implementation, let's disconnect the signal to enable optimization in Monitor
    QObject::disconnect( sAgentBase->changeRecorder(), SIGNAL(tagRemoved(Akonadi::Tag)),
                         sAgentBase->d_ptr, SLOT(tagRemoved(Akonadi::Tag)) );
    sAgentBase->d_ptr->changeProcessed();
  }
}

void AgentBase::ObserverV4::itemsTagsChanged( const Item::List &items, const QSet<Tag> &addedTags, const QSet<Tag> &removedTags )
{
  Q_UNUSED( items );
  Q_UNUSED( addedTags );
  Q_UNUSED( removedTags );

  if ( sAgentBase != 0 ) {
    // not implementation, let's disconnect the signal to enable optimization in Monitor
    QObject::disconnect( sAgentBase->changeRecorder(), SIGNAL(itemsTagsChanged(Akonadi::Item::List,QSet<Akonadi::Tag>,QSet<Akonadi::Tag>)),
                         sAgentBase->d_ptr, SLOT(itemsTagsChanged(Akonadi::Item::List,QSet<Akonadi::Tag>,QSet<Akonadi::Tag>)) );
    sAgentBase->d_ptr->changeProcessed();
  }
}



//@cond PRIVATE

AgentBasePrivate::AgentBasePrivate( AgentBase *parent )
  : q_ptr( parent ),
    mDBusConnection( QString() ),
    mStatusCode( AgentBase::Idle ),
    mProgress( 0 ),
    mNeedsNetwork( false ),
    mOnline( false ),
    mDesiredOnlineState( false ),
    mSettings( 0 ),
    mChangeRecorder( 0 ),
    mTracer( 0 ),
    mObserver(0),
    mTemporaryOfflineTimer(0)
{
  Internal::setClientType( Internal::Agent );
}

AgentBasePrivate::~AgentBasePrivate()
{
  mChangeRecorder->setConfig( 0 );
  delete mSettings;
}

void AgentBasePrivate::init()
{
  Q_Q( AgentBase );

  /**
   * Create a default session for this process.
   */
  SessionPrivate::createDefaultSession( mId.toLatin1() );

  if ( QThread::currentThread() != QCoreApplication::instance()->thread() ) {
    mDBusConnection = QDBusConnection::connectToBus( QDBusConnection::SessionBus, q->identifier() );
    Q_ASSERT( mDBusConnection.isConnected() );
  }

  mTracer = new org::freedesktop::Akonadi::Tracer( ServerManager::serviceName( ServerManager::Server ),
                                                   QLatin1String( "/tracing" ),
                                                   DBusConnectionPool::threadConnection(), q );

  new Akonadi__ControlAdaptor( q );
  new Akonadi__StatusAdaptor( q );
  if ( !DBusConnectionPool::threadConnection().registerObject( QLatin1String( "/" ), q, QDBusConnection::ExportAdaptors ) )
    q->error( i18n( "Unable to register object at dbus: %1", DBusConnectionPool::threadConnection().lastError().message() ) );

  mSettings = new QSettings( QString::fromLatin1( "%1/agent_config_%2" ).arg( Internal::xdgSaveDir( "config" ), mId ), QSettings::IniFormat );

  mChangeRecorder = new ChangeRecorder( q );
  mChangeRecorder->ignoreSession( Session::defaultSession() );
  mChangeRecorder->itemFetchScope().setCacheOnly( true );
  mChangeRecorder->setConfig( mSettings );

  mDesiredOnlineState = mSettings->value( QLatin1String( "Agent/DesiredOnlineState" ), true ).toBool();
  mOnline = mDesiredOnlineState;

  // reinitialize the status message now that online state is available
  mStatusMessage = defaultReadyMessage();

  mName = mSettings->value( QLatin1String( "Agent/Name" ) ).toString();
  if ( mName.isEmpty() ) {
    mName = mSettings->value( QLatin1String( "Resource/Name" ) ).toString();
    if ( !mName.isEmpty() ) {
      mSettings->remove( QLatin1String( "Resource/Name" ) );
      mSettings->setValue( QLatin1String( "Agent/Name" ), mName );
    }
  }

  connect( mChangeRecorder, SIGNAL(itemAdded(Akonadi::Item,Akonadi::Collection)),
           SLOT(itemAdded(Akonadi::Item,Akonadi::Collection)) );
  connect( mChangeRecorder, SIGNAL(itemChanged(Akonadi::Item,QSet<QByteArray>)),
           SLOT(itemChanged(Akonadi::Item,QSet<QByteArray>)) );
  connect( mChangeRecorder, SIGNAL(collectionAdded(Akonadi::Collection,Akonadi::Collection)),
           SLOT(collectionAdded(Akonadi::Collection,Akonadi::Collection)) );
  connect( mChangeRecorder, SIGNAL(collectionChanged(Akonadi::Collection)),
           SLOT(collectionChanged(Akonadi::Collection)) );
  connect( mChangeRecorder, SIGNAL(collectionChanged(Akonadi::Collection,QSet<QByteArray>)),
           SLOT(collectionChanged(Akonadi::Collection,QSet<QByteArray>)) );
  connect( mChangeRecorder, SIGNAL(collectionMoved(Akonadi::Collection,Akonadi::Collection,Akonadi::Collection)),
           SLOT(collectionMoved(Akonadi::Collection,Akonadi::Collection,Akonadi::Collection)) );
  connect( mChangeRecorder, SIGNAL(collectionRemoved(Akonadi::Collection)),
           SLOT(collectionRemoved(Akonadi::Collection)) );
  connect( mChangeRecorder, SIGNAL(collectionSubscribed(Akonadi::Collection,Akonadi::Collection)),
           SLOT(collectionSubscribed(Akonadi::Collection,Akonadi::Collection)) );
  connect( mChangeRecorder, SIGNAL(collectionUnsubscribed(Akonadi::Collection)),
           SLOT(collectionUnsubscribed(Akonadi::Collection)) );

  connect( q, SIGNAL(status(int,QString)), q, SLOT(slotStatus(int,QString)) );
  connect( q, SIGNAL(percent(int)), q, SLOT(slotPercent(int)) );
  connect( q, SIGNAL(warning(QString)), q, SLOT(slotWarning(QString)) );
  connect( q, SIGNAL(error(QString)), q, SLOT(slotError(QString)) );

  connect( Solid::PowerManagement::notifier(), SIGNAL(resumingFromSuspend()), q, SLOT(slotResumedFromSuspend()) );

  // Use reference counting to allow agents to finish internal jobs when the
  // agent is stopped.
  KGlobal::ref();
  if ( QThread::currentThread() == QCoreApplication::instance()->thread() ) {
    KGlobal::setAllowQuit( true );
  }

  // disable session management
  if ( KApplication::kApplication() ) {
    KApplication::kApplication()->disableSessionManagement();
  }

  mResourceTypeName = AgentManager::self()->instance( mId ).type().name();
  setProgramName();

  QTimer::singleShot( 0, q, SLOT(delayedInit()) );
}

void AgentBasePrivate::delayedInit()
{
  Q_Q( AgentBase );

  const QString serviceId = ServerManager::agentServiceName( ServerManager::Agent, mId );
  if ( !DBusConnectionPool::threadConnection().registerService( serviceId ) ) {
    kFatal() << "Unable to register service" << serviceId << "at dbus:" << DBusConnectionPool::threadConnection().lastError().message();
  }
  q->setOnlineInternal( mDesiredOnlineState );

  DBusConnectionPool::threadConnection().registerObject( QLatin1String( "/Debug" ), this, QDBusConnection::ExportScriptableSlots );
}

void AgentBasePrivate::setProgramName()
{
  // ugly, really ugly, if you find another solution, change it and blame me for this code (Andras)
  QString programName = mResourceTypeName;
  if ( !mName.isEmpty() ) {
    programName = i18nc( "Name and type of Akonadi resource", "%1 of type %2", mName, mResourceTypeName ) ;
  }
  const_cast<KAboutData*>( KGlobal::mainComponent().aboutData() )->setProgramName( ki18n( programName.toUtf8() ) );
}

void AgentBasePrivate::itemAdded( const Akonadi::Item &item, const Akonadi::Collection &collection )
{
  if ( mObserver != 0 ) {
    mObserver->itemAdded( item, collection );
  }
}

void AgentBasePrivate::itemChanged( const Akonadi::Item &item, const QSet<QByteArray> &partIdentifiers )
{
  if ( mObserver != 0 ) {
    mObserver->itemChanged( item, partIdentifiers );
  }
}

void AgentBasePrivate::itemMoved( const Akonadi::Item &item, const Akonadi::Collection &source, const Akonadi::Collection &dest )
{
  AgentBase::ObserverV2 *observer2 = dynamic_cast<AgentBase::ObserverV2*>( mObserver );
  if ( mObserver ) {
    // inter-resource moves, requires we know which resources the source and destination are in though
    if ( !source.resource().isEmpty() && !dest.resource().isEmpty() ) {
      if ( source.resource() != dest.resource() ) {
        if ( source.resource() == q_ptr->identifier() ) { // moved away from us
          Akonadi::Item i( item );
          i.setParentCollection( source );
          mObserver->itemRemoved( i );
        } else if ( dest.resource() == q_ptr->identifier() ) { // moved to us
          mObserver->itemAdded( item, dest );
        } else if ( observer2 ) {
          observer2->itemMoved( item, source, dest );
        } else {
          // not for us, not sure if we should get here at all
          changeProcessed();
        }
        return;
      }
    }
    // intra-resource move
    if ( observer2 ) {
      observer2->itemMoved( item, source, dest );
    } else {
      // ### we cannot just call itemRemoved here as this will already trigger changeProcessed()
      // so, just itemAdded() is good enough as no resource can have implemented intra-resource moves anyway
      // without using ObserverV2
      mObserver->itemAdded( item, dest );
      // mObserver->itemRemoved( item );
    }
  }
}

void AgentBasePrivate::itemRemoved( const Akonadi::Item &item )
{
  if ( mObserver != 0 ) {
    mObserver->itemRemoved( item );
  }
}

void AgentBasePrivate::itemLinked( const Akonadi::Item &item, const Akonadi::Collection &collection )
{
  AgentBase::ObserverV2 *observer2 = dynamic_cast<AgentBase::ObserverV2*>( mObserver );
  if ( observer2 ) {
    observer2->itemLinked( item, collection );
  } else {
    changeProcessed();
  }
}

void AgentBasePrivate::itemUnlinked( const Akonadi::Item &item, const Akonadi::Collection &collection )
{
  AgentBase::ObserverV2 *observer2 = dynamic_cast<AgentBase::ObserverV2*>( mObserver );
  if ( observer2 ) {
    observer2->itemUnlinked( item, collection );
  } else {
    changeProcessed();
  }
}

void AgentBasePrivate::itemsFlagsChanged( const Akonadi::Item::List &items, const QSet<QByteArray> &addedFlags, const QSet<QByteArray> &removedFlags )
{
  AgentBase::ObserverV3 *observer3 = dynamic_cast<AgentBase::ObserverV3*>( mObserver );
  if ( observer3 ) {
    observer3->itemsFlagsChanged( items, addedFlags, removedFlags );
  } else {
    Q_ASSERT_X( false, Q_FUNC_INFO, "Batch slots must never be called when ObserverV3 is not available" );
  }
}

void AgentBasePrivate::itemsMoved( const Akonadi::Item::List &items, const Akonadi::Collection &source, const Akonadi::Collection &destination )
{
  AgentBase::ObserverV3 *observer3 = dynamic_cast<AgentBase::ObserverV3*>( mObserver );
  if ( observer3 ) {
    observer3->itemsMoved( items, source, destination );
  } else {
    Q_ASSERT_X( false, Q_FUNC_INFO, "Batch slots must never be called when ObserverV3 is not available" );
  }
}

void AgentBasePrivate::itemsRemoved( const Akonadi::Item::List &items )
{
  AgentBase::ObserverV3 *observer3 = dynamic_cast<AgentBase::ObserverV3*>( mObserver );
  if ( observer3 ) {
    observer3->itemsRemoved( items );
  } else {
    Q_ASSERT_X( false, Q_FUNC_INFO, "Batch slots must never be called when ObserverV3 is not available" );
  }
}

void AgentBasePrivate::itemsLinked( const Akonadi::Item::List &items, const Akonadi::Collection &collection )
{
  if ( !mObserver ) {
    changeProcessed();
    return;
  }

  AgentBase::ObserverV3 *observer3 = dynamic_cast<AgentBase::ObserverV3*>( mObserver );
  if ( observer3 ) {
    observer3->itemsLinked( items, collection );
  } else {
    Q_ASSERT_X( false, Q_FUNC_INFO, "Batch slots must never be called when ObserverV3 is not available" );
  }
}

void AgentBasePrivate::itemsUnlinked( const Akonadi::Item::List &items, const Akonadi::Collection &collection )
{
  if ( !mObserver ) {
    return;
  }

  AgentBase::ObserverV3 *observer3 = dynamic_cast<AgentBase::ObserverV3*>( mObserver );
  if ( observer3 ) {
    observer3->itemsUnlinked( items, collection );
  } else {
    Q_ASSERT_X( false, Q_FUNC_INFO, "Batch slots must never be called when ObserverV3 is not available" );
  }
}

void AgentBasePrivate::tagAdded( const Akonadi::Tag &tag )
{
  if ( !mObserver ) {
    return;
  }

  AgentBase::ObserverV4 *observer4 = dynamic_cast<AgentBase::ObserverV4*>( mObserver );
  if ( observer4 ) {
    observer4->tagAdded( tag );
  } else {
    changeProcessed();
  }
}

void AgentBasePrivate::tagChanged( const Akonadi::Tag &tag )
{
  if ( !mObserver ) {
    return;
  }

  AgentBase::ObserverV4 *observer4 = dynamic_cast<AgentBase::ObserverV4*>( mObserver );
  if ( observer4 ) {
    observer4->tagChanged( tag );
  } else {
    changeProcessed();
  }
}

void AgentBasePrivate::tagRemoved( const Akonadi::Tag &tag )
{
  if ( !mObserver ) {
    return;
  }

  AgentBase::ObserverV4 *observer4 = dynamic_cast<AgentBase::ObserverV4*>( mObserver );
  if ( observer4 ) {
    observer4->tagRemoved( tag );;
  } else {
    changeProcessed();
  }
}

void AgentBasePrivate::itemsTagsChanged( const Akonadi::Item::List &items, const QSet<Akonadi::Tag> &addedTags, const QSet<Akonadi::Tag> &removedTags )
{
  if ( !mObserver ) {
    return;
  }

  AgentBase::ObserverV4 *observer4 = dynamic_cast<AgentBase::ObserverV4*>( mObserver );
  if ( observer4 ) {
    observer4->itemsTagsChanged( items, addedTags, removedTags );
  } else {
    changeProcessed();
  }
}

void AgentBasePrivate::collectionAdded( const Akonadi::Collection &collection, const Akonadi::Collection &parent )
{
  if ( mObserver != 0 ) {
    mObserver->collectionAdded( collection, parent );
  }
}

void AgentBasePrivate::collectionChanged( const Akonadi::Collection &collection )
{
  AgentBase::ObserverV2 *observer2 = dynamic_cast<AgentBase::ObserverV2*>( mObserver );
  if ( mObserver != 0 && observer2 == 0 ) { // For ObserverV2 we use the variant with the part identifiers
    mObserver->collectionChanged( collection );
  }
}

void AgentBasePrivate::collectionChanged( const Akonadi::Collection &collection, const QSet<QByteArray> &changedAttributes )
{
  AgentBase::ObserverV2 *observer2 = dynamic_cast<AgentBase::ObserverV2*>( mObserver );
  if ( observer2 != 0 ) {
    observer2->collectionChanged( collection, changedAttributes );
  }
}

void AgentBasePrivate::collectionMoved( const Akonadi::Collection &collection, const Akonadi::Collection &source, const Akonadi::Collection &dest )
{
  AgentBase::ObserverV2 *observer2 = dynamic_cast<AgentBase::ObserverV2*>( mObserver );
  if ( observer2 ) {
    observer2->collectionMoved( collection, source, dest );
  } else if ( mObserver ) {
    // ### we cannot just call collectionRemoved here as this will already trigger changeProcessed()
    // so, just collectionAdded() is good enough as no resource can have implemented intra-resource moves anyway
    // without using ObserverV2
    mObserver->collectionAdded( collection, dest );
  } else {
    changeProcessed();
  }
}

void AgentBasePrivate::collectionRemoved( const Akonadi::Collection &collection )
{
  if ( mObserver != 0 ) {
    mObserver->collectionRemoved( collection );
  }
}

void AgentBasePrivate::collectionSubscribed( const Akonadi::Collection &collection, const Akonadi::Collection &parent )
{
  Q_UNUSED( collection );
  Q_UNUSED( parent );
  changeProcessed();
}

void AgentBasePrivate::collectionUnsubscribed( const Akonadi::Collection &collection )
{
  Q_UNUSED( collection );
  changeProcessed();
}

void AgentBasePrivate::changeProcessed()
{
  mChangeRecorder->changeProcessed();
  QTimer::singleShot( 0, mChangeRecorder, SLOT(replayNext()) );
}

void AgentBasePrivate::slotStatus( int status, const QString &message )
{
  mStatusMessage = message;
  mStatusCode = 0;

  switch ( status ) {
    case AgentBase::Idle:
      if ( mStatusMessage.isEmpty() ) {
        mStatusMessage = defaultReadyMessage();
      }

      mStatusCode = 0;
      break;
    case AgentBase::Running:
      if ( mStatusMessage.isEmpty() ) {
        mStatusMessage = defaultSyncingMessage();
      }

      mStatusCode = 1;
      break;
    case AgentBase::Broken:
      if ( mStatusMessage.isEmpty() ) {
        mStatusMessage = defaultErrorMessage();
      }

      mStatusCode = 2;
      break;

    case AgentBase::NotConfigured:
      if ( mStatusMessage.isEmpty() ) {
        mStatusMessage = defaultUnconfiguredMessage();
      }

      mStatusCode = 3;
      break;

    default:
      Q_ASSERT( !"Unknown status passed" );
      break;
  }
}

void AgentBasePrivate::slotPercent( int progress )
{
  mProgress = progress;
}

void AgentBasePrivate::slotWarning( const QString& message )
{
  mTracer->warning( QString::fromLatin1( "AgentBase(%1)" ).arg( mId ), message );
}

void AgentBasePrivate::slotError( const QString& message )
{
  mTracer->error( QString::fromLatin1( "AgentBase(%1)" ).arg( mId ), message );
}

void AgentBasePrivate::slotNetworkStatusChange( Solid::Networking::Status stat )
{
  Q_UNUSED( stat );
  Q_Q( AgentBase );
  q->setOnlineInternal( mDesiredOnlineState );
}

void AgentBasePrivate::slotResumedFromSuspend()
{
  if ( mNeedsNetwork ) {
    slotNetworkStatusChange( Solid::Networking::status() );
  }
}

void AgentBasePrivate::slotTemporaryOfflineTimeout()
{
  Q_Q(AgentBase);
  q->setOnlineInternal(true);
}

QString AgentBasePrivate::dumpNotificationListToString() const
{
  return mChangeRecorder->dumpNotificationListToString();
}

void AgentBasePrivate::dumpMemoryInfo() const
{
  // Send it to stdout, so we can debug user problems.
  // since you have to explicitely call this
  // it won't flood users with release builds.
  QTextStream stream( stdout );
  stream << dumpMemoryInfoToString();
}

QString AgentBasePrivate::dumpMemoryInfoToString() const
{
  // man mallinfo for more info
  QString str;
#if defined __GLIBC__
  struct mallinfo mi;
  mi = mallinfo();
  QTextStream stream( &str );
  stream
    << "Total non-mmapped bytes (arena):      " << mi.arena     << '\n'
    << "# of free chunks (ordblks):           " << mi.ordblks   << '\n'
    << "# of free fastbin blocks (smblks>:    " << mi.smblks    << '\n'
    << "# of mapped regions (hblks):          " << mi.hblks     << '\n'
    << "Bytes in mapped regions (hblkhd):     " << mi.hblkhd    << '\n'
    << "Max. total allocated space (usmblks): " << mi.usmblks   << '\n'
    << "Free bytes held in fastbins (fsmblks):" << mi.fsmblks   << '\n'
    << "Total allocated space (uordblks):     " << mi.uordblks  << '\n'
    << "Total free space (fordblks):          " << mi.fordblks  << '\n'
    << "Topmost releasable block (keepcost):  " << mi.keepcost  << '\n';
#else
  str = QLatin1String( "mallinfo() not supported" );
#endif
  return str;
}


AgentBase::AgentBase( const QString & id )
  : d_ptr( new AgentBasePrivate( this ) )
{
  sAgentBase = this;
  d_ptr->mId = id;
  d_ptr->init();
}

AgentBase::AgentBase( AgentBasePrivate* d, const QString &id ) :
    d_ptr( d )
{
  sAgentBase = this;
  d_ptr->mId = id;
  d_ptr->init();
}

AgentBase::~AgentBase()
{
  delete d_ptr;
}

QString AgentBase::parseArguments( int argc, char **argv )
{
  QString identifier;
  if ( argc < 3 ) {
    kDebug() << "Not enough arguments passed...";
    exit( 1 );
  }

  for ( int i = 1; i < argc - 1; ++i ) {
    if ( QLatin1String( argv[ i ] ) == QLatin1String( "--identifier" ) ) {
      identifier = QLatin1String( argv[ i + 1 ] );
    }
  }

  if ( identifier.isEmpty() ) {
    kDebug() << "Identifier argument missing";
    exit( 1 );
  }

  const QFileInfo fi( QString::fromLocal8Bit( argv[0] ) );
  // strip off full path and possible .exe suffix
  const QByteArray catalog = fi.baseName().toLatin1();

  KCmdLineArgs::init( argc, argv, ServerManager::addNamespace( identifier ).toLatin1(), catalog, ki18n( "Akonadi Agent" ), KDEPIMLIBS_VERSION,
                      ki18n( "Akonadi Agent" ) );

  KCmdLineOptions options;
  options.add( "identifier <argument>", ki18n( "Agent identifier" ) );
  KCmdLineArgs::addCmdLineOptions( options );

  return identifier;
}

// @endcond

int AgentBase::init( AgentBase *r )
{
  QApplication::setQuitOnLastWindowClosed( false );
  KGlobal::locale()->insertCatalog( QLatin1String( "libakonadi" ) );
  int rv = kapp->exec();
  delete r;
  return rv;
}

int AgentBase::status() const
{
  Q_D( const AgentBase );

  return d->mStatusCode;
}

QString AgentBase::statusMessage() const
{
  Q_D( const AgentBase );

  return d->mStatusMessage;
}

int AgentBase::progress() const
{
  Q_D( const AgentBase );

  return d->mProgress;
}

QString AgentBase::progressMessage() const
{
  Q_D( const AgentBase );

  return d->mProgressMessage;
}

bool AgentBase::isOnline() const
{
  Q_D( const AgentBase );

  return d->mOnline;
}

void AgentBase::setNeedsNetwork( bool needsNetwork )
{
  Q_D( AgentBase );
  d->mNeedsNetwork = needsNetwork;

  if ( d->mNeedsNetwork ) {
    connect( Solid::Networking::notifier()
           , SIGNAL(statusChanged(Solid::Networking::Status))
           , this, SLOT(slotNetworkStatusChange(Solid::Networking::Status))
           , Qt::UniqueConnection );

  } else {
    disconnect( Solid::Networking::notifier(), 0, 0, 0 );
    setOnlineInternal( d->mDesiredOnlineState );
  }
}

void AgentBase::setOnline( bool state )
{
  Q_D( AgentBase );
  d->mDesiredOnlineState = state;
  d->mSettings->setValue( QLatin1String( "Agent/DesiredOnlineState" ), state );
  setOnlineInternal( state );
}

void AgentBase::setTemporaryOffline(int makeOnlineInSeconds)
{
  Q_D(AgentBase);

  // if not currently online, avoid bringing it online after the timeout
  if (!d->mOnline) {
      return;
  }

  setOnlineInternal(false);

  if (!d->mTemporaryOfflineTimer) {
      d->mTemporaryOfflineTimer = new QTimer(d);
      d->mTemporaryOfflineTimer->setSingleShot(true);
      connect(d->mTemporaryOfflineTimer, SIGNAL(timeout()), this, SLOT(slotTemporaryOfflineTimeout()));
  }
  d->mTemporaryOfflineTimer->setInterval(makeOnlineInSeconds * 1000);
  d->mTemporaryOfflineTimer->start();
}

void AgentBase::setOnlineInternal( bool state )
{
  Q_D( AgentBase );
  if (state && d->mNeedsNetwork) {
    const Solid::Networking::Status stat = Solid::Networking::status();
    if (stat != Solid::Networking::Unknown && stat != Solid::Networking::Connected) {
      //Don't go online if the resource needs network but there is none
      state = false;
    }
  }
  d->mOnline = state;

  if (d->mTemporaryOfflineTimer) {
      d->mTemporaryOfflineTimer->stop();
  }

  const QString newMessage = d->defaultReadyMessage();
  if ( d->mStatusMessage != newMessage && d->mStatusCode != AgentBase::Broken ) {
    emit status( d->mStatusCode, newMessage );
  }

  doSetOnline( state );
  emit onlineChanged( state );
}

void AgentBase::doSetOnline( bool online )
{
  Q_UNUSED( online );
}

void AgentBase::configure( WId windowId )
{
  Q_UNUSED( windowId );
  emit configurationDialogAccepted();
}

#ifdef Q_OS_WIN //krazy:exclude=cpp
void AgentBase::configure( qlonglong windowId )
{
  configure( reinterpret_cast<WId>( windowId ) );
}
#endif

WId AgentBase::winIdForDialogs() const
{
  const bool registered = DBusConnectionPool::threadConnection().interface()->isServiceRegistered( QLatin1String( "org.freedesktop.akonaditray" ) );
  if ( !registered ) {
    return 0;
  }

  QDBusInterface dbus( QLatin1String( "org.freedesktop.akonaditray" ), QLatin1String( "/Actions" ),
                       QLatin1String( "org.freedesktop.Akonadi.Tray" ) );
  const QDBusMessage reply = dbus.call( QLatin1String( "getWinId" ) );

  if ( reply.type() == QDBusMessage::ErrorMessage ) {
    return 0;
  }

  const WId winid = (WId)reply.arguments().at( 0 ).toLongLong();

  return winid;
}

void AgentBase::quit()
{
  Q_D( AgentBase );
  aboutToQuit();

  if ( d->mSettings ) {
    d->mChangeRecorder->setConfig( 0 );
    d->mSettings->sync();
  }

  KGlobal::deref();
}

void AgentBase::aboutToQuit()
{
}

void AgentBase::cleanup()
{
  Q_D( AgentBase );
  // prevent the monitor from picking up deletion signals for our own data if we are a resource
  // and thus avoid that we kill our own data as last act before our own death
  d->mChangeRecorder->blockSignals( true );

  aboutToQuit();

  const QString fileName = d->mSettings->fileName();

  /*
   * First destroy the settings object...
   */
  d->mChangeRecorder->setConfig( 0 );
  delete d->mSettings;
  d->mSettings = 0;

  /*
   * ... then remove the file from hd.
   */
  QFile::remove( fileName );

  /*
   * ... and remove the changes file from hd.
   */
  QFile::remove( fileName + QLatin1String( "_changes.dat" ) );

  /*
   * ... and also remove the agent configuration file if there is one.
   */
  QString configFile = KStandardDirs::locateLocal( "config", config()->name() );
  QFile::remove( configFile );

  KGlobal::deref();
}

void AgentBase::registerObserver( Observer *observer )
{
  // TODO in theory we should re-connect change recorder signals here that we disconnected previously
  d_ptr->mObserver = observer;

  const bool hasObserverV3 = (dynamic_cast<AgentBase::ObserverV3*>(d_ptr->mObserver) != 0);
  const bool hasObserverV4 = (dynamic_cast<AgentBase::ObserverV4*>(d_ptr->mObserver) != 0);

  disconnect( d_ptr->mChangeRecorder, SIGNAL(tagAdded(Akonadi::Tag)),
              d_ptr, SLOT(tagAdded(Akonadi::Tag)) );
  disconnect( d_ptr->mChangeRecorder, SIGNAL(tagChanged(Akonadi::Tag)),
              d_ptr, SLOT(tagChanged(Akonadi::Tag)) );
  disconnect( d_ptr->mChangeRecorder, SIGNAL(tagRemoved(Akonadi::Tag)),
              d_ptr, SLOT(tagRemoved(Akonadi::Tag)) );
  disconnect( d_ptr->mChangeRecorder, SIGNAL(itemsTagsChanged(Akonadi::Item::List,QSet<Akonadi::Tag>,QSet<Akonadi::Tag>)),
              d_ptr, SLOT(itemsTagsChanged(Akonadi::Item::List,QSet<Akonadi::Tag>,QSet<Akonadi::Tag>)) );
  disconnect( d_ptr->mChangeRecorder, SIGNAL(itemsFlagsChanged(Akonadi::Item::List,QSet<QByteArray>,QSet<QByteArray>)),
              d_ptr, SLOT(itemsFlagsChanged(Akonadi::Item::List,QSet<QByteArray>,QSet<QByteArray>)) );
  disconnect( d_ptr->mChangeRecorder, SIGNAL(itemsMoved(Akonadi::Item::List,Akonadi::Collection,Akonadi::Collection)),
              d_ptr, SLOT(itemsMoved(Akonadi::Item::List,Akonadi::Collection,Akonadi::Collection)) );
  disconnect( d_ptr->mChangeRecorder, SIGNAL(itemsRemoved(Akonadi::Item::List)),
              d_ptr, SLOT(itemsRemoved(Akonadi::Item::List)) );
  disconnect( d_ptr->mChangeRecorder, SIGNAL(itemsLinked(Akonadi::Item::List,Akonadi::Collection)),
              d_ptr, SLOT(itemsLinked(Akonadi::Item::List,Akonadi::Collection)) );
  disconnect( d_ptr->mChangeRecorder, SIGNAL(itemsUnlinked(Akonadi::Item::List,Akonadi::Collection)),
              d_ptr, SLOT(itemsUnlinked(Akonadi::Item::List,Akonadi::Collection)) );
  disconnect( d_ptr->mChangeRecorder, SIGNAL(itemMoved(Akonadi::Item,Akonadi::Collection,Akonadi::Collection)),
              d_ptr, SLOT(itemMoved(Akonadi::Item,Akonadi::Collection,Akonadi::Collection)) );
  disconnect( d_ptr->mChangeRecorder, SIGNAL(itemRemoved(Akonadi::Item)),
              d_ptr, SLOT(itemRemoved(Akonadi::Item)) );
  disconnect( d_ptr->mChangeRecorder, SIGNAL(itemLinked(Akonadi::Item,Akonadi::Collection)),
              d_ptr, SLOT(itemLinked(Akonadi::Item,Akonadi::Collection)) );
  disconnect( d_ptr->mChangeRecorder, SIGNAL(itemUnlinked(Akonadi::Item,Akonadi::Collection)),
              d_ptr, SLOT(itemUnlinked(Akonadi::Item,Akonadi::Collection)) );

  if ( hasObserverV4 ) {
    connect( d_ptr->mChangeRecorder, SIGNAL(tagAdded(Akonadi::Tag)),
             d_ptr, SLOT(tagAdded(Akonadi::Tag)) );
    connect( d_ptr->mChangeRecorder, SIGNAL(tagChanged(Akonadi::Tag)),
             d_ptr, SLOT(tagChanged(Akonadi::Tag)) );
    connect( d_ptr->mChangeRecorder, SIGNAL(tagRemoved(Akonadi::Tag)),
             d_ptr, SLOT(tagRemoved(Akonadi::Tag)) );
    connect( d_ptr->mChangeRecorder, SIGNAL(itemsTagsChanged(Akonadi::Item::List,QSet<Akonadi::Tag>,QSet<Akonadi::Tag>)),
             d_ptr, SLOT(itemsTagsChanged(Akonadi::Item::List,QSet<Akonadi::Tag>,QSet<Akonadi::Tag>)) );
  }

  if ( hasObserverV3 ) {
    connect( d_ptr->mChangeRecorder, SIGNAL(itemsFlagsChanged(Akonadi::Item::List,QSet<QByteArray>,QSet<QByteArray>)),
             d_ptr, SLOT(itemsFlagsChanged(Akonadi::Item::List,QSet<QByteArray>,QSet<QByteArray>)) );
    connect( d_ptr->mChangeRecorder, SIGNAL(itemsMoved(Akonadi::Item::List,Akonadi::Collection,Akonadi::Collection)),
             d_ptr, SLOT(itemsMoved(Akonadi::Item::List,Akonadi::Collection,Akonadi::Collection)) );
    connect( d_ptr->mChangeRecorder, SIGNAL(itemsRemoved(Akonadi::Item::List)),
             d_ptr, SLOT(itemsRemoved(Akonadi::Item::List)) );
    connect( d_ptr->mChangeRecorder, SIGNAL(itemsLinked(Akonadi::Item::List,Akonadi::Collection)),
             d_ptr, SLOT(itemsLinked(Akonadi::Item::List,Akonadi::Collection)) );
    connect( d_ptr->mChangeRecorder, SIGNAL(itemsUnlinked(Akonadi::Item::List,Akonadi::Collection)),
             d_ptr, SLOT(itemsUnlinked(Akonadi::Item::List,Akonadi::Collection)) );
  } else {
    // V2 - don't connect these if we have V3
    connect( d_ptr->mChangeRecorder, SIGNAL(itemMoved(Akonadi::Item,Akonadi::Collection,Akonadi::Collection)),
             d_ptr, SLOT(itemMoved(Akonadi::Item,Akonadi::Collection,Akonadi::Collection)) );
    connect( d_ptr->mChangeRecorder, SIGNAL(itemRemoved(Akonadi::Item)),
             d_ptr, SLOT(itemRemoved(Akonadi::Item)) );
    connect( d_ptr->mChangeRecorder, SIGNAL(itemLinked(Akonadi::Item,Akonadi::Collection)),
             d_ptr, SLOT(itemLinked(Akonadi::Item,Akonadi::Collection)) );
    connect( d_ptr->mChangeRecorder, SIGNAL(itemUnlinked(Akonadi::Item,Akonadi::Collection)),
             d_ptr, SLOT(itemUnlinked(Akonadi::Item,Akonadi::Collection)) );
  }
}

QString AgentBase::identifier() const
{
  return d_ptr->mId;
}

void AgentBase::setAgentName( const QString &name )
{
  Q_D( AgentBase );
  if ( name == d->mName ) {
    return;
  }

  // TODO: rename collection
  d->mName = name;

  if ( d->mName.isEmpty() || d->mName == d->mId ) {
    d->mSettings->remove( QLatin1String( "Resource/Name" ) );
    d->mSettings->remove( QLatin1String( "Agent/Name" ) );
  } else
    d->mSettings->setValue( QLatin1String( "Agent/Name" ), d->mName );

  d->mSettings->sync();

  d->setProgramName();

  emit agentNameChanged( d->mName );
}

QString AgentBase::agentName() const
{
  Q_D( const AgentBase );
  if ( d->mName.isEmpty() ) {
    return d->mId;
  } else {
    return d->mName;
  }
}

void AgentBase::changeProcessed()
{
  Q_D( AgentBase );
  d->changeProcessed();
}

ChangeRecorder * AgentBase::changeRecorder() const
{
  return d_ptr->mChangeRecorder;
}

KSharedConfigPtr AgentBase::config()
{
  if ( QCoreApplication::instance()->thread() == QThread::currentThread() ) {
    return KGlobal::config();
  } else {
    return componentData().config();
  }
}

void AgentBase::abort()
{
  emit abortRequested();
}

void AgentBase::reconfigure()
{
  emit reloadConfiguration();
}

extern QThreadStorage<KComponentData*> s_agentComponentDatas;

KComponentData AgentBase::componentData()
{
  if ( QThread::currentThread() == QCoreApplication::instance()->thread() ) {
    if ( s_agentComponentDatas.hasLocalData() ) {
      return *( s_agentComponentDatas.localData() );
    } else {
      return KGlobal::mainComponent();
    }
  }

  Q_ASSERT( s_agentComponentDatas.hasLocalData() );
  return *( s_agentComponentDatas.localData() );
}

#include "moc_agentbase.cpp"
#include "moc_agentbase_p.cpp"
