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

#include "Utils.h"
#include "Fc.h"
#include "FontInst.h"
#include "Misc.h"
#include "WritingSystems.h"
#include <KShell>
#include <QByteArray>
#include <QFile>
#include <QTextStream>
#include <fontconfig/fontconfig.h>

using namespace Qt::StringLiterals;

namespace KFI::Utils
{
bool isAAfm(const QString &fname)
{
    if (Misc::checkExt(QString::fromLocal8Bit(QFile::encodeName(fname)), u"afm"_s)) // CPD? Is this a necessary check?
    {
        QFile file(fname);

        if (file.open(QIODevice::ReadOnly)) {
            QTextStream stream(&file);
            QString line;

            for (int lc = 0; lc < 30 && !stream.atEnd(); ++lc) {
                line = stream.readLine();

                if (line.contains(u"StartFontMetrics")) {
                    file.close();
                    return true;
                }
            }

            file.close();
        }
    }

    return false;
}

bool isAPfm(const QString &fname)
{
    bool ok = false;

    // I know extension checking is bad, but Ghostscript's pf2afm requires the pfm file to
    // have the .pfm extension...
    QByteArray name(QFile::encodeName(fname));

    if (Misc::checkExt(QString::fromLocal8Bit(name), u"pfm"_s)) {
        //
        // OK, the extension matches, so perform a little contents checking...
        FILE *f = fopen(name.constData(), "r");

        if (f) {
            static const unsigned long constCopyrightLen = 60;
            static const unsigned long constTypeToExt = 49;
            static const unsigned long constExtToFname = 20;
            static const unsigned long constExtLen = 30;
            static const unsigned long constFontnameMin = 75;
            static const unsigned long constFontnameMax = 512;

            unsigned short version = 0, type = 0, extlen = 0;
            unsigned long length = 0, fontname = 0, fLength = 0;

            fseek(f, 0, SEEK_END);
            fLength = ftell(f);
            fseek(f, 0, SEEK_SET);

            if (2 == fread(&version, 1, 2, f) && // Read version
                4 == fread(&length, 1, 4, f) && // length...
                length == fLength && 0 == fseek(f, constCopyrightLen, SEEK_CUR) && // Skip copyright notice...
                2 == fread(&type, 1, 2, f) && 0 == fseek(f, constTypeToExt, SEEK_CUR) && 2 == fread(&extlen, 1, 2, f) && extlen == constExtLen
                && 0 == fseek(f, constExtToFname, SEEK_CUR) && 4 == fread(&fontname, 1, 4, f) && fontname > constFontnameMin && fontname < constFontnameMax) {
                ok = true;
            }
            fclose(f);
        }
    }

    return ok;
}

// This function is *only* used for the generation of AFMs from PFMs.
bool isAType1(const QString &fname)
{
    static const char constStr[] = "%!PS-AdobeFont-";
    static const unsigned int constStrLen = 15;
    static const unsigned int constPfbOffset = 6;
    static const unsigned int constPfbLen = constStrLen + constPfbOffset;

    QByteArray name(QFile::encodeName(fname));
    char buffer[constPfbLen];
    bool match = false;

    if (Misc::checkExt(QString::fromLocal8Bit(name), u"pfa"_s)) {
        FILE *f = fopen(name.constData(), "r");

        if (f) {
            if (constStrLen == fread(buffer, 1, constStrLen, f)) {
                match = 0 == memcmp(buffer, constStr, constStrLen);
            }
            fclose(f);
        }
    } else if (Misc::checkExt(QString::fromLocal8Bit(name), u"pfb"_s)) {
        static const char constPfbMarker = static_cast<char>(0x80);

        FILE *f = fopen(name.constData(), "r");

        if (f) {
            if (constPfbLen == fread(buffer, 1, constPfbLen, f)) {
                match = buffer[0] == constPfbMarker && 0 == memcmp(&buffer[constPfbOffset], constStr, constStrLen);
            }
            fclose(f);
        }
    }

    return match;
}

static QString getMatch(const QString &file, const char *extension)
{
    QString f(Misc::changeExt(file, QLatin1String(extension)));

    return Misc::fExists(f) ? f : QString();
}

void createAfm(const QString &file, EFileType type)
{
    bool pfm = FILE_PFM == type, type1 = FILE_SCALABLE == type && isAType1(file);

    if (type1 || pfm) {
        // pf2afm wants files with lowercase extension, so just check for lowercase!
        // -- when a font is installed, the extension is converted to lowercase anyway...
        QString afm = getMatch(file, "afm");

        if (afm.isEmpty()) // No point creating if AFM already exists!
        {
            QString pfm, t1;

            if (type1) // Its a Type1, so look for existing PFM
            {
                pfm = getMatch(file, "pfm");
                t1 = file;
            } else // Its a PFM, so look for existing Type1
            {
                t1 = getMatch(file, "pfa");
                if (t1.isEmpty()) {
                    t1 = getMatch(file, "pfb");
                }
                pfm = file;
            }

            if (!t1.isEmpty() && !pfm.isEmpty()) // Do we have both Type1 and PFM?
            {
                QString rootName(t1.left(t1.length() - 4));
                Misc::doCmd(u"pf2afm"_s, KShell::quoteArg(rootName)); // pf2afm wants name without extension...
                Misc::setFilePerms(QFile::encodeName(rootName + ".afm"_L1));
            }
        }
    }
}

EFileType check(const QString &file, Family &fam)
{
    if (isAAfm(file)) {
        return FILE_AFM;
    } else if (isAPfm(file)) {
        return FILE_PFM;
    } else {
        // Check that file is a font via FreeType...
        int count = 0;
        FcPattern *pat = FcFreeTypeQuery((const FcChar8 *)(QFile::encodeName(file).constData()), 0, nullptr, &count);

        if (pat) {
            FcBool scalable;
            QString family, foundry;
            quint32 style;
            int index;
            qulonglong ws;
            EFileType type = (FcResultMatch != FcPatternGetBool(pat, FC_SCALABLE, 0, &scalable) || !scalable) ? FILE_BITMAP : FILE_SCALABLE;

            FC::getDetails(pat, family, style, index, foundry);
            ws = WritingSystems::instance()->get(pat);
            FcPatternDestroy(pat);
            Style st(style, scalable, ws);
            st.add(File(file, foundry, index));
            fam = Family(family);
            fam.add(st);
            return type;
        }
    }
    return FILE_INVALID;
}

}
