/*  This file is part of the KDE libraries
 *  Copyright 2008  David Faure  <faure@kde.org>
 *
 *  This library is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU Lesser General Public License as published by
 *  the Free Software Foundation; either version 2 of the License or ( at
 *  your option ) version 3 or, at the discretion of KDE e.V. ( which shall
 *  act as a proxy as in section 14 of the GPLv3 ), 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 Lesser 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 "kmimeassociations.h"
#include <kservice.h>
#include <kconfiggroup.h>
#include <kconfig.h>
#include <QDebug>
#include <qstandardpaths.h>
#include <qmimedatabase.h>

KMimeAssociations::KMimeAssociations(KOfferHash &offerHash)
    : m_offerHash(offerHash)
{
}

/*

The goal of this class is to parse mimeapps.list files, which are used to
let users configure the application-mimetype associations.

Example file:

[Added Associations]
text/plain=kate.desktop;

[Removed Associations]
text/plain=gnome-gedit.desktop;gnu-emacs.desktop;

*/

bool KMimeAssociations::parseAllMimeAppsList()
{
    // Using the "merged view" from KConfig is not enough since we -add- at every level, we don't replace.
    const QStringList mimeappsFiles = QStandardPaths::locateAll(QStandardPaths::ApplicationsLocation, "mimeapps.list");
    if (mimeappsFiles.isEmpty()) {
        return false;
    }

    int basePreference = 1000; // start high :)
    QListIterator<QString> mimeappsIter(mimeappsFiles);
    mimeappsIter.toBack();
    while (mimeappsIter.hasPrevious()) { // global first, then local.
        const QString mimeappsFile = mimeappsIter.previous();
        //qDebug() << "Parsing" << mimeappsFile;
        parseMimeAppsList(mimeappsFile, basePreference);
        basePreference += 50;
    }
    return true;
}

void KMimeAssociations::parseMimeAppsList(const QString &file, int basePreference)
{
    KConfig profile(file, KConfig::SimpleConfig);
    parseAddedAssociations(KConfigGroup(&profile, "Added Associations"), file, basePreference);
    parseRemovedAssociations(KConfigGroup(&profile, "Removed Associations"), file);

    // KDE extension for parts and plugins, see settings/filetypes/mimetypedata.cpp
    parseAddedAssociations(KConfigGroup(&profile, "Added KDE Service Associations"), file, basePreference);
    parseRemovedAssociations(KConfigGroup(&profile, "Removed KDE Service Associations"), file);
}

void KMimeAssociations::parseAddedAssociations(const KConfigGroup &group, const QString &file, int basePreference)
{
    Q_UNUSED(file) // except in debug statements
    QMimeDatabase db;
    Q_FOREACH (const QString &mimeName, group.keyList()) {
        const QStringList services = group.readXdgListEntry(mimeName);
        const QString resolvedMimeName = db.mimeTypeForName(mimeName).name();
        if (resolvedMimeName.isEmpty()) {
            //qDebug() << file << "specifies unknown mimeType" << mimeName << "in" << group.name();
        } else {
            int pref = basePreference;
            Q_FOREACH (const QString &service, services) {
                KService::Ptr pService = KService::serviceByStorageId(service);
                if (!pService) {
                    //qDebug() << file << "specifies unknown service" << service << "in" << group.name();
                } else {
                    //qDebug() << "adding mime" << resolvedMimeName << "to service" << pService->entryPath() << "pref=" << pref;
                    m_offerHash.addServiceOffer(resolvedMimeName, KServiceOffer(pService, pref, 0, pService->allowAsDefault()));
                    --pref;
                }
            }
        }
    }
}

void KMimeAssociations::parseRemovedAssociations(const KConfigGroup &group, const QString &file)
{
    Q_UNUSED(file) // except in debug statements
    Q_FOREACH (const QString &mime, group.keyList()) {
        const QStringList services = group.readXdgListEntry(mime);
        Q_FOREACH (const QString &service, services) {
            KService::Ptr pService = KService::serviceByStorageId(service);
            if (!pService) {
                //qDebug() << file << "specifies unknown service" << service << "in" << group.name();
            } else {
                //qDebug() << "removing mime" << mime << "from service" << pService.data() << pService->entryPath();
                m_offerHash.removeServiceOffer(mime, pService);
            }
        }
    }
}

void KOfferHash::addServiceOffer(const QString &serviceType, const KServiceOffer &offer)
{
    KService::Ptr service = offer.service();
    //qDebug() << "Adding" << service->entryPath() << "to" << serviceType << offer.preference();
    ServiceTypeOffersData &data = m_serviceTypeData[serviceType]; // find or create
    QList<KServiceOffer> &offers = data.offers;
    QSet<KService::Ptr> &offerSet = data.offerSet;
    if (!offerSet.contains(service)) {
        offers.append(offer);
        offerSet.insert(service);
    } else {
        //qDebug() << service->entryPath() << "already in" << serviceType;
        // This happens when mimeapps.list mentions a service (to make it preferred)
        // Update initialPreference to qMax(existing offer, new offer)
        QMutableListIterator<KServiceOffer> sfit(data.offers);
        while (sfit.hasNext()) {
            if (sfit.next().service() == service) { // we can compare KService::Ptrs because they are from the memory hash
                sfit.value().setPreference(qMax(sfit.value().preference(), offer.preference()));
            }
        }
    }
}

void KOfferHash::removeServiceOffer(const QString &serviceType, KService::Ptr service)
{
    ServiceTypeOffersData &data = m_serviceTypeData[serviceType]; // find or create
    data.removedOffers.insert(service);
    data.offerSet.remove(service);
    QMutableListIterator<KServiceOffer> sfit(data.offers);
    while (sfit.hasNext()) {
        if (sfit.next().service()->storageId() == service->storageId()) {
            sfit.remove();
        }
    }
}

bool KOfferHash::hasRemovedOffer(const QString &serviceType, KService::Ptr service) const
{
    QHash<QString, ServiceTypeOffersData>::const_iterator it = m_serviceTypeData.find(serviceType);
    if (it != m_serviceTypeData.end()) {
        return (*it).removedOffers.contains(service);
    }
    return false;
}
