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

#include "Printer.h"
#include "ActionLabel.h"
#include "Fc.h"
#include "FcEngine.h"
#include "KfiConstants.h"
#include "config-fontinst.h"
#include <KAboutData>
#include <QApplication>
#include <QCloseEvent>
#include <QCommandLineOption>
#include <QCommandLineParser>
#include <QFile>
#include <QFontDatabase>
#include <QFontMetrics>
#include <QFrame>
#include <QGridLayout>
#include <QPainter>
#include <QPrintDialog>
#include <QPrinter>
#include <QProgressBar>
#include <QTextStream>
#include <QWidget>

#include "config-workspace.h"

#ifdef HAVE_LOCALE_H
#include <QDialogButtonBox>
#include <QPushButton>
#include <QVBoxLayout>
#include <locale.h>
#endif
#include "CreateParent.h"

// Enable the following to allow printing of non-installed fonts. Does not seem to work :-(
// #define KFI_PRINT_APP_FONTS

using namespace Qt::StringLiterals;
using namespace KFI;

static const int constMarginLineBefore = 1;
static const int constMarginLineAfter = 2;
static const int constMarginFont = 4;

inline bool sufficientSpace(int y, int pageHeight, const QFontMetrics &fm)
{
    return (y + constMarginFont + fm.height()) < pageHeight;
}

static bool sufficientSpace(int y, QPainter *painter, QFont font, const int *sizes, int pageHeight, int size)
{
    int titleFontHeight = painter->fontMetrics().height(), required = titleFontHeight + constMarginLineBefore + constMarginLineAfter;

    for (unsigned int s = 0; sizes[s]; ++s) {
        font.setPointSize(sizes[s]);
        required += QFontMetrics(font, painter->device()).height();
        if (sizes[s + 1]) {
            required += constMarginFont;
        }
    }

    if (0 == size) {
        font.setPointSize(CFcEngine::constDefaultAlphaSize);
        int fontHeight = QFontMetrics(font, painter->device()).height();

        required += (3 * (constMarginFont + fontHeight)) + constMarginLineBefore + constMarginLineAfter;
    }
    return (y + required) < pageHeight;
}

static QString usableStr(QFont &font, const QString &str)
{
    Q_UNUSED(font)
    return str;
}

static bool hasStr(QFont &font, const QString &str)
{
    Q_UNUSED(font)
    Q_UNUSED(str)
    return true;
}

static QString previewString(QFont &font, const QString &text, bool onlyDrawChars)
{
    Q_UNUSED(font)
    Q_UNUSED(onlyDrawChars)
    return text;
}

CPrintThread::CPrintThread(QPrinter *printer, const QList<Misc::TFont> &items, int size, QObject *parent)
    : QThread(parent)
    , m_printer(printer)
    , m_items(items)
    , m_size(size)
    , m_cancelled(false)
{
}

CPrintThread::~CPrintThread()
{
}

void CPrintThread::cancel()
{
    m_cancelled = true;
}

void CPrintThread::run()
{
    QPainter painter;
    QFont sans(u"sans"_s, 12, QFont::Bold);
    bool changedFontEmbeddingSetting(false);
    QString str(CFcEngine(false).getPreviewString());

    if (!m_printer->fontEmbeddingEnabled()) {
        m_printer->setFontEmbeddingEnabled(true);
        changedFontEmbeddingSetting = true;
    }

    m_printer->setResolution(72);
    painter.begin(m_printer);

    int pageWidth = painter.device()->width(), pageHeight = painter.device()->height(), y = 0, oneSize[2] = {m_size, 0};
    const int *sizes = oneSize;
    bool firstFont(true);

    if (0 == m_size) {
        sizes = CFcEngine::constScalableSizes;
    }

    painter.setClipping(true);
    painter.setClipRect(0, 0, pageWidth, pageHeight);

    QList<Misc::TFont>::ConstIterator it(m_items.constBegin()), end(m_items.constEnd());

    for (int i = 0; it != end && !m_cancelled; ++it, ++i) {
        QString name(FC::createName((*it).family, (*it).styleInfo));
        Q_EMIT progress(i, name);

        unsigned int s = 0;
        QFont font;

#ifdef KFI_PRINT_APP_FONTS
        QString family;

        if (-1 != appFont[(*it).family]) {
            family = QFontDatabase::applicationFontFamilies(appFont[(*it).family]).first();
            font = QFont(family);
        }
#else
        font = CFcEngine::getQFont((*it).family, (*it).styleInfo, CFcEngine::constDefaultAlphaSize);
#endif
        painter.setFont(sans);

        if (!firstFont && !sufficientSpace(y, &painter, font, sizes, pageHeight, m_size)) {
            m_printer->newPage();
            y = 0;
        }
        painter.setFont(sans);
        y += painter.fontMetrics().height();
        painter.drawText(0, y, name);

        y += constMarginLineBefore;
        painter.drawLine(0, y, pageWidth, y);
        y += constMarginLineAfter;

        bool onlyDrawChars = false;
        Qt::TextElideMode em = Qt::LeftToRight == QApplication::layoutDirection() ? Qt::ElideRight : Qt::ElideLeft;

        if (0 == m_size) {
            font.setPointSize(CFcEngine::constDefaultAlphaSize);
            painter.setFont(font);

            QFontMetrics fm(font, painter.device());
            bool lc = hasStr(font, CFcEngine::getLowercaseLetters()), uc = hasStr(font, CFcEngine::getUppercaseLetters());

            onlyDrawChars = !lc && !uc;

            if (lc || uc) {
                y += CFcEngine::constDefaultAlphaSize;
            }

            if (lc) {
                painter.drawText(0, y, fm.elidedText(CFcEngine::getLowercaseLetters(), em, pageWidth));
                y += constMarginFont + CFcEngine::constDefaultAlphaSize;
            }

            if (uc) {
                painter.drawText(0, y, fm.elidedText(CFcEngine::getUppercaseLetters(), em, pageWidth));
                y += constMarginFont + CFcEngine::constDefaultAlphaSize;
            }

            if (lc || uc) {
                QString validPunc(usableStr(font, CFcEngine::getPunctuation()));
                if (validPunc.length() >= (CFcEngine::getPunctuation().length() / 2)) {
                    painter.drawText(0, y, fm.elidedText(CFcEngine::getPunctuation(), em, pageWidth));
                    y += constMarginFont + constMarginLineBefore;
                }
                painter.drawLine(0, y, pageWidth, y);
                y += constMarginLineAfter;
            }
        }

        for (; sizes[s]; ++s) {
            y += sizes[s];
            font.setPointSize(sizes[s]);
            painter.setFont(font);

            QFontMetrics fm(font, painter.device());

            if (sufficientSpace(y, pageHeight, fm)) {
                painter.drawText(0, y, fm.elidedText(previewString(font, str, onlyDrawChars), em, pageWidth));
                if (sizes[s + 1]) {
                    y += constMarginFont;
                }
            } else {
                break;
            }
        }
        y += (s < 1 || sizes[s - 1] < 25 ? 14 : 28);
        firstFont = false;
    }
    Q_EMIT progress(m_items.count(), QString());
    painter.end();

    //
    // Did we change the users font settings? If so, reset to their previous values...
    if (changedFontEmbeddingSetting) {
        m_printer->setFontEmbeddingEnabled(false);
    }
}

CPrinter::CPrinter(QWidget *parent)
    : QDialog(parent)
{
    setWindowTitle(i18n("Print"));

    QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Cancel);
    connect(buttonBox, &QDialogButtonBox::rejected, this, &CPrinter::slotCancelClicked);

    QVBoxLayout *mainLayout = new QVBoxLayout;
    setLayout(mainLayout);

    QFrame *page = new QFrame(this);
    QGridLayout *layout = new QGridLayout(page);
    m_statusLabel = new QLabel(page);
    m_progress = new QProgressBar(page);
    layout->addWidget(m_actionLabel = new CActionLabel(this), 0, 0, 2, 1);
    layout->addWidget(m_statusLabel, 0, 1);
    layout->addWidget(m_progress, 1, 1);
    m_progress->setRange(0, 100);
    layout->addItem(new QSpacerItem(0, 0, QSizePolicy::Fixed, QSizePolicy::Expanding), 2, 0);

    mainLayout->addWidget(page);
    mainLayout->addWidget(buttonBox);
    setMinimumSize(420, 80);
}

CPrinter::~CPrinter()
{
}

void CPrinter::print(const QList<Misc::TFont> &items, int size)
{
#ifdef HAVE_LOCALE_H
    char *oldLocale = setlocale(LC_NUMERIC, "C");
#endif

    QPrinter printer;
    QPrintDialog *dialog = new QPrintDialog(&printer, parentWidget());

    if (dialog->exec()) {
        CPrintThread *thread = new CPrintThread(&printer, items, size, this);

        m_progress->setRange(0, items.count());
        m_progress->setValue(0);
        progress(0, QString());
        connect(thread, &CPrintThread::progress, this, &CPrinter::progress);
        connect(thread, &QThread::finished, this, &QDialog::accept);
        connect(this, &CPrinter::cancelled, thread, &CPrintThread::cancel);
        m_actionLabel->startAnimation();
        thread->start();
        exec();
        delete thread;
    }

    delete dialog;

#ifdef HAVE_LOCALE_H
    if (oldLocale) {
        setlocale(LC_NUMERIC, oldLocale);
    }
#endif
}

void CPrinter::progress(int p, const QString &label)
{
    if (!label.isEmpty()) {
        m_statusLabel->setText(label);
    }
    m_progress->setValue(p);
}

void CPrinter::slotCancelClicked()
{
    m_statusLabel->setText(i18n("Canceling…"));
    Q_EMIT cancelled();
}

void CPrinter::closeEvent(QCloseEvent *e)
{
    Q_UNUSED(e)
    e->ignore();
    slotCancelClicked();
}

int main(int argc, char **argv)
{
    QApplication app(argc, argv);

    KLocalizedString::setApplicationDomain(KFI_CATALOGUE);
    KAboutData aboutData(u"kfontprint"_s,
                         i18n("Font Printer"),
                         QLatin1String(WORKSPACE_VERSION_STRING),
                         i18n("Simple font printer"),
                         KAboutLicense::GPL,
                         i18n("(C) Craig Drummond, 2007"));
    KAboutData::setApplicationData(aboutData);

    QGuiApplication::setWindowIcon(QIcon::fromTheme(u"kfontprint"_s));

    QCommandLineParser parser;
    const QCommandLineOption embedOption(QLatin1String("embed"), i18n("Makes the dialog transient for an X app specified by winid"), QLatin1String("winid"));
    parser.addOption(embedOption);
    const QCommandLineOption sizeOption(QLatin1String("size"), i18n("Size index to print fonts"), QLatin1String("index"));
    parser.addOption(sizeOption);
    const QCommandLineOption pfontOption(
        QLatin1String("pfont"),
        i18n("Font to print, specified as \"Family,Style\" where Style is a 24-bit decimal number composed as: <weight><width><slant>"),
        QLatin1String("font"));
    parser.addOption(pfontOption);
    const QCommandLineOption listfileOption(QLatin1String("listfile"), i18n("File containing list of fonts to print"), QLatin1String("file"));
    parser.addOption(listfileOption);
    const QCommandLineOption deletefileOption(QLatin1String("deletefile"), i18n("Remove file containing list of fonts to print"));
    parser.addOption(deletefileOption);

    aboutData.setupCommandLine(&parser);
    parser.process(app);
    aboutData.processCommandLine(&parser);

    QList<Misc::TFont> fonts;
    int size(parser.value(sizeOption).toInt());

    if (size > -1 && size < 256) {
        QString listFile(parser.value(listfileOption));

        if (!listFile.isEmpty()) {
            QFile f(listFile);

            if (f.open(QIODevice::ReadOnly)) {
                QTextStream str(&f);

                while (!str.atEnd()) {
                    QString family(str.readLine()), style(str.readLine());

                    if (!family.isEmpty() && !style.isEmpty()) {
                        fonts.append(Misc::TFont(family, style.toUInt()));
                    } else {
                        break;
                    }
                }
                f.close();
            }

            if (parser.isSet(deletefileOption)) {
                ::unlink(listFile.toLocal8Bit().constData());
            }
        } else {
            QStringList fl(parser.values(pfontOption));
            QStringList::ConstIterator it(fl.begin()), end(fl.end());

            for (; it != end; ++it) {
                QString f(*it);

                int commaPos = f.lastIndexOf(u',');

                if (-1 != commaPos) {
                    fonts.append(Misc::TFont(f.left(commaPos), f.mid(commaPos + 1).toUInt()));
                }
            }
        }

        if (!fonts.isEmpty()) {
            CPrinter(createParent(parser.value(embedOption).toInt(nullptr, 16))).print(fonts, size);

            return 0;
        }
    }

    return -1;
}
