/*
    Copyright (c) 2009 Constantin Berzan <exit3219@gmail.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 "specialcollections.h"

#include "specialcollections_p.h"
#include "specialcollectionattribute_p.h"

#include "akonadi/agentinstance.h"
#include "akonadi/agentmanager.h"
#include "akonadi/collectionmodifyjob.h"
#include "akonadi/collectionfetchjob.h"
#include "akonadi/monitor.h"

#include <KDebug>
#include <kcoreconfigskeleton.h>

#include <QtCore/QHash>
#include <QtCore/QObject>
#include <QtCore/QSet>
#include <akonadi/collectionfetchscope.h>

using namespace Akonadi;

SpecialCollectionsPrivate::SpecialCollectionsPrivate(KCoreConfigSkeleton *settings, SpecialCollections *qq)
    : q(qq)
    , mSettings(settings)
    , mBatchMode(false)
{
    mMonitor = new Monitor(q);
    mMonitor->fetchCollectionStatistics(true);

    /// In order to know if items are added or deleted
    /// from one of our specialcollection folders,
    /// we have to watch all mail item add/move/delete notifications
    /// and check for the parent to see if it is one we care about
    QObject::connect(mMonitor, SIGNAL(collectionRemoved(Akonadi::Collection)),
                     q, SLOT(collectionRemoved(Akonadi::Collection)));
    QObject::connect(mMonitor, SIGNAL(collectionStatisticsChanged(Akonadi::Collection::Id,Akonadi::CollectionStatistics)),
                     q, SLOT(collectionStatisticsChanged(Akonadi::Collection::Id,Akonadi::CollectionStatistics)));
}

SpecialCollectionsPrivate::~SpecialCollectionsPrivate()
{
}

QString SpecialCollectionsPrivate::defaultResourceId() const
{
    if (mDefaultResourceId.isEmpty()) {
        mSettings->readConfig();
        const KConfigSkeletonItem *item = mSettings->findItem(QLatin1String("DefaultResourceId"));
        Q_ASSERT(item);

        mDefaultResourceId = item->property().toString();
    }
    return mDefaultResourceId;
}

void SpecialCollectionsPrivate::emitChanged(const QString &resourceId)
{
    if (mBatchMode) {
        mToEmitChangedFor.insert(resourceId);
    } else {
        kDebug() << "Emitting changed for" << resourceId;
        const AgentInstance agentInstance = AgentManager::self()->instance(resourceId);
        emit q->collectionsChanged(agentInstance);
        // first compare with local value then with config value (which also updates the local value)
        if (resourceId == mDefaultResourceId || resourceId == defaultResourceId()) {
            kDebug() << "Emitting defaultFoldersChanged.";
            emit q->defaultCollectionsChanged();
        }
    }
}

void SpecialCollectionsPrivate::collectionRemoved(const Collection &collection)
{
    kDebug() << "Collection" << collection.id() << "resource" << collection.resource();
    if (mFoldersForResource.contains(collection.resource())) {

        // Retrieve the list of special folders for the resource the collection belongs to
        QHash<QByteArray, Collection> &folders = mFoldersForResource[collection.resource()];
        {
            QMutableHashIterator<QByteArray, Collection> it(folders);
            while (it.hasNext()) {
                it.next();
                if (it.value() == collection) {
                    // The collection to be removed is a special folder
                    it.remove();
                    emitChanged(collection.resource());
                }
            }
        }

        if (folders.isEmpty()) {
            // This resource has no more folders, so remove it completely.
            mFoldersForResource.remove(collection.resource());
        }
    }
}

void SpecialCollectionsPrivate::collectionStatisticsChanged(Akonadi::Collection::Id collectionId, const Akonadi::CollectionStatistics &statistics)
{
    // need to get the name of the collection in order to be able to check if we are storing it,
    // but we have the id from the monitor, so fetch the name.
    Akonadi::CollectionFetchJob *fetchJob = new Akonadi::CollectionFetchJob(Collection(collectionId), Akonadi::CollectionFetchJob::Base);
    fetchJob->fetchScope().setAncestorRetrieval(Akonadi::CollectionFetchScope::None);
    fetchJob->setProperty("statistics", QVariant::fromValue(statistics));

    q->connect(fetchJob, SIGNAL(result(KJob*)), q, SLOT(collectionFetchJobFinished(KJob*)));
}

void SpecialCollectionsPrivate::collectionFetchJobFinished(KJob *job)
{
    if (job->error()) {
        kWarning() << "Error fetching collection to get name from id for statistics updating in specialcollections!";
        return;
    }

    const Akonadi::CollectionFetchJob *fetchJob = qobject_cast<Akonadi::CollectionFetchJob *>(job);

    Q_ASSERT(fetchJob->collections().size() > 0);
    const Akonadi::Collection collection = fetchJob->collections().first();
    const Akonadi::CollectionStatistics statistics = fetchJob->property("statistics").value<Akonadi::CollectionStatistics>();

    mFoldersForResource[collection.resource()][collection.name().toUtf8()].setStatistics(statistics);
}

void SpecialCollectionsPrivate::beginBatchRegister()
{
    Q_ASSERT(!mBatchMode);
    mBatchMode = true;
    Q_ASSERT(mToEmitChangedFor.isEmpty());
}

void SpecialCollectionsPrivate::endBatchRegister()
{
    Q_ASSERT(mBatchMode);
    mBatchMode = false;

    foreach (const QString &resourceId, mToEmitChangedFor) {
        emitChanged(resourceId);
    }

    mToEmitChangedFor.clear();
}

void SpecialCollectionsPrivate::forgetFoldersForResource(const QString &resourceId)
{
    if (mFoldersForResource.contains(resourceId)) {
        const Collection::List folders = mFoldersForResource[resourceId].values();

        foreach (const Collection &collection, folders) {
            mMonitor->setCollectionMonitored(collection, false);
        }

        mFoldersForResource.remove(resourceId);
        emitChanged(resourceId);
    }
}

AgentInstance SpecialCollectionsPrivate::defaultResource() const
{
    const QString identifier = defaultResourceId();
    return AgentManager::self()->instance(identifier);
}

SpecialCollections::SpecialCollections(KCoreConfigSkeleton *settings, QObject *parent)
    : QObject(parent)
    , d(new SpecialCollectionsPrivate(settings, this))
{
}

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

bool SpecialCollections::hasCollection(const QByteArray &type, const AgentInstance &instance) const
{
    return d->mFoldersForResource.value(instance.identifier()).contains(type);
}

Akonadi::Collection SpecialCollections::collection(const QByteArray &type, const AgentInstance &instance) const
{
    return d->mFoldersForResource.value(instance.identifier()).value(type);
}

void SpecialCollections::setSpecialCollectionType(const QByteArray &type, const Akonadi::Collection &collection)
{
    if (!collection.hasAttribute<SpecialCollectionAttribute>() || collection.attribute<SpecialCollectionAttribute>()->collectionType() != type) {
        Collection attributeCollection(collection);
        SpecialCollectionAttribute *attribute = attributeCollection.attribute<SpecialCollectionAttribute>(Collection::AddIfMissing);
        attribute->setCollectionType(type);
        new CollectionModifyJob(attributeCollection);
    }
}

void SpecialCollections::unsetSpecialCollection(const Akonadi::Collection &collection)
{
    if (collection.hasAttribute<SpecialCollectionAttribute>()) {
        Collection attributeCollection(collection);
        attributeCollection.removeAttribute<SpecialCollectionAttribute>();
        new CollectionModifyJob(attributeCollection);
    }
}

bool SpecialCollections::unregisterCollection(const Collection &collection)
{
    if (!collection.isValid()) {
        kWarning() << "Invalid collection.";
        return false;
    }

    const QString &resourceId = collection.resource();
    if (resourceId.isEmpty()) {
        kWarning() << "Collection has empty resourceId.";
        return false;
    }

    unsetSpecialCollection(collection);

    d->mMonitor->setCollectionMonitored(collection, false);
    //Remove from list of collection
    d->collectionRemoved(collection);
    return true;
}

bool SpecialCollections::registerCollection(const QByteArray &type, const Collection &collection)
{
    if (!collection.isValid()) {
        kWarning() << "Invalid collection.";
        return false;
    }

    const QString &resourceId = collection.resource();
    if (resourceId.isEmpty()) {
        kWarning() << "Collection has empty resourceId.";
        return false;
    }

    setSpecialCollectionType(type, collection);

    const Collection oldCollection = d->mFoldersForResource.value(resourceId).value(type);
    if (oldCollection != collection) {
        if (oldCollection.isValid()) {
            d->mMonitor->setCollectionMonitored(oldCollection, false);
        }
        d->mMonitor->setCollectionMonitored(collection, true);
        d->mFoldersForResource[resourceId].insert(type, collection);
        d->emitChanged(resourceId);
    }

    return true;
}

bool SpecialCollections::hasDefaultCollection(const QByteArray &type) const
{
    return hasCollection(type, d->defaultResource());
}

Akonadi::Collection SpecialCollections::defaultCollection(const QByteArray &type) const
{
    return collection(type, d->defaultResource());
}

#include "moc_specialcollections.cpp"
