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

#include "Misc.h"
#include "config-paths.h"
#include <QByteArray>
#include <QDir>
#include <QList>
#include <QMap>
#include <QProcess>
#include <QSet>
#include <QStandardPaths>
#include <QTemporaryFile>
#include <QTextStream>
#include <QUrlQuery>
#include <ctype.h>

using namespace Qt::StringLiterals;

namespace KFI
{
namespace Misc
{
QString prettyUrl(const QUrl &url)
{
    QString u(url.url());

    u.replace("%20"_L1, " "_L1);
    return u;
}

QString dirSyntax(const QString &d)
{
    if (!d.isEmpty()) {
        QString ds(d);

        ds.replace("//"_L1, "/"_L1);

        int slashPos(ds.lastIndexOf(u'/'));

        if (slashPos != (((int)ds.length()) - 1)) {
            ds.append(u'/');
        }

        return ds;
    }

    return d;
}

QString fileSyntax(const QString &d)
{
    if (!d.isEmpty()) {
        QString ds(d);

        ds.replace("//"_L1, "/"_L1);

        int slashPos(ds.lastIndexOf(u'/'));

        if (slashPos == (((int)ds.length()) - 1)) {
            ds.remove(slashPos, 1);
        }
        return ds;
    }

    return d;
}

QString getDir(const QString &f)
{
    QString d(f);

    int slashPos(d.lastIndexOf(u'/'));

    if (slashPos != -1) {
        d.remove(slashPos + 1, d.length());
    }

    return dirSyntax(d);
}

QString getFile(const QString &f)
{
    QString d(f);

    int slashPos = d.lastIndexOf(u'/');

    if (slashPos != -1) {
        d.remove(0, slashPos + 1);
    }

    return d;
}

bool createDir(const QString &dir)
{
    if (!QDir().mkpath(dir)) {
        return false;
    }
    //
    // Clear any umask before setting dir perms
    mode_t oldMask(umask(0000));
    const QByteArray d = QFile::encodeName(dir);
    ::chmod(d.constData(), DIR_PERMS);
    // Reset umask
    ::umask(oldMask);
    return true;
}

void setFilePerms(const QByteArray &f)
{
    //
    // Clear any umask before setting file perms
    mode_t oldMask(umask(0000));
    ::chmod(f.constData(), FILE_PERMS);
    // Reset umask
    ::umask(oldMask);
}

bool doCmd(const QString &cmd, const QString &p1, const QString &p2, const QString &p3)
{
    QStringList args;

    if (!p1.isEmpty()) {
        args << p1;
    }
    if (!p2.isEmpty()) {
        args << p2;
    }
    if (!p3.isEmpty()) {
        args << p3;
    }

    return 0 == QProcess::execute(cmd, args);
}

QString changeExt(const QString &f, QLatin1String newExt)
{
    QString newStr(f);
    int dotPos(newStr.lastIndexOf(u'.'));

    if (-1 == dotPos) {
        newStr += u'.' + newExt;
    } else {
        newStr.remove(dotPos + 1, newStr.length());
        newStr += newExt;
    }
    return newStr;
}

//
// Get a list of files associated with a file, e.g.:
//
//    File: /home/a/courier.pfa
//
//    Associated: /home/a/courier.afm /home/a/courier.pfm
//
void getAssociatedFiles(const QString &path, QStringList &files, bool afmAndPfm)
{
    QString ext(path);
    int dotPos(ext.lastIndexOf(u'.'));
    bool check(false);

    if (-1 == dotPos) { // Hmm, no extension - check anyway...
        check = true;
    } else // Cool, got an extension - see if it is a Type1 font...
    {
        ext = ext.mid(dotPos + 1);
        check = 0 == ext.compare(u"pfa", Qt::CaseInsensitive) || 0 == ext.compare(u"pfb", Qt::CaseInsensitive);
    }

    if (check) {
        constexpr const char *afm[] = {"afm", "AFM", "Afm", nullptr}, *pfm[] = {"pfm", "PFM", "Pfm", nullptr};
        bool gotAfm(false);
        int e;

        for (e = 0; afm[e]; ++e) {
            QString statFile(changeExt(path, QLatin1String(afm[e])));

            if (fExists(statFile)) {
                files.append(statFile);
                gotAfm = true;
                break;
            }
        }

        if (afmAndPfm || !gotAfm) {
            for (e = 0; pfm[e]; ++e) {
                QString statFile(changeExt(path, QLatin1String(pfm[e])));

                if (fExists(statFile)) {
                    files.append(statFile);
                    break;
                }
            }
        }
    }
}

time_t getTimeStamp(const QString &item)
{
    QT_STATBUF info;

    return !item.isEmpty() && 0 == QT_LSTAT(QFile::encodeName(item).constData(), &info) ? info.st_mtime : 0;
}

bool check(const QString &path, bool file, bool checkW)
{
    QT_STATBUF info;
    QByteArray pathC(QFile::encodeName(path));

    return 0 == QT_LSTAT(pathC.constData(), &info) && (file ? (S_ISREG(info.st_mode) || S_ISLNK(info.st_mode)) : S_ISDIR(info.st_mode))
        && (!checkW || 0 == ::access(pathC.constData(), W_OK));
}

QString getFolder(const QString &defaultDir, const QString &root, QStringList &dirs)
{
    if (dirs.contains(defaultDir)) {
        return defaultDir;
    } else {
        QStringList::const_iterator it, end = dirs.constEnd();
        bool found = false;

        for (it = dirs.constBegin(); it != end && !found; ++it) {
            if (0 == (*it).indexOf(root)) {
                return *it;
            }
        }
    }

    return defaultDir;
}

bool checkExt(const QString &fname, QStringView ext)
{
    QString extension(u'.' + ext);

    return fname.length() > extension.length() ? 0 == fname.mid(fname.length() - extension.length()).compare(extension, Qt::CaseInsensitive) : false;
}

bool isBitmap(const QString &str)
{
    return checkExt(str, u"pcf") || checkExt(str, u"bdf") || checkExt(str, u"pcf.gz") || checkExt(str, u"bdf.gz");
}

bool isMetrics(const QString &str)
{
    return checkExt(str, u"afm") || checkExt(str, u"pfm");
}

int getIntQueryVal(const QUrl &url, const char *key, int defVal)
{
    QUrlQuery query(url);
    QString item(query.queryItemValue(QString::fromLocal8Bit(key)));
    int val(defVal);

    if (!item.isNull()) {
        val = item.toInt();
    }

    return val;
}

bool printable(const QString &mime)
{
    return mime == u"font/otf" || mime == u"font/ttf" || mime == u"application/x-font-ttf" || mime == u"application/x-font-otf"
        || mime == u"application/x-font-type1";
}

uint qHash(const KFI::Misc::TFont &key)
{
    // return qHash(QString(key.family+'%'+QString().setNum(key.styleInfo, 16)));
    const QChar *p = key.family.unicode();
    int n = key.family.size();
    uint h = 0, g;

    h = (h << 4) + key.styleInfo;
    if ((g = (h & 0xf0000000)) != 0) {
        h ^= g >> 23;
    }
    h &= ~g;

    while (n--) {
        h = (h << 4) + (*p++).unicode();
        if ((g = (h & 0xf0000000)) != 0) {
            h ^= g >> 23;
        }
        h &= ~g;
    }
    return h;
}

// Taken from qdom.cpp
QString encodeText(const QString &str)
{
    QString retval(str);
    int len = retval.length(), i = 0;

    while (i < len) {
        const QChar ati(retval.at(i));

        if (ati == QLatin1Char('<')) {
            retval.replace(i, 1, QLatin1String("&lt;"));
            len += 3;
            i += 4;
        } else if (ati == QLatin1Char('"')) {
            retval.replace(i, 1, QLatin1String("&quot;"));
            len += 5;
            i += 6;
        } else if (ati == QLatin1Char('&')) {
            retval.replace(i, 1, QLatin1String("&amp;"));
            len += 4;
            i += 5;
        } else if (ati == QLatin1Char('>') && i >= 2 && retval[i - 1] == QLatin1Char(']') && retval[i - 2] == QLatin1Char(']')) {
            retval.replace(i, 1, QLatin1String("&gt;"));
            len += 3;
            i += 4;
        } else {
            if (ati.isPrint()) {
                ++i;
            } else {
                // We have to use a character reference to get it through.
                const ushort codepoint(ati.unicode());
                const QString replacement(QLatin1String("&#x") + QString::number(codepoint, 16) + QLatin1Char(';'));
                retval.replace(i, 1, replacement);
                i += replacement.length();
                len += replacement.length() - 1;
            }
        }
    }

    return retval;
}

QString contractHome(QString path)
{
    if (!path.isEmpty() && u'/' == path[0]) {
        QString home(QDir::homePath());

        if (path.startsWith(home)) {
            int len = home.length();

            if (len > 1 && (path.length() == len || path[len] == u'/')) {
                return path.replace(0, len, u'~');
            }
        }
    }

    return path;
}

QString expandHome(QString path)
{
    if (!path.isEmpty() && u'~' == path[0]) {
        return 1 == path.length() ? QDir::homePath() : path.replace(0, 1, QDir::homePath());
    }

    return path;
}

QMap<QString, QString> getFontFileMap(const QSet<QString> &files)
{
    QMap<QString, QString> map;
    QSet<QString>::ConstIterator it = files.constBegin(), end = files.constEnd();
    QMap<QString, QSet<QString>> fontsFiles;

    for (; it != end; ++it) {
        fontsFiles[unhide(getFile(*it))].insert(getDir(*it));
    }

    QMap<QString, QSet<QString>>::ConstIterator fIt(fontsFiles.constBegin()), fEnd(fontsFiles.constEnd());

    for (; fIt != fEnd; ++fIt) {
        if (fIt.value().count() > 1) {
            QList<QString> orig(fIt.value().count()), modified(fIt.value().count());
            QSet<QString>::ConstIterator oIt(fIt.value().constBegin());
            bool good = true;
            int count = fIt.value().count();

            for (int i = 0; i < count && good; ++i, ++oIt) {
                orig[i] = modified[i] = *oIt;
            }

            while (good) {
                int end = modified[0].indexOf(u'/', 1);

                if (-1 != end) {
                    QString dir = modified[0].left(end);

                    for (int i = 1; i < count && good; ++i) {
                        if (0 != modified[i].indexOf(dir)) {
                            good = false;
                        }
                    }
                    if (good) {
                        for (int i = 0; i < count && good; ++i) {
                            modified[i].remove(0, dir.length());
                        }
                    }
                } else {
                    good = false;
                }
            }
            for (int i = 0; i < count; ++i) {
                map[getDir(modified[i]).mid(1) + fIt.key()] = fExists(orig[i] + fIt.key()) ? QString(orig[i] + fIt.key()) : QString(orig[i] + hide(fIt.key()));
            }
        } else { // Only 1 entry! :-)
            map[unhide(fIt.key())] =
                fExists((*fIt.value().begin()) + fIt.key()) ? QString((*fIt.value().begin()) + fIt.key()) : QString((*fIt.value().begin()) + hide(fIt.key()));
        }
    }

    return map;
}

QString modifyName(const QString &fname)
{
    static constexpr char16_t constSymbols[] = {u'-', u' ', u':', u';', u'/', u'~', 0};

    QString rv(fname);

    for (int s = 0; constSymbols[s]; ++s) {
        rv.replace(constSymbols[s], u'_');
    }

    int dotPos(rv.lastIndexOf(u'.'));

    return -1 == dotPos ? rv : rv.left(dotPos + 1) + rv.mid(dotPos + 1).toLower();
}

QString app(const QString &name, const char *path)
{
    static QMap<QString, QString> apps;

    if (!apps.contains(name)) {
        QStringList installPaths;
        if (qstrcmp(path, "libexec") == 0) {
            installPaths.append(QString::fromLocal8Bit(KFONTINST_LIBEXEC_DIR));
        }
        apps[name] = QStandardPaths::findExecutable(name, installPaths);
    }
    return apps[name];
}

} // Misc::

} // KFI::
