/*
    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"
