#include "kxftconfig.h"

#include <ctype.h>
#include <math.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>

#include <QByteArray>
#include <QDebug>
#include <QDir>
#include <QFile>
#include <QFileInfo>
#include <QStandardPaths>
#include <QX11Info>

#include <fontconfig/fontconfig.h>

using namespace std;

static int point2Pixel(double point)
{
    return (int)(((point * QX11Info::appDpiY()) / 72.0) + 0.5);
}

static int pixel2Point(double pixel)
{
    return (int)(((pixel * 72.0) / (double)QX11Info::appDpiY()) + 0.5);
}

static bool equal(double d1, double d2)
{
    return (fabs(d1 - d2) < 0.0001);
}

static QString dirSyntax(const QString &d)
{
    if (d.isNull()) {
        return d;
    }

    QString ds(d);
    ds.replace(QLatin1String("//"), QLatin1String("/"));
    if (!ds.endsWith(QLatin1Char('/'))) {
        ds += QLatin1Char('/');
    }

    return ds;
}

inline bool fExists(const QString &p)
{
    return QFileInfo(p).isFile();
}

inline bool dWritable(const QString &p)
{
    QFileInfo info(p);
    return info.isDir() && info.isWritable();
}

static QString getDir(const QString &path)
{
    QString str(path);

    const int slashPos = str.lastIndexOf(QLatin1Char('/'));
    if (slashPos != -1) {
        str.truncate(slashPos + 1);
    }

    return dirSyntax(str);
}

static QDateTime getTimeStamp(const QString &item)
{
    return QFileInfo(item).lastModified();
}

static QString getEntry(QDomElement element, const char *type, unsigned int numAttributes, ...)
{
    if (numAttributes == uint(element.attributes().length())) {
        va_list args;
        unsigned int arg;
        bool ok = true;

        va_start(args, numAttributes);

        for (arg = 0; arg < numAttributes && ok; ++arg) {
            const char *attr = va_arg(args, const char *);
            const char *val = va_arg(args, const char *);

            if (!attr || !val || val != element.attribute(attr)) {
                ok = false;
            }
        }

        va_end(args);

        if (ok) {
            QDomNode n = element.firstChild();

            if (!n.isNull()) {
                QDomElement e = n.toElement();

                if (!e.isNull() && type == e.tagName()) {
                    return e.text();
                }
            }
        }
    }

    return QString();
}

static KXftConfig::SubPixel::Type strToType(const char *str)
{
    if (0 == strcmp(str, "rgb")) {
        return KXftConfig::SubPixel::Rgb;
    } else if (0 == strcmp(str, "bgr")) {
        return KXftConfig::SubPixel::Bgr;
    } else if (0 == strcmp(str, "vrgb")) {
        return KXftConfig::SubPixel::Vrgb;
    } else if (0 == strcmp(str, "vbgr")) {
        return KXftConfig::SubPixel::Vbgr;
    } else if (0 == strcmp(str, "none")) {
        return KXftConfig::SubPixel::None;
    } else {
        return KXftConfig::SubPixel::NotSet;
    }
}

static KXftConfig::Hint::Style strToStyle(const char *str)
{
    if (0 == strcmp(str, "hintslight")) {
        return KXftConfig::Hint::Slight;
    } else if (0 == strcmp(str, "hintmedium")) {
        return KXftConfig::Hint::Medium;
    } else if (0 == strcmp(str, "hintfull")) {
        return KXftConfig::Hint::Full;
    } else {
        return KXftConfig::Hint::None;
    }
}

KXftConfig::KXftConfig()
    : m_doc("fontconfig")
    , m_file(getConfigFile())
{
    qDebug() << "Using fontconfig file:" << m_file;
    reset();
}

KXftConfig::~KXftConfig()
{
}

//
// Obtain location of config file to use.
QString KXftConfig::getConfigFile()
{
    FcStrList *list = FcConfigGetConfigFiles(FcConfigGetCurrent());
    QStringList localFiles;
    FcChar8 *file;
    QString home(dirSyntax(QDir::homePath()));

    m_globalFiles.clear();

    while ((file = FcStrListNext(list))) {
        QString f((const char *)file);

        if (fExists(f) && 0 == f.indexOf(home)) {
            localFiles.append(f);
        } else {
            m_globalFiles.append(f);
        }
    }
    FcStrListDone(list);

    //
    // Go through list of localFiles, looking for the preferred one...
    if (!localFiles.isEmpty()) {
        for (const QString &file : qAsConst(localFiles)) {
            if (file.endsWith(QLatin1String("/fonts.conf")) || file.endsWith(QLatin1String("/.fonts.conf"))) {
                return file;
            }
        }
        return localFiles.front(); // Just return the 1st one...
    } else { // Hmmm... no known localFiles?
        if (FcGetVersion() >= 21000) {
            const QString targetPath(QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation) + QLatin1Char('/') + QLatin1String("fontconfig"));
            QDir target(targetPath);
            if (!target.exists()) {
                target.mkpath(targetPath);
            }
            return targetPath + QLatin1String("/fonts.conf");
        } else {
            return home + QLatin1String("/.fonts.conf");
        }
    }
}

bool KXftConfig::reset()
{
    m_madeChanges = false;
    m_hint.reset();
    m_hinting.reset();
    m_excludeRange.reset();
    m_excludePixelRange.reset();
    m_subPixel.reset();
    m_antiAliasing.reset();
    m_antiAliasingHasLocalConfig = false;
    m_subPixelHasLocalConfig = false;
    m_hintHasLocalConfig = false;

    bool ok = false;
    std::for_each(m_globalFiles.cbegin(), m_globalFiles.cend(), [this, &ok](const QString &file) {
        ok |= parseConfigFile(file);
    });

    AntiAliasing globalAntialiasing;
    globalAntialiasing.state = m_antiAliasing.state;
    SubPixel globalSubPixel;
    globalSubPixel.type = m_subPixel.type;
    Hint globalHint;
    globalHint.style = m_hint.style;
    Exclude globalExcludeRange;
    globalExcludeRange.from = m_excludeRange.from;
    globalExcludeRange.to = m_excludePixelRange.to;
    Exclude globalExcludePixelRange;
    globalExcludePixelRange.from = m_excludePixelRange.from;
    globalExcludePixelRange.to = m_excludePixelRange.to;
    Hinting globalHinting;
    globalHinting.set = m_hinting.set;

    m_antiAliasing.reset();
    m_subPixel.reset();
    m_hint.reset();
    m_hinting.reset();
    m_excludeRange.reset();
    m_excludePixelRange.reset();

    ok |= parseConfigFile(m_file);

    if (m_antiAliasing.node.isNull()) {
        m_antiAliasing = globalAntialiasing;
    } else {
        m_antiAliasingHasLocalConfig = true;
    }

    if (m_subPixel.node.isNull()) {
        m_subPixel = globalSubPixel;
    } else {
        m_subPixelHasLocalConfig = true;
    }

    if (m_hint.node.isNull()) {
        m_hint = globalHint;
    } else {
        m_hintHasLocalConfig = true;
    }

    if (m_hinting.node.isNull()) {
        m_hinting = globalHinting;
    }
    if (m_excludeRange.node.isNull()) {
        m_excludeRange = globalExcludeRange;
    }
    if (m_excludePixelRange.node.isNull()) {
        m_excludePixelRange = globalExcludePixelRange;
    }

    return ok;
}

bool KXftConfig::apply()
{
    bool ok = true;

    if (m_madeChanges) {
        //
        // Check if file has been written since we last read it. If it has, then re-read and add any
        // of our changes...
        if (fExists(m_file) && getTimeStamp(m_file) != m_time) {
            KXftConfig newConfig;

            newConfig.setExcludeRange(m_excludeRange.from, m_excludeRange.to);
            newConfig.setSubPixelType(m_subPixel.type);
            newConfig.setHintStyle(m_hint.style);
            newConfig.setAntiAliasing(m_antiAliasing.state);

            ok = newConfig.changed() ? newConfig.apply() : true;
            if (ok) {
                reset();
            } else {
                m_time = getTimeStamp(m_file);
            }
        } else {
            // Ensure these are always equal...
            m_excludePixelRange.from = (int)point2Pixel(m_excludeRange.from);
            m_excludePixelRange.to = (int)point2Pixel(m_excludeRange.to);

            FcAtomic *atomic = FcAtomicCreate((const unsigned char *)(QFile::encodeName(m_file).data()));

            ok = false;
            if (atomic) {
                if (FcAtomicLock(atomic)) {
                    FILE *f = fopen((char *)FcAtomicNewFile(atomic), "w");

                    if (f) {
                        applySubPixelType();
                        applyHintStyle();
                        applyAntiAliasing();
                        applyExcludeRange(false);
                        applyExcludeRange(true);

                        //
                        // Check document syntax...
                        static const char qtXmlHeader[] = "<?xml version = '1.0'?>";
                        static const char xmlHeader[] = "<?xml version=\"1.0\"?>";
                        static const char qtDocTypeLine[] = "<!DOCTYPE fontconfig>";
                        static const char docTypeLine[] =
                            "<!DOCTYPE fontconfig SYSTEM "
                            "\"fonts.dtd\">";

                        QString str(m_doc.toString());
                        int idx;

                        if (0 != str.indexOf("<?xml")) {
                            str.insert(0, xmlHeader);
                        } else if (0 == str.indexOf(qtXmlHeader)) {
                            str.replace(0, strlen(qtXmlHeader), xmlHeader);
                        }

                        if (-1 != (idx = str.indexOf(qtDocTypeLine))) {
                            str.replace(idx, strlen(qtDocTypeLine), docTypeLine);
                        }

                        //
                        // Write to file...
                        fputs(str.toUtf8(), f);
                        fclose(f);

                        if (FcAtomicReplaceOrig(atomic)) {
                            ok = true;
                            reset(); // Re-read contents..
                        } else {
                            FcAtomicDeleteNew(atomic);
                        }
                    }
                    FcAtomicUnlock(atomic);
                }
                FcAtomicDestroy(atomic);
            }
        }
    }

    return ok;
}

bool KXftConfig::subPixelTypeHasLocalConfig() const
{
    return m_subPixelHasLocalConfig;
}

bool KXftConfig::getSubPixelType(SubPixel::Type &type)
{
    type = m_subPixel.type;
    return SubPixel::None != m_subPixel.type;
}

void KXftConfig::setSubPixelType(SubPixel::Type type)
{
    if (type != m_subPixel.type) {
        m_subPixel.type = type;
        m_madeChanges = true;
    }
}

bool KXftConfig::hintStyleHasLocalConfig() const
{
    return m_hintHasLocalConfig;
}

bool KXftConfig::getHintStyle(Hint::Style &style)
{
    if (Hint::NotSet != m_hint.style && !m_hint.toBeRemoved) {
        style = m_hint.style;
        return true;
    } else {
        return false;
    }
}

void KXftConfig::setHintStyle(Hint::Style style)
{
    if ((Hint::NotSet == style && Hint::NotSet != m_hint.style && !m_hint.toBeRemoved)
        || (Hint::NotSet != style && (style != m_hint.style || m_hint.toBeRemoved))) {
        m_hint.toBeRemoved = (Hint::NotSet == style);
        m_hint.style = style;
        m_madeChanges = true;
    }

    if (Hint::NotSet != style) {
        setHinting(Hint::None != m_hint.style);
    }
}

void KXftConfig::setHinting(bool set)
{
    if (set != m_hinting.set) {
        m_hinting.set = set;
        m_madeChanges = true;
    }
}

bool KXftConfig::getExcludeRange(double &from, double &to)
{
    if (!equal(0, m_excludeRange.from) || !equal(0, m_excludeRange.to)) {
        from = m_excludeRange.from;
        to = m_excludeRange.to;
        return true;
    } else {
        return false;
    }
}

void KXftConfig::setExcludeRange(double from, double to)
{
    double f = from < to ? from : to, t = from < to ? to : from;

    if (!equal(f, m_excludeRange.from) || !equal(t, m_excludeRange.to)) {
        m_excludeRange.from = f;
        m_excludeRange.to = t;
        m_madeChanges = true;
    }
}

QString KXftConfig::description(SubPixel::Type t)
{
    return QString();
//    switch (t) {
//    default:
//    case SubPixel::NotSet:
//        return QObject::tr("use system subpixel setting", "Vendor default");
//    case SubPixel::None:
//        return QObject::tr("no subpixel rendering", "None");
//    case SubPixel::Rgb:
//        return QObject::tr("RGB");
//    case SubPixel::Bgr:
//        return QObject::tr("BGR");
//    case SubPixel::Vrgb:
//        return QObject::tr("Vertical RGB");
//    case SubPixel::Vbgr:
//        return QObject::tr("Vertical BGR");
//    }
}

const char *KXftConfig::toStr(SubPixel::Type t)
{
    switch (t) {
    default:
    case SubPixel::NotSet:
        return "";
    case SubPixel::None:
        return "none";
    case SubPixel::Rgb:
        return "rgb";
    case SubPixel::Bgr:
        return "bgr";
    case SubPixel::Vrgb:
        return "vrgb";
    case SubPixel::Vbgr:
        return "vbgr";
    }
}

QString KXftConfig::description(Hint::Style s)
{
    switch (s) {
    default:
    case Hint::NotSet:
        return QObject::tr("Vendor default");
    case Hint::Medium:
        return QObject::tr("Medium");
    case Hint::None:
        return QObject::tr("None");
    case Hint::Slight:
        return QObject::tr("Slight");
    case Hint::Full:
        return QObject::tr("Full");
    }
}

const char *KXftConfig::toStr(Hint::Style s)
{
    switch (s) {
    default:
    case Hint::NotSet:
        return "";
    case Hint::Medium:
        return "hintmedium";
    case Hint::None:
        return "hintnone";
    case Hint::Slight:
        return "hintslight";
    case Hint::Full:
        return "hintfull";
    }
}

KXftConfig::Hint::Style KXftConfig::toHintStyle(const QString &str)
{
    if (str == "hintmedium") {
        return Hint::Medium;
    } else if (str == "hintnone") {
        return Hint::None;
    } else if (str == "hintslight") {
        return Hint::Slight;
    } else if (str == "hintfull") {
        return Hint::Full;
    }

    return Hint::NotSet;
}

bool KXftConfig::parseConfigFile(const QString &filename)
{
    bool ok = false;

    QFile f(filename);

    if (f.open(QIODevice::ReadOnly)) {
        m_time = getTimeStamp(filename);
        ok = true;
        m_doc.clear();

        if (m_doc.setContent(&f)) {
            readContents();
        }
        f.close();
    } else {
        ok = !fExists(filename) && dWritable(getDir(filename));
    }

    if (m_doc.documentElement().isNull()) {
        m_doc.appendChild(m_doc.createElement("fontconfig"));
    }

    if (ok) {
        //
        // Check exclude range values - i.e. size and pixel size...
        // If "size" range is set, ensure "pixelsize" matches...
        if (!equal(0, m_excludeRange.from) || !equal(0, m_excludeRange.to)) {
            double pFrom = (double)point2Pixel(m_excludeRange.from), pTo = (double)point2Pixel(m_excludeRange.to);

            if (!equal(pFrom, m_excludePixelRange.from) || !equal(pTo, m_excludePixelRange.to)) {
                m_excludePixelRange.from = pFrom;
                m_excludePixelRange.to = pTo;
                m_madeChanges = true;
            }
        } else if (!equal(0, m_excludePixelRange.from) || !equal(0, m_excludePixelRange.to)) {
            // "pixelsize" set, but not "size" !!!
            m_excludeRange.from = (int)pixel2Point(m_excludePixelRange.from);
            m_excludeRange.to = (int)pixel2Point(m_excludePixelRange.to);
            m_madeChanges = true;
        }
    }

    return ok;
}

void KXftConfig::readContents()
{
    QDomNode n = m_doc.documentElement().firstChild();

    while (!n.isNull()) {
        QDomElement e = n.toElement();

        if (!e.isNull()) {
            if ("match" == e.tagName()) {
                QString str;

                int childNodesCount = e.childNodes().count();
                QDomNode en = e.firstChild();
                while (!en.isNull()) {
                    if (en.isComment()) {
                        childNodesCount--;
                    }
                    en = en.nextSibling();
                }

                switch (childNodesCount) {
                case 1:
                    if ("font" == e.attribute("target") || "pattern" == e.attribute("target")) {
                        QDomNode en = e.firstChild();
                        while (!en.isNull()) {
                            QDomElement ene = en.toElement();

                            while (ene.isComment()) {
                                ene = ene.nextSiblingElement();
                            }

                            if (!ene.isNull() && "edit" == ene.tagName()) {
                                if (!(str = getEntry(ene, "const", 2, "name", "rgba", "mode", "assign")).isNull()
                                    || (m_subPixel.type == SubPixel::NotSet && !(str = getEntry(ene, "const", 2, "name", "rgba", "mode", "append")).isNull())) {
                                    m_subPixel.node = n;
                                    m_subPixel.type = strToType(str.toLatin1());
                                } else if (!(str = getEntry(ene, "const", 2, "name", "hintstyle", "mode", "assign")).isNull()
                                           || (m_hint.style == Hint::NotSet
                                               && !(str = getEntry(ene, "const", 2, "name", "hintstyle", "mode", "append")).isNull())) {
                                    m_hint.node = n;
                                    m_hint.style = strToStyle(str.toLatin1());
                                } else if (!(str = getEntry(ene, "bool", 2, "name", "hinting", "mode", "assign")).isNull()) {
                                    m_hinting.node = n;
                                    m_hinting.set = str.toLower() != "false";
                                } else if (!(str = getEntry(ene, "bool", 2, "name", "antialias", "mode", "assign")).isNull()
                                           || (m_antiAliasing.state == AntiAliasing::NotSet
                                               && !(str = getEntry(ene, "bool", 2, "name", "antialias", "mode", "append")).isNull())) {
                                    m_antiAliasing.node = n;
                                    m_antiAliasing.state = str.toLower() != "false" ? AntiAliasing::Enabled : AntiAliasing::Disabled;
                                }
                            }
                            en = en.nextSibling();
                        }
                    }
                    break;
                case 3: // CPD: Is target "font" or "pattern" ????
                    if ("font" == e.attribute("target")) {
                        bool foundFalse = false;
                        QDomNode en = e.firstChild();
                        while (en.isComment()) {
                            en = en.nextSibling();
                        }
                        double from = -1.0, to = -1.0, pixelFrom = -1.0, pixelTo = -1.0;

                        while (!en.isNull()) {
                            QDomElement ene = en.toElement();

                            if (!ene.isNull()) {
                                if ("test" == ene.tagName()) {
                                    // kcmfonts used to write incorrectly more or less instead of
                                    // more_eq and less_eq, so read both,
                                    // first the old (wrong) one then the right one
                                    if (!(str = getEntry(ene, "double", 3, "qual", "any", "name", "size", "compare", "more")).isNull()) {
                                        from = str.toDouble();
                                    }
                                    if (!(str = getEntry(ene, "double", 3, "qual", "any", "name", "size", "compare", "more_eq")).isNull()) {
                                        from = str.toDouble();
                                    }
                                    if (!(str = getEntry(ene, "double", 3, "qual", "any", "name", "size", "compare", "less")).isNull()) {
                                        to = str.toDouble();
                                    }
                                    if (!(str = getEntry(ene, "double", 3, "qual", "any", "name", "size", "compare", "less_eq")).isNull()) {
                                        to = str.toDouble();
                                    }
                                    if (!(str = getEntry(ene, "double", 3, "qual", "any", "name", "pixelsize", "compare", "more")).isNull()) {
                                        pixelFrom = str.toDouble();
                                    }
                                    if (!(str = getEntry(ene, "double", 3, "qual", "any", "name", "pixelsize", "compare", "more_eq")).isNull()) {
                                        pixelFrom = str.toDouble();
                                    }
                                    if (!(str = getEntry(ene, "double", 3, "qual", "any", "name", "pixelsize", "compare", "less")).isNull()) {
                                        pixelTo = str.toDouble();
                                    }
                                    if (!(str = getEntry(ene, "double", 3, "qual", "any", "name", "pixelsize", "compare", "less_eq")).isNull()) {
                                        pixelTo = str.toDouble();
                                    }
                                } else if ("edit" == ene.tagName() && "false" == getEntry(ene, "bool", 2, "name", "antialias", "mode", "assign")) {
                                    foundFalse = true;
                                }
                            }

                            en = en.nextSibling();
                        }

                        if ((from >= 0 || to >= 0) && foundFalse) {
                            m_excludeRange.from = from < to ? from : to;
                            m_excludeRange.to = from < to ? to : from;
                            m_excludeRange.node = n;
                        } else if ((pixelFrom >= 0 || pixelTo >= 0) && foundFalse) {
                            m_excludePixelRange.from = pixelFrom < pixelTo ? pixelFrom : pixelTo;
                            m_excludePixelRange.to = pixelFrom < pixelTo ? pixelTo : pixelFrom;
                            m_excludePixelRange.node = n;
                        }
                    }
                    break;
                default:
                    break;
                }
            }
        }
        n = n.nextSibling();
    }
}

void KXftConfig::applySubPixelType()
{
    if (SubPixel::NotSet == m_subPixel.type) {
        if (!m_subPixel.node.isNull()) {
            m_doc.documentElement().removeChild(m_subPixel.node);
            m_subPixel.node.clear();
        }
    } else {
        QDomElement matchNode = m_doc.createElement("match");
        QDomElement typeNode = m_doc.createElement("const");
        QDomElement editNode = m_doc.createElement("edit");
        QDomText typeText = m_doc.createTextNode(toStr(m_subPixel.type));

        matchNode.setAttribute("target", "font");
        editNode.setAttribute("mode", "assign");
        editNode.setAttribute("name", "rgba");
        editNode.appendChild(typeNode);
        typeNode.appendChild(typeText);
        matchNode.appendChild(editNode);
        if (m_subPixel.node.isNull()) {
            m_doc.documentElement().appendChild(matchNode);
        } else {
            m_doc.documentElement().replaceChild(matchNode, m_subPixel.node);
        }
        m_subPixel.node = matchNode;
    }
}

void KXftConfig::applyHintStyle()
{
    applyHinting();

    if (Hint::NotSet == m_hint.style) {
        if (!m_hint.node.isNull()) {
            m_doc.documentElement().removeChild(m_hint.node);
            m_hint.node.clear();
        }
        if (!m_hinting.node.isNull()) {
            m_doc.documentElement().removeChild(m_hinting.node);
            m_hinting.node.clear();
        }
    } else {
        QDomElement matchNode = m_doc.createElement("match"), typeNode = m_doc.createElement("const"), editNode = m_doc.createElement("edit");
        QDomText typeText = m_doc.createTextNode(toStr(m_hint.style));

        matchNode.setAttribute("target", "font");
        editNode.setAttribute("mode", "assign");
        editNode.setAttribute("name", "hintstyle");
        editNode.appendChild(typeNode);
        typeNode.appendChild(typeText);
        matchNode.appendChild(editNode);
        if (m_hint.node.isNull()) {
            m_doc.documentElement().appendChild(matchNode);
        } else {
            m_doc.documentElement().replaceChild(matchNode, m_hint.node);
        }
        m_hint.node = matchNode;
    }
}

void KXftConfig::applyHinting()
{
    QDomElement matchNode = m_doc.createElement("match"), typeNode = m_doc.createElement("bool"), editNode = m_doc.createElement("edit");
    QDomText typeText = m_doc.createTextNode(m_hinting.set ? "true" : "false");

    matchNode.setAttribute("target", "font");
    editNode.setAttribute("mode", "assign");
    editNode.setAttribute("name", "hinting");
    editNode.appendChild(typeNode);
    typeNode.appendChild(typeText);
    matchNode.appendChild(editNode);
    if (m_hinting.node.isNull()) {
        m_doc.documentElement().appendChild(matchNode);
    } else {
        m_doc.documentElement().replaceChild(matchNode, m_hinting.node);
    }
    m_hinting.node = matchNode;
}

void KXftConfig::applyExcludeRange(bool pixel)
{
    Exclude &range = pixel ? m_excludePixelRange : m_excludeRange;

    if (equal(range.from, 0) && equal(range.to, 0)) {
        if (!range.node.isNull()) {
            m_doc.documentElement().removeChild(range.node);
            range.node.clear();
        }
    } else {
        QString fromString, toString;

        fromString.setNum(range.from);
        toString.setNum(range.to);

        QDomElement matchNode = m_doc.createElement("match"), fromTestNode = m_doc.createElement("test"), fromNode = m_doc.createElement("double"),
                    toTestNode = m_doc.createElement("test"), toNode = m_doc.createElement("double"), editNode = m_doc.createElement("edit"),
                    boolNode = m_doc.createElement("bool");
        QDomText fromText = m_doc.createTextNode(fromString), toText = m_doc.createTextNode(toString), boolText = m_doc.createTextNode("false");

        matchNode.setAttribute("target", "font"); // CPD: Is target "font" or "pattern" ????
        fromTestNode.setAttribute("qual", "any");
        fromTestNode.setAttribute("name", pixel ? "pixelsize" : "size");
        fromTestNode.setAttribute("compare", "more_eq");
        fromTestNode.appendChild(fromNode);
        fromNode.appendChild(fromText);
        toTestNode.setAttribute("qual", "any");
        toTestNode.setAttribute("name", pixel ? "pixelsize" : "size");
        toTestNode.setAttribute("compare", "less_eq");
        toTestNode.appendChild(toNode);
        toNode.appendChild(toText);
        editNode.setAttribute("mode", "assign");
        editNode.setAttribute("name", "antialias");
        editNode.appendChild(boolNode);
        boolNode.appendChild(boolText);
        matchNode.appendChild(fromTestNode);
        matchNode.appendChild(toTestNode);
        matchNode.appendChild(editNode);

        if (!m_antiAliasing.node.isNull()) {
            m_doc.documentElement().removeChild(range.node);
        }
        if (range.node.isNull()) {
            m_doc.documentElement().appendChild(matchNode);
        } else {
            m_doc.documentElement().replaceChild(matchNode, range.node);
        }
        range.node = matchNode;
    }
}

bool KXftConfig::antiAliasingHasLocalConfig() const
{
    return m_antiAliasingHasLocalConfig;
}

KXftConfig::AntiAliasing::State KXftConfig::getAntiAliasing() const
{
    return m_antiAliasing.state;
}

void KXftConfig::setAntiAliasing(AntiAliasing::State state)
{
    if (state != m_antiAliasing.state) {
        m_antiAliasing.state = state;
        m_madeChanges = true;
    }
}

void KXftConfig::applyAntiAliasing()
{
    if (AntiAliasing::NotSet == m_antiAliasing.state) {
        if (!m_antiAliasing.node.isNull()) {
            m_doc.documentElement().removeChild(m_antiAliasing.node);
            m_antiAliasing.node.clear();
        }
    } else {
        QDomElement matchNode = m_doc.createElement("match");
        QDomElement typeNode = m_doc.createElement("bool");
        QDomElement editNode = m_doc.createElement("edit");
        QDomText typeText = m_doc.createTextNode(m_antiAliasing.state == AntiAliasing::Enabled ? "true" : "false");

        matchNode.setAttribute("target", "font");
        editNode.setAttribute("mode", "assign");
        editNode.setAttribute("name", "antialias");
        editNode.appendChild(typeNode);
        typeNode.appendChild(typeText);
        matchNode.appendChild(editNode);
        if (!m_antiAliasing.node.isNull()) {
            m_doc.documentElement().removeChild(m_antiAliasing.node);
        }
        m_doc.documentElement().appendChild(matchNode);
        m_antiAliasing.node = matchNode;
    }
}

// KXftConfig only parses one config file, user's .fonts.conf usually.
// If that one doesn't exist, then KXftConfig doesn't know if antialiasing
// is enabled or not. So try to find out the default value from the default font.
// Maybe there's a better way *shrug*.
bool KXftConfig::aliasingEnabled()
{
    FcPattern *pattern = FcPatternCreate();
    FcConfigSubstitute(nullptr, pattern, FcMatchPattern);
    FcDefaultSubstitute(pattern);
    FcResult result;
    FcPattern *f = FcFontMatch(nullptr, pattern, &result);
    FcBool antialiased = FcTrue;
    FcPatternGetBool(f, FC_ANTIALIAS, 0, &antialiased);
    FcPatternDestroy(f);
    FcPatternDestroy(pattern);
    return antialiased == FcTrue;
}
