/* This file is part of the KDE project
 *
 * SPDX-FileCopyrightText: 2017 Wolthera van Hövell tot Westerflier <griffinvalley@gmail.com>
 *
 * SPDX-License-Identifier: LGPL-2.0-or-later
 */
#include "kis_font_family_combo_box.h"
#include "KisFontFamilyValidator.h"

#include <QFontDatabase>
#include <QFontComboBox>
#include <QHBoxLayout>
#include <QComboBox>
#include <QAbstractItemView>
#include <QScrollBar>
#include <QCompleter>
#include <klocalizedstring.h>
#include <kis_debug.h>
#include <QPainter>
#include <kconfiggroup.h>
#include <ksharedconfig.h>

PinnedFontsSeparator::PinnedFontsSeparator(QAbstractItemDelegate *_default, QWidget *parent)
    : QStyledItemDelegate(parent)
    , m_separatorIndex(0)
    , m_separatorAdded(false)
    , m_defaultDelegate(_default)
{
}

void PinnedFontsSeparator::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
    if (index.row() == m_separatorIndex && m_separatorAdded) {
        QRect viewRect = option.rect;
        painter->setPen(Qt::gray);
        painter->drawLine((viewRect.topLeft() + viewRect.bottomLeft()) / 2 + QPoint(5, 0),
                          (viewRect.topRight() + viewRect.bottomRight()) / 2 - QPoint(5, 0));
    } else {
        m_defaultDelegate->paint(painter, option, index);
    }
}

QSize PinnedFontsSeparator::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const
{
    return QStyledItemDelegate::sizeHint(option, index) * 1.25;
}

void PinnedFontsSeparator::setSeparatorIndex(int index)
{
    m_separatorIndex = index;
}

void PinnedFontsSeparator::setSeparatorAdded()
{
    m_separatorAdded = true;
}

KisFontFamilyComboBox::KisFontFamilyComboBox(QWidget *parent)
    : QComboBox(parent)
    , m_initialized(false)
    , m_initializeFromConfig(false)
{
    setEditable(true);
    completer()->setCompletionMode(QCompleter::InlineCompletion);
    completer()->setCaseSensitivity(Qt::CaseInsensitive);
    // The following are all helper fonts for LaTeX that no one but LaTeX would use
    // but because many people use LaTeX, they do show up on quite a few systems.
    m_blacklistedFonts << "bbold10" << "cmbsy10" << "cmmib10"
                       << "cmss10" << "cmex10" << "cmmi10"
                       << "cmr10" << "cmsy10" << "eufb10"
                       << "eufm10" << "eurb10" << "eurm10"
                       << "esint10" << "eufm10" << "eusb10"
                       << "eusm10" << "lasy10" << "lasyb10"
                       << "msam10" << "msbm10" << "rsfs10"
                       << "stmary10" << "wasy10" << "wasyb10";
    refillComboBox();
    QFontComboBox *temp = new QFontComboBox(this);
    m_fontSeparator = new PinnedFontsSeparator(temp->itemDelegate(), this);
    temp->setEnabled(true);
    temp->hide();
    m_separatorIndex = 0;

    KConfigGroup cfg(KSharedConfig::openConfig(), "");
    m_pinnedFonts = cfg.readEntry<QStringList>("PinnedFonts", QStringList());

    connect(this, SIGNAL(currentTextChanged(QString)), this, SLOT(slotTextChanged(QString)));
}

void KisFontFamilyComboBox::refillComboBox(QVector<QFontDatabase::WritingSystem> writingSystems)
{
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
    QFontDatabase fonts = QFontDatabase();
#endif
    int maxWidth        = 0;
    this->clear();

    QStringList duplicateFonts;
    QStringList filteredFonts;

    if (writingSystems.isEmpty()) {
        writingSystems.append(QFontDatabase::Any);
    }

    for (int i = 0; i < writingSystems.size(); i++) {
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
        Q_FOREACH (QString family, fonts.families(writingSystems.at(i))) {
            // if it's a private family it shouldn't be added.
            bool addFont = !fonts.isPrivateFamily(family);
#else
        Q_FOREACH (QString family, QFontDatabase::families(writingSystems.at(i))) {
            // if it's a private family it shouldn't be added.
            bool addFont = !QFontDatabase::isPrivateFamily(family);
#endif

            if (addFont && filteredFonts.contains(family)) {
                addFont = false;
            }
            if (addFont && duplicateFonts.contains(family)) {
                addFont = false;
            }
            if (addFont && m_blacklistedFonts.contains(family)) {
                addFont = false;
            }
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
            if (addFont && !fonts.isSmoothlyScalable(family)) {
#else
            if (addFont && !QFontDatabase::isSmoothlyScalable(family)) {
#endif
                addFont = false;
            }

            if (addFont) {
                // now, check for all possible familyname+style name combinations, so we can avoid those.
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
                Q_FOREACH (const QString style, fonts.styles(family)) {
#else
                Q_FOREACH (const QString style, QFontDatabase::styles(family)) {
#endif
                    duplicateFonts.append(family + " " + style);
                    duplicateFonts.append(family + "_" + style);
                }
                filteredFonts.append(family);
                int width = 1.5 * view()->fontMetrics()
                        .horizontalAdvance(family + " " + QFontDatabase::writingSystemSample(QFontDatabase::Any));
                if (width > maxWidth) {
                    maxWidth = width;
                }
            }
        }
    }
    this->addItems(filteredFonts);
    KisFontFamilyValidator *familyValidator = new KisFontFamilyValidator(filteredFonts, this);
    this->setValidator(familyValidator);
    if (this->count() > this->maxVisibleItems()) {
        maxWidth += view()->style()->pixelMetric(QStyle::PixelMetric::PM_ScrollBarExtent);
    }
    view()->setMinimumWidth(maxWidth);
} // KisFontFamilyComboBox::refillComboBox

void KisFontFamilyComboBox::setTopFont(const QString &family)
{
    if (family.isEmpty() || !m_initialized || m_pinnedFonts.contains(family)) {
        return;
    }

    if (m_pinnedFonts.count() > 4) {
        this->removeItem(4);
        m_pinnedFonts.pop_back();
        m_separatorIndex--;
    }

    if (m_pinnedFonts.isEmpty()) {
        this->insertSeparator(0);
        m_fontSeparator->setSeparatorAdded();
    }

    m_pinnedFonts.push_front(family);
    this->insertItem(0, family);
    m_separatorIndex++;
    m_fontSeparator->setSeparatorIndex(m_separatorIndex);
    KConfigGroup cfg(KSharedConfig::openConfig(), "");

    cfg.writeEntry("PinnedFonts", m_pinnedFonts);
}

void KisFontFamilyComboBox::setInitialized()
{
    if(m_initialized)
        return;

    m_initialized = true;

    for(int i=m_pinnedFonts.count()-1; i>=0; i--){
        this->insertItem(0, m_pinnedFonts[i]);
        m_separatorIndex++;
    }

    if(m_pinnedFonts.count() > 0){
        this->insertSeparator(m_separatorIndex);
        m_fontSeparator->setSeparatorIndex(m_separatorIndex);
        m_fontSeparator->setSeparatorAdded();
    }

    this->setItemDelegate(m_fontSeparator);
}

void KisFontFamilyComboBox::slotTextChanged(const QString &input) {
    QString text = input;
    int pos = text.size();
    this->validator()->fixup(text);
    if (this->validator()->validate(text, pos) == QValidator::Acceptable) {
        setCurrentText(text);
        Q_EMIT activated(currentIndex());
    }
}

KisFontComboBoxes::KisFontComboBoxes(QWidget *parent)
    : QWidget(parent)
{
    QHBoxLayout *layout = new QHBoxLayout(this);
    layout->setContentsMargins(0, 0, 0, 0);

    m_family = new KisFontFamilyComboBox();
    m_family->setMinimumWidth(100);
    m_family->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Preferred);
    layout->addWidget(m_family);

    m_styles = new QComboBox();
    m_styles->setObjectName("stylesComboBox");
    layout->addWidget(m_styles);
    fontFamilyChanged();
    m_family->setToolTip(i18n("Font Family"));
    m_styles->setToolTip(i18n("Font Style"));
    connect(m_family, SIGNAL(activated(int)), this, SLOT(fontFamilyChanged()));
    connect(m_family, SIGNAL(activated(int)), this, SLOT(fontChange()));
    connect(m_styles, SIGNAL(activated(int)), this, SLOT(fontChange()));
}

void KisFontComboBoxes::setCurrentFont(QFont font)
{
    setCurrentFamily(font.family());
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
    setCurrentStyle(QFontDatabase().styleString(font));
#else
    setCurrentStyle(QFontDatabase::styleString(font));
#endif
}

void KisFontComboBoxes::setCurrentFamily(const QString family)
{
    m_family->setCurrentText(family);
    fontFamilyChanged();
}

void KisFontComboBoxes::setCurrentStyle(QString style)
{
    int index = m_styles->findText(style, Qt::MatchFixedString);

    if (index < 0) {
        index = m_styles->findText(style, Qt::MatchContains);
    }

    if (index < 0) {
        index = m_styles->findText("regular", Qt::MatchContains);
    }

    if (index >= 0) {
        m_styles->setCurrentIndex(index);
    }
}

QString KisFontComboBoxes::currentFamily() const
{
    return m_family->currentText();
}

QString KisFontComboBoxes::currentStyle() const
{
    return m_styles->currentText();
}

QFont KisFontComboBoxes::currentFont(int pointSize) const
{
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
    return QFontDatabase().font(m_family->currentText(), m_styles->currentText(), pointSize);
#else
    return QFontDatabase::font(m_family->currentText(), m_styles->currentText(), pointSize);
#endif
}

void KisFontComboBoxes::refillComboBox(QVector<QFontDatabase::WritingSystem> writingSystems)
{
    KisFontFamilyComboBox *cmb = qobject_cast<KisFontFamilyComboBox *>(m_family);
    cmb->refillComboBox(writingSystems);
}

void KisFontComboBoxes::fontFamilyChanged()
{
    QString currentText = m_styles->currentText();
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
    QFontDatabase fonts;
#endif
    const QString family = m_family->currentText();
    int maxWidth         = 0;
    m_styles->clear();
    QStringList styles;

    KisFontFamilyComboBox *cmb = qobject_cast<KisFontFamilyComboBox *>(m_family);
    cmb->setTopFont(family);

#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
    if (fonts.styles(family).isEmpty()) {
#else
    if (QFontDatabase::styles(family).isEmpty()) {
#endif
        styles.append("Normal");
    }

#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
    Q_FOREACH (const QString style, fonts.styles(family)) {
        int b      = fonts.weight(family, style);
#else
    Q_FOREACH (const QString style, QFontDatabase::styles(family)) {
        int b      = QFontDatabase::weight(family, style);
#endif
        int bindex = 0;
        for (int i = 0; i < styles.size(); i++) {
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
            if (b > fonts.weight(family, styles.at(i))) {
#else
            if (b > QFontDatabase::weight(family, styles.at(i))) {
#endif
                bindex = i;
            }
        }
        if (!styles.contains(style)) {
            styles.insert(bindex, style);
            maxWidth = qMax(m_styles->view()->fontMetrics().horizontalAdvance(style + "  "), maxWidth);
        }
    }
    m_styles->addItems(styles);
    if (m_styles->count() > m_styles->maxVisibleItems()) {
        maxWidth += m_styles->view()->style()->pixelMetric(QStyle::PixelMetric::PM_ScrollBarExtent);
    }
    m_styles->view()->setMinimumWidth(maxWidth);
    if (styles.contains(currentText)) {
        m_styles->setCurrentText(currentText);
    }
} // KisFontComboBoxes::fontFamilyChanged

void KisFontComboBoxes::fontChange()
{
    Q_EMIT fontChanged(currentFont(10).toString());
}

void KisFontComboBoxes::setInitialized()
{
    KisFontFamilyComboBox *cmb = qobject_cast<KisFontFamilyComboBox *>(m_family);
    cmb->setInitialized();
}
