/*  This file is part of the KDE libraries
 *  Copyright (C) 1999-2006 David Faure <faure@kde.org>
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Library General Public
 *  License version 2 as published by the Free Software Foundation;
 *
 *  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 "kservicefactory_p.h"
#include "ksycoca.h"
#include "ksycocatype.h"
#include "ksycocadict_p.h"
#include "kservice.h"
#include "servicesdebug.h"
#include "servicesdebug.h"
#include <QDir>
#include <QFile>

extern int servicesDebugArea();

KServiceFactory::KServiceFactory(KSycoca *db)
    : KSycocaFactory(KST_KServiceFactory, db),
      m_nameDict(nullptr),
      m_relNameDict(nullptr),
      m_menuIdDict(nullptr)
{
    m_offerListOffset = 0;
    m_nameDictOffset = 0;
    m_relNameDictOffset = 0;
    m_menuIdDictOffset = 0;
    if (!sycoca()->isBuilding()) {
        QDataStream *str = stream();
        Q_ASSERT(str);
        if (!str) {
            return;
        }
        // Read Header
        qint32 i;
        (*str) >> i;
        m_nameDictOffset = i;
        (*str) >> i;
        m_relNameDictOffset = i;
        (*str) >> i;
        m_offerListOffset = i;
        (*str) >> i;
        m_menuIdDictOffset = i;

        const qint64 saveOffset = str->device()->pos();
        // Init index tables
        m_nameDict = new KSycocaDict(str, m_nameDictOffset);
        // Init index tables
        m_relNameDict = new KSycocaDict(str, m_relNameDictOffset);
        // Init index tables
        m_menuIdDict = new KSycocaDict(str, m_menuIdDictOffset);
        str->device()->seek(saveOffset);
    }
}

KServiceFactory::~KServiceFactory()
{
    delete m_nameDict;
    delete m_relNameDict;
    delete m_menuIdDict;
}

KService::Ptr KServiceFactory::findServiceByName(const QString &_name)
{
    if (!sycocaDict()) {
        return KService::Ptr();    // Error!
    }

    // Warning : this assumes we're NOT building a database
    // But since findServiceByName isn't called in that case...
    // [ see KServiceTypeFactory for how to do it if needed ]

    int offset = sycocaDict()->find_string(_name);
    if (!offset) {
        return KService::Ptr();    // Not found
    }

    KService::Ptr newService(createEntry(offset));

    // Check whether the dictionary was right.
    if (newService && (newService->name() != _name)) {
        // No it wasn't...
        return KService::Ptr();
    }
    return newService;
}

KService::Ptr KServiceFactory::findServiceByDesktopName(const QString &_name)
{
    if (!m_nameDict) {
        return KService::Ptr();    // Error!
    }

    // Warning : this assumes we're NOT building a database
    // KBuildServiceFactory reimplements it for the case where we are building one

    int offset = m_nameDict->find_string(_name);
    if (!offset) {
        return KService::Ptr();    // Not found
    }

    KService::Ptr newService(createEntry(offset));

    // Check whether the dictionary was right.
    if (newService && (newService->desktopEntryName() != _name)) {
        // No it wasn't...
        return KService::Ptr();
    }
    return newService;
}

KService::Ptr KServiceFactory::findServiceByDesktopPath(const QString &_name)
{
    if (!m_relNameDict) {
        return KService::Ptr();    // Error!
    }

    // Warning : this assumes we're NOT building a database
    // KBuildServiceFactory reimplements it for the case where we are building one

    int offset = m_relNameDict->find_string(_name);
    if (!offset) {
        //qCDebug(SERVICES) << "findServiceByDesktopPath:" << _name << "not found";
        return KService::Ptr(); // Not found
    }

    KService::Ptr newService(createEntry(offset));
    if (!newService) {
        qCDebug(SERVICES) << "createEntry failed!";
    }
    // Check whether the dictionary was right
    // It's ok that it's wrong, for the case where we're looking up an unknown service,
    // and the hash value gave us another one.
    if (newService && (newService->entryPath() != _name)) {
        // No it wasn't...
        return KService::Ptr();
    }
    return newService;
}

KService::Ptr KServiceFactory::findServiceByMenuId(const QString &_menuId)
{
    if (!m_menuIdDict) {
        return KService::Ptr();    // Error!
    }

    // Warning : this assumes we're NOT building a database
    // KBuildServiceFactory reimplements it for the case where we are building one

    int offset = m_menuIdDict->find_string(_menuId);
    if (!offset) {
        return KService::Ptr();    // Not found
    }

    KService::Ptr newService(createEntry(offset));

    // Check whether the dictionary was right.
    if (newService && (newService->menuId() != _menuId)) {
        // No it wasn't...
        return KService::Ptr();
    }
    return newService;
}

KService::Ptr KServiceFactory::findServiceByStorageId(const QString &_storageId)
{
    KService::Ptr service = findServiceByMenuId(_storageId);
    if (service) {
        return service;
    }

    service = findServiceByDesktopPath(_storageId);
    if (service) {
        return service;
    }

    if (!QDir::isRelativePath(_storageId) && QFile::exists(_storageId)) {
        return KService::Ptr(new KService(_storageId));
    }

    QString tmp = _storageId;
    tmp = tmp.mid(tmp.lastIndexOf(QLatin1Char('/')) + 1); // Strip dir

    if (tmp.endsWith(QLatin1String(".desktop"))) {
        tmp.truncate(tmp.length() - 8);
    }

    if (tmp.endsWith(QLatin1String(".kdelnk"))) {
        tmp.truncate(tmp.length() - 7);
    }

    service = findServiceByDesktopName(tmp);

    return service;
}

KService *KServiceFactory::createEntry(int offset) const
{
    KSycocaType type;
    QDataStream *str = sycoca()->findEntry(offset, type);
    if (type != KST_KService) {
        qCWarning(SERVICES) << "KServiceFactory: unexpected object entry in KSycoca database (type=" << int(type) << ")";
        return nullptr;
    }
    KService *newEntry = new KService(*str, offset);
    if (!newEntry->isValid()) {
        qCWarning(SERVICES) << "KServiceFactory: corrupt object in KSycoca database!";
        delete newEntry;
        newEntry = nullptr;
    }
    return newEntry;
}

KService::List KServiceFactory::allServices()
{
    KService::List result;
    const KSycocaEntry::List list = allEntries();
    KSycocaEntry::List::const_iterator it = list.begin();
    const KSycocaEntry::List::const_iterator end = list.end();
    for (; it != end; ++it) {
        const KSycocaEntry::Ptr entry = *it;
        if (entry->isType(KST_KService)) {
            KService::Ptr service(static_cast<KService*>(entry.data()));
            result.append(service);
        }
    }
    return result;
}

QStringList KServiceFactory::resourceDirs()
{
    return KSycocaFactory::allDirectories(QStringLiteral("kservices5"))
           + KSycocaFactory::allDirectories(QStringLiteral("applications"));
}

QList<KServiceOffer> KServiceFactory::offers(int serviceTypeOffset, int serviceOffersOffset)
{
    QList<KServiceOffer> list;

    // Jump to the offer list
    QDataStream *str = stream();
    str->device()->seek(m_offerListOffset + serviceOffersOffset);

    qint32 aServiceTypeOffset, aServiceOffset, initialPreference, mimeTypeInheritanceLevel;
    while (true) {
        (*str) >> aServiceTypeOffset;
        if (aServiceTypeOffset) {
            (*str) >> aServiceOffset;
            (*str) >> initialPreference;
            (*str) >> mimeTypeInheritanceLevel;
            if (aServiceTypeOffset == serviceTypeOffset) {
                // Save stream position !
                const qint64 savedPos = str->device()->pos();
                // Create Service
                KService *serv = createEntry(aServiceOffset);
                if (serv) {
                    KService::Ptr servPtr(serv);
                    list.append(KServiceOffer(servPtr, initialPreference, mimeTypeInheritanceLevel, servPtr->allowAsDefault()));
                }
                // Restore position
                str->device()->seek(savedPos);
            } else {
                break;    // too far
            }
        } else {
            break;    // 0 => end of list
        }
    }
    return list;
}

KService::List KServiceFactory::serviceOffers(int serviceTypeOffset, int serviceOffersOffset)
{
    KService::List list;

    // Jump to the offer list
    QDataStream *str = stream();
    str->device()->seek(m_offerListOffset + serviceOffersOffset);

    qint32 aServiceTypeOffset, aServiceOffset, initialPreference, mimeTypeInheritanceLevel;
    while (true) {
        (*str) >> aServiceTypeOffset;
        if (aServiceTypeOffset) {
            (*str) >> aServiceOffset;
            (*str) >> initialPreference;
            (*str) >> mimeTypeInheritanceLevel;
            if (aServiceTypeOffset == serviceTypeOffset) {
                // Save stream position !
                const qint64 savedPos = str->device()->pos();
                // Create service
                KService *serv = createEntry(aServiceOffset);
                if (serv) {
                    list.append(KService::Ptr(serv));
                }
                // Restore position
                str->device()->seek(savedPos);
            } else {
                break;    // too far
            }
        } else {
            break;    // 0 => end of list
        }
    }
    return list;
}

bool KServiceFactory::hasOffer(int serviceTypeOffset, int serviceOffersOffset, int testedServiceOffset)
{
    // Save stream position
    QDataStream *str = stream();
    const qint64 savedPos = str->device()->pos();

    // Jump to the offer list
    str->device()->seek(m_offerListOffset + serviceOffersOffset);
    bool found = false;
    qint32 aServiceTypeOffset, aServiceOffset, initialPreference, mimeTypeInheritanceLevel;
    while (!found) {
        (*str) >> aServiceTypeOffset;
        if (aServiceTypeOffset) {
            (*str) >> aServiceOffset;
            (*str) >> initialPreference;
            (*str) >> mimeTypeInheritanceLevel;
            if (aServiceTypeOffset == serviceTypeOffset) {
                if (aServiceOffset == testedServiceOffset) {
                    found = true;
                }
            } else {
                break;    // too far
            }
        } else {
            break;    // 0 => end of list
        }
    }
    // Restore position
    str->device()->seek(savedPos);
    return found;
}

void KServiceFactory::virtual_hook(int id, void *data)
{
    KSycocaFactory::virtual_hook(id, data);
}

