/*
    KHelper - KDE Font Installer

    SPDX-FileCopyrightText: 2003-2010 Craig Drummond <craig@kde.org>
    SPDX-License-Identifier: GPL-2.0-or-later
*/

#include "Helper.h"
#include "Folder.h"
#include "FontInst.h"
#include "Misc.h"
#include "Utils.h"
#include <KAuth/HelperSupport>
#include <QCoreApplication>
#include <QDebug>
#include <QDomDocument>
#include <kio/global.h>
#include <signal.h>
#include <sys/errno.h>
#include <sys/types.h>
#include <unistd.h>

KAUTH_HELPER_MAIN("org.kde.fontinst", KFI::Helper)

using namespace Qt::StringLiterals;

namespace KFI
{
static Folder theFontFolder;

typedef void (*SignalHandler)(int);

static void registerSignalHandler(SignalHandler handler)
{
    if (!handler) {
        handler = SIG_DFL;
    }

    sigset_t mask;
    sigemptyset(&mask);

#ifdef SIGSEGV
    signal(SIGSEGV, handler);
    sigaddset(&mask, SIGSEGV);
#endif
#ifdef SIGFPE
    signal(SIGFPE, handler);
    sigaddset(&mask, SIGFPE);
#endif
#ifdef SIGILL
    signal(SIGILL, handler);
    sigaddset(&mask, SIGILL);
#endif
#ifdef SIGABRT
    signal(SIGABRT, handler);
    sigaddset(&mask, SIGABRT);
#endif

    sigprocmask(SIG_UNBLOCK, &mask, nullptr);
}

static void signalHander(int)
{
    static bool inHandler = false;

    if (!inHandler) {
        inHandler = true;
        theFontFolder.saveDisabled();
        inHandler = false;
    }
}

static void cleanup()
{
    theFontFolder.saveDisabled();
}

Helper::Helper()
{
    registerSignalHandler(signalHander);
    qAddPostRoutine(cleanup);
    theFontFolder.init(true, true);
    theFontFolder.loadDisabled();
}

Helper::~Helper()
{
    theFontFolder.saveDisabled();
}

ActionReply Helper::manage(const QVariantMap &args)
{
    int result = KIO::ERR_UNSUPPORTED_ACTION;
    QString method = args[u"method"_s].toString();

    // qDebug() << method;

    if (u"install" == method) {
        result = install(args);
    } else if (u"uninstall" == method) {
        result = uninstall(args);
    } else if (u"move" == method) {
        result = move(args);
    } else if (u"toggle" == method) {
        result = toggle(args);
    } else if (u"removeFile" == method) {
        result = removeFile(args);
    } else if (u"reconfigure" == method) {
        result = reconfigure();
    } else if (u"saveDisabled" == method) {
        result = saveDisabled();
    } else {
        // qDebug() << "Uknown action";
    }

    if (FontInst::STATUS_OK == result) {
        return ActionReply::SuccessReply();
    }

    ActionReply reply(ActionReply::HelperErrorType);
    reply.setErrorCode(static_cast<KAuth::ActionReply::Error>(result));
    return reply;
}

int Helper::install(const QVariantMap &args)
{
    QString file(args[u"file"_s].toString()), name(args[u"name"_s].toString()), destFolder(args[u"destFolder"_s].toString());
    bool createAfm(args[u"createAfm"_s].toBool());
    int type(args[u"type"_s].toInt());

    // qDebug() << file << destFolder << name << createAfm;

    int result = FontInst::STATUS_OK;

    if (!Misc::dExists(destFolder)) {
        result = Misc::createDir(destFolder) ? (int)FontInst::STATUS_OK : (int)KIO::ERR_WRITE_ACCESS_DENIED;
    }

    if (FontInst::STATUS_OK == result) {
        result = QFile::copy(file, destFolder + name) ? (int)FontInst::STATUS_OK : (int)KIO::ERR_WRITE_ACCESS_DENIED;
    }

    if (FontInst::STATUS_OK == result) {
        Misc::setFilePerms(QFile::encodeName(destFolder + name));
        if ((Utils::FILE_SCALABLE == type || Utils::FILE_PFM == type) && createAfm) {
            Utils::createAfm(destFolder + name, (KFI::Utils::EFileType)type);
        }
        theFontFolder.addModifiedDir(destFolder);
    }

    return result;
}

int Helper::uninstall(const QVariantMap &args)
{
    QStringList files(args[u"files"_s].toStringList());
    int result = checkWriteAction(files);

    if (FontInst::STATUS_OK == result) {
        QStringList::ConstIterator it(files.constBegin()), end(files.constEnd());

        for (; it != end; ++it) {
            if (!Misc::fExists(*it) || QFile::remove(*it)) {
                // Also remove any AFM or PFM files...
                QStringList other;
                Misc::getAssociatedFiles(*it, other);
                QStringList::ConstIterator oit(other.constBegin()), oend(other.constEnd());
                for (; oit != oend; ++oit) {
                    QFile::remove(*oit);
                }

                theFontFolder.addModifiedDir(Misc::getDir(*it));
            }
        }
    }

    return result;
}

static bool renameFontFile(const QString &from, const QString &to, int uid = -1, int gid = -1)
{
    if (!QFile::rename(from, to)) {
        return false;
    }

    QByteArray dest(QFile::encodeName(to));

    Misc::setFilePerms(dest);
    if (-1 != uid && -1 != gid) {
        ::chown(dest.data(), uid, gid);
    }
    return true;
}

int Helper::move(const QVariantMap &args)
{
    QStringList files(args[u"files"_s].toStringList());
    bool toSystem(args[u"toSystem"_s].toBool());
    QString dest(args[u"dest"_s].toString());
    int uid(args[u"uid"_s].toInt()), gid(args[u"gid"_s].toInt());

    // qDebug() << files << dest << toSystem;

    int result = FontInst::STATUS_OK;
    QStringList::ConstIterator it(files.constBegin()), end(files.constEnd());

    // Cant move hidden fonts - need to unhide first.
    for (; it != end && FontInst::STATUS_OK == result; ++it) {
        if (Misc::isHidden(Misc::getFile(*it))) {
            result = KIO::ERR_UNSUPPORTED_ACTION;
        }
    }

    if (FontInst::STATUS_OK == result) {
        QHash<QString, QString> movedFiles;
        int toUid = toSystem ? getuid() : uid, fromUid = toSystem ? uid : getuid(), toGid = toSystem ? getgid() : gid, fromGid = toSystem ? gid : getgid();

        // Move fonts!
        for (it = files.constBegin(); it != end && FontInst::STATUS_OK == result; ++it) {
            QString name(Misc::modifyName(Misc::getFile(*it))), destFolder(Misc::getDestFolder(dest, name));

            if (!Misc::dExists(destFolder)) {
                result = Misc::createDir(destFolder) ? (int)FontInst::STATUS_OK : (int)KIO::ERR_WRITE_ACCESS_DENIED;
                if (FontInst::STATUS_OK == result) {
                    ::chown(QFile::encodeName(destFolder).data(), toUid, toGid);
                }
            }

            if (renameFontFile(*it, destFolder + name, toUid, toGid)) {
                movedFiles[*it] = destFolder + name;
                // Now try to move an associated AFM or PFM files...
                QStringList assoc;

                Misc::getAssociatedFiles(*it, assoc);

                QStringList::ConstIterator ait(assoc.constBegin()), aend(assoc.constEnd());

                for (; ait != aend && FontInst::STATUS_OK == result; ++ait) {
                    name = Misc::getFile(*ait);
                    if (renameFontFile(*ait, destFolder + name, toUid, toGid)) {
                        movedFiles[*ait] = destFolder + name;
                    } else {
                        result = KIO::ERR_WRITE_ACCESS_DENIED;
                    }
                }

                if (toSystem) {
                    theFontFolder.addModifiedDir(theFontFolder.location());
                }
            } else {
                result = KIO::ERR_WRITE_ACCESS_DENIED;
            }
        }

        if (FontInst::STATUS_OK != result) // un-move fonts!
        {
            QHash<QString, QString>::ConstIterator it(movedFiles.constBegin()), end(movedFiles.constEnd());

            for (; it != end; ++it) {
                renameFontFile(it.value(), it.key(), fromUid, fromGid);
            }
        }
    }
    return result;
}

int Helper::toggle(const QVariantMap &args)
{
    QDomDocument doc;
    doc.setContent(args[u"xml"_s].toString());
    Family font(doc.documentElement(), true);
    bool enable(args[u"enable"_s].toBool());

    // qDebug() << font.name() << enable;

    if (1 != font.styles().count()) {
        return KIO::ERR_WRITE_ACCESS_DENIED;
    }

    int result = FontInst::STATUS_OK;
    FileCont files((*font.styles().begin()).files()), toggledFiles;
    FileCont::ConstIterator it(files.constBegin()), end(files.constEnd());
    QHash<File, QString> movedFonts;
    QHash<QString, QString> movedAssoc;
    QSet<QString> modifiedDirs;
    // Move fonts!
    for (; it != end && FontInst::STATUS_OK == result; ++it) {
        QString to = Misc::getDir((*it).path()) + QString(enable ? Misc::unhide(Misc::getFile((*it).path())) : Misc::hide(Misc::getFile((*it).path())));

        if (to != (*it).path()) {
            // qDebug() << "MOVE:" << (*it).path() << " to " << to;
            if (renameFontFile((*it).path(), to)) {
                modifiedDirs.insert(Misc::getDir(enable ? to : (*it).path()));
                toggledFiles.insert(File(to, (*it).foundry(), (*it).index()));
                // Now try to move an associated AFM or PFM files...
                QStringList assoc;

                movedFonts[*it] = to;
                Misc::getAssociatedFiles((*it).path(), assoc);

                QStringList::ConstIterator ait(assoc.constBegin()), aend(assoc.constEnd());

                for (; ait != aend && FontInst::STATUS_OK == result; ++ait) {
                    to = Misc::getDir(*ait) + QString(enable ? Misc::unhide(Misc::getFile(*ait)) : Misc::hide(Misc::getFile(*ait)));

                    if (to != *ait) {
                        if (renameFontFile(*ait, to)) {
                            movedAssoc[*ait] = to;
                        } else {
                            result = KIO::ERR_WRITE_ACCESS_DENIED;
                        }
                    }
                }
            } else {
                result = KIO::ERR_WRITE_ACCESS_DENIED;
            }
        }
    }

    theFontFolder.addModifiedDirs(modifiedDirs);

    if (FontInst::STATUS_OK == result) {
        FamilyCont::ConstIterator f = theFontFolder.fonts().find(font);

        if (theFontFolder.fonts().end() == f) {
            f = theFontFolder.addFont(font);
        }

        StyleCont::ConstIterator st = (*f).styles().find(*font.styles().begin());

        if ((*f).styles().end() == st) {
            st = (*f).add(*font.styles().begin());
        }

        // This helper only needs to store list of disabled fonts,
        // for writing back to disk - therefore no need to store
        // list of enabled font files.
        FileCont empty;
        (*st).setFiles(enable ? empty : toggledFiles);
        if ((*st).files().isEmpty()) {
            (*f).remove(*st);
        }
        if ((*f).styles().isEmpty()) {
            theFontFolder.removeFont(*f);
        }
        theFontFolder.setDisabledDirty();
    } else {
        QHash<File, QString>::ConstIterator fit(movedFonts.constBegin()), fend(movedFonts.constEnd());
        QHash<QString, QString>::ConstIterator ait(movedAssoc.constBegin()), aend(movedAssoc.constEnd());

        for (; fit != fend; ++fit) {
            renameFontFile(fit.value(), fit.key().path());
        }
        for (; ait != aend; ++ait) {
            renameFontFile(ait.value(), ait.key());
        }
    }

    return result;
}

int Helper::removeFile(const QVariantMap &args)
{
    QString file(args[u"file"_s].toString());

    // qDebug() << file;

    QString dir(Misc::getDir(file));
    int result = Misc::fExists(file) ? QFile::remove(file) ? (int)FontInst::STATUS_OK : (int)KIO::ERR_WRITE_ACCESS_DENIED : (int)KIO::ERR_DOES_NOT_EXIST;

    if (FontInst::STATUS_OK == result) {
        theFontFolder.addModifiedDir(dir);
    }

    return result;
}

int Helper::reconfigure()
{
    saveDisabled();
    // qDebug() << theFontFolder.isModified();
    if (theFontFolder.isModified()) {
        theFontFolder.configure();
    }
    return FontInst::STATUS_OK;
}

int Helper::saveDisabled()
{
    // Load internally calls save!
    theFontFolder.loadDisabled();
    return FontInst::STATUS_OK;
}

int Helper::checkWriteAction(const QStringList &files)
{
    QStringList::ConstIterator it(files.constBegin()), end(files.constEnd());

    for (; it != end; ++it) {
        if (!Misc::dWritable(Misc::getDir(*it))) {
            return KIO::ERR_WRITE_ACCESS_DENIED;
        }
    }
    return FontInst::STATUS_OK;
}

}
