/*
    SPDX-FileCopyrightText: 1997 Mark Donohoe
    SPDX-FileCopyrightText: 1999 Lars Knoll
    SPDX-FileCopyrightText: 2000 Rik Hemsley
    SPDX-FileCopyrightText: 2015 Antonis Tsiapaliokas <antonis.tsiapaliokas@kde.org>
    SPDX-FileCopyrightText: 2017 Marco Martin <mart@kde.org>
    SPDX-FileCopyrightText: 2019 Benjamin Port <benjamin.port@enioka.com>

    Ported to kcontrol2:
    SPDX-FileCopyrightText: Geert Jansen

    SPDX-License-Identifier: LGPL-2.0-or-later
*/

#include "fonts.h"

#include <QApplication>
#include <QDBusConnection>
#include <QDBusMessage>
#include <QFontDatabase>
#include <QProcess>
#include <QQmlEngine>
#include <QQuickItem>
#include <QQuickView>
#include <QWindow>

#include <KAcceleratorManager>
#include <KConfig>
#include <KConfigGroup>
#include <KFontChooserDialog>
#include <KLocalizedString>
#include <KPluginFactory>
#include <KWindowSystem>

#include "../kcms-common_p.h"
#include "devicepixelratiohelper.h"
#include "krdb.h"
#include "kxftconfig.h"
#include "previewimageprovider.h"

#include "fontsdata.h"

/**** DLL Interface ****/
K_PLUGIN_FACTORY_WITH_JSON(KFontsFactory, "kcm_fonts.json", registerPlugin<KFonts>(); registerPlugin<FontsData>();)

using namespace Qt::StringLiterals;

/**** KFonts ****/

KFonts::KFonts(QObject *parent, const KPluginMetaData &metaData)
    : KQuickManagedConfigModule(parent, metaData)
    , m_data(new FontsData(this))
    , m_subPixelOptionsModel(new QStandardItemModel(this))
    , m_hintingOptionsModel(new QStandardItemModel(this))
    , m_imageProviderReady(false)
{
    qmlRegisterAnonymousType<QStandardItemModel>("QStandardItemModel", 1);
    qmlRegisterAnonymousType<FontsSettings>("FontsSettings", 1);
    qmlRegisterAnonymousType<FontsAASettings>("FontsAASettings", 1);
    qmlRegisterType<DevicePixelRatioHelper>("org.kde.plasma.kcm.fonts", 1, 0, "DevicePixelRatioHelper");

    setButtons(Apply | Default | Help);

    for (KXftConfig::SubPixel::Type t :
         {KXftConfig::SubPixel::None, KXftConfig::SubPixel::Rgb, KXftConfig::SubPixel::Bgr, KXftConfig::SubPixel::Vrgb, KXftConfig::SubPixel::Vbgr}) {
        auto item = new QStandardItem(KXftConfig::description(t));
        m_subPixelOptionsModel->appendRow(item);
    }

    for (KXftConfig::Hint::Style s : {KXftConfig::Hint::None, KXftConfig::Hint::Slight, KXftConfig::Hint::Medium, KXftConfig::Hint::Full}) {
        auto item = new QStandardItem(KXftConfig::description(s));
        m_hintingOptionsModel->appendRow(item);
    }
    connect(fontsAASettings(), &FontsAASettings::hintingChanged, this, &KFonts::hintingCurrentIndexChanged);
    connect(fontsAASettings(), &FontsAASettings::subPixelChanged, this, &KFonts::subPixelCurrentIndexChanged);
}

KFonts::~KFonts() = default;

FontsSettings *KFonts::fontsSettings() const
{
    return m_data->fontsSettings();
}

FontsAASettings *KFonts::fontsAASettings() const
{
    return m_data->fontsAASettings();
}

QAbstractItemModel *KFonts::subPixelOptionsModel() const
{
    return m_subPixelOptionsModel;
}

QAbstractItemModel *KFonts::hintingOptionsModel() const
{
    return m_hintingOptionsModel;
}

void KFonts::load()
{
    // first load all the settings
    KQuickManagedConfigModule::load();

    // Load preview
    // NOTE: This needs to be done AFTER AA settings is loaded
    // otherwise AA settings will be reset in process of loading
    // previews
    engine()->addImageProvider(u"preview"_s, new PreviewImageProvider(fontsSettings()->font()));
    setImageProviderReady(true);
    // KCM expect save state to be false at this point (can be true because if a font setting loaded
    // from the config isn't available on the system, font substitution may take place)
    setNeedsSave(false);
}

void KFonts::setImageProviderReady(bool ready)
{
    if (m_imageProviderReady != ready) {
        m_imageProviderReady = ready;
        Q_EMIT imageProviderReadyChanged();
    }
}

bool KFonts::imageProviderReady()
{
    return m_imageProviderReady;
}

void KFonts::save()
{
#if HAVE_X11
    bool forceFontDPIChanged = false;

    if (KWindowSystem::isPlatformX11()) {
        auto dpiItem = fontsAASettings()->findItem(u"forceFontDPI"_s);
        auto antiAliasingItem = fontsAASettings()->findItem(u"antiAliasing"_s);
        Q_ASSERT(dpiItem && antiAliasingItem);
        if (dpiItem->isSaveNeeded() || antiAliasingItem->isSaveNeeded()) {
            Q_EMIT aliasingChangeApplied();
        }

        forceFontDPIChanged = dpiItem->isSaveNeeded();
    }
#endif

    KQuickManagedConfigModule::save();

#if HAVE_X11
    // if the setting is reset in the module, remove the dpi value,
    // otherwise don't explicitly remove it and leave any possible system-wide value
    if (fontsAASettings()->forceFontDPI() == 0 && forceFontDPIChanged && KWindowSystem::isPlatformX11()) {
        QProcess proc;
        proc.setProcessChannelMode(QProcess::ForwardedChannels);
        proc.start(u"xrdb"_s, QStringList{u"-quiet"_s, u"-remove"_s, u"-nocpp"_s});
        if (proc.waitForStarted()) {
            proc.write("Xft.dpi\n");
            proc.closeWriteChannel();
            proc.waitForFinished();
        }
    }
#endif

    // Notify the world about the font changes
    if (qEnvironmentVariableIsSet("KDE_FULL_SESSION")) {
        QDBusMessage message = QDBusMessage::createSignal(u"/KDEPlatformTheme"_s, u"org.kde.KDEPlatformTheme"_s, u"refreshFonts"_s);
        QDBusConnection::sessionBus().send(message);
    }

    runRdb(KRdbExportXftSettings | KRdbExportGtkTheme);
}

void KFonts::adjustFont(const QFont &font, const QString &category)
{
    QFont selFont = font;
    int ret = KFontChooserDialog::getFont(selFont, KFontChooser::NoDisplayFlags, QApplication::activeWindow());

    if (selFont.pointSize() < 4) {
        Q_EMIT fontTooSmall();
        selFont.setPointSize(4);
    }

    if (ret == QDialog::Accepted) {
        if (category == QLatin1String("font")) {
            fontsSettings()->setFont(selFont);
        } else if (category == QLatin1String("menuFont")) {
            fontsSettings()->setMenuFont(selFont);
        } else if (category == QLatin1String("toolBarFont")) {
            fontsSettings()->setToolBarFont(selFont);
        } else if (category == QLatin1String("activeFont")) {
            fontsSettings()->setActiveFont(selFont);
        } else if (category == QLatin1String("smallestReadableFont")) {
            fontsSettings()->setSmallestReadableFont(selFont);
        } else if (category == QLatin1String("fixed")) {
            fontsSettings()->setFixed(selFont);
        }
    }
    Q_EMIT fontsHaveChanged();
}

void KFonts::adjustAllFonts()
{
    QFont font = fontsSettings()->font();
    KFontChooser::FontDiffFlags fontDiffFlags;
    int ret = KFontChooserDialog::getFontDiff(font, fontDiffFlags, KFontChooser::NoDisplayFlags, QApplication::activeWindow());

    if (ret == QDialog::Accepted && fontDiffFlags) {
        fontsSettings()->setFont(applyFontDiff(fontsSettings()->font(), font, fontDiffFlags));
        fontsSettings()->setMenuFont(applyFontDiff(fontsSettings()->menuFont(), font, fontDiffFlags));
        fontsSettings()->setToolBarFont(applyFontDiff(fontsSettings()->toolBarFont(), font, fontDiffFlags));
        fontsSettings()->setActiveFont(applyFontDiff(fontsSettings()->activeFont(), font, fontDiffFlags));

        QFont smallestFont = font;
        // Make the small font 2 points smaller than the general font, but only
        // if the general font is 9pt or higher or else the small font would be
        // borderline unreadable. Assume that if the user is making the font
        // tiny, they want a tiny font everywhere.
        const int generalFontPointSize = font.pointSize();
        if (generalFontPointSize >= 9) {
            smallestFont.setPointSize(generalFontPointSize - 2);
        }
        fontsSettings()->setSmallestReadableFont(applyFontDiff(fontsSettings()->smallestReadableFont(), smallestFont, fontDiffFlags));

        const QFont adjustedFont = applyFontDiff(fontsSettings()->fixed(), font, fontDiffFlags);
        if (QFontInfo(adjustedFont).fixedPitch()) {
            fontsSettings()->setFixed(adjustedFont);
        }
        Q_EMIT fontsHaveChanged();
    }
}

QFont KFonts::applyFontDiff(const QFont &fnt, const QFont &newFont, int fontDiffFlags)
{
    QFont font(fnt);

    if (fontDiffFlags & KFontChooser::FontDiffSize) {
        font.setPointSizeF(newFont.pointSizeF());
    }
    if ((fontDiffFlags & KFontChooser::FontDiffFamily)) {
        font.setFamily(newFont.family());
    }
    if (fontDiffFlags & KFontChooser::FontDiffStyle) {
        font.setWeight(newFont.weight());
        font.setStyle(newFont.style());
        font.setUnderline(newFont.underline());
        font.setStyleName(newFont.styleName());
    }

    return font;
}

int KFonts::subPixelCurrentIndex() const
{
    return fontsAASettings()->subPixel() - KXftConfig::SubPixel::None;
}

void KFonts::setSubPixelCurrentIndex(int idx)
{
    fontsAASettings()->setSubPixel(static_cast<KXftConfig::SubPixel::Type>(KXftConfig::SubPixel::None + idx));
}

int KFonts::hintingCurrentIndex() const
{
    return fontsAASettings()->hinting() - KXftConfig::Hint::None;
}

void KFonts::setHintingCurrentIndex(int idx)
{
    fontsAASettings()->setHinting(static_cast<KXftConfig::Hint::Style>(KXftConfig::Hint::None + idx));
}

#include "fonts.moc"

#include "moc_fonts.cpp"
