/***************************************************************************
 *   Copyright (C) 2007-2008 by Albert Astals Cid <aacid@kde.org>          *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 ***************************************************************************/

#include "generator_ghostview.h"

#include <math.h>

#include <QFile>
#include <QPainter>
#include <QPixmap>
#include <QSize>
#include <QPrinter>

#include <KAboutData>
#include <kconfigdialog.h>
#include <QDebug>
#include <QMimeType>
#include <QMimeDatabase>
#include <QTemporaryFile>
#include <QDir>
#include <KLocalizedString>

#include <core/document.h>
#include <core/page.h>
#include <core/fileprinter.h>
#include <core/utils.h>

#include "ui_gssettingswidget.h"
#include "gssettings.h"

#include "spectre_debug.h"
#include "rendererthread.h"

OKULAR_EXPORT_PLUGIN(GSGenerator, "libokularGenerator_ghostview.json")

GSGenerator::GSGenerator( QObject *parent, const QVariantList &args ) :
    Okular::Generator( parent, args ),
    m_internalDocument(nullptr),
    m_request(nullptr)
{
    setFeature( PrintPostscript );
    setFeature( PrintToFile );

    GSRendererThread *renderer = GSRendererThread::getCreateRenderer();
    if (!renderer->isRunning()) renderer->start();
    connect(renderer, &GSRendererThread::imageDone, this, &GSGenerator::slotImageGenerated, Qt::QueuedConnection);
}

GSGenerator::~GSGenerator()
{
}

bool GSGenerator::reparseConfig()
{
    bool changed = false;
    if (m_internalDocument)
    {
#define SET_HINT(hintname, hintdefvalue, hintvar) \
{ \
    bool newhint = documentMetaData(hintname, hintdefvalue).toBool(); \
    if (newhint != cache_##hintvar) \
    { \
        cache_##hintvar = newhint; \
        changed = true; \
    } \
}
    SET_HINT(GraphicsAntialiasMetaData, true, AAgfx)
    SET_HINT(TextAntialiasMetaData, true, AAtext)
#undef SET_HINT
    }
    return changed;
}

void GSGenerator::addPages( KConfigDialog *dlg )
{
    Ui_GSSettingsWidget gsw;
    QWidget* w = new QWidget(dlg);
    gsw.setupUi(w);
    dlg->addPage(w, GSSettings::self(), i18n("Ghostscript"), QStringLiteral("okular-gv"), i18n("Ghostscript Backend Configuration") );
}

bool GSGenerator::print( QPrinter& printer )
{
    bool result = false;

    // Create tempfile to write to
    QTemporaryFile tf(QDir::tempPath() + QLatin1String("/okular_XXXXXX.ps"));

    // Get list of pages to print
    QList<int> pageList = Okular::FilePrinter::pageList( printer,
                                               spectre_document_get_n_pages( m_internalDocument ),
                                               document()->currentPage() + 1,
                                               document()->bookmarkedPageList() );

    // Default to Postscript export, but if printing to PDF use that instead
    SpectreExporterFormat exportFormat = SPECTRE_EXPORTER_FORMAT_PS;
    if ( printer.outputFileName().right(3) == QLatin1String("pdf") )
    {
        exportFormat = SPECTRE_EXPORTER_FORMAT_PDF;
        tf.setFileTemplate(QDir::tempPath() + QLatin1String("/okular_XXXXXX.pdf"));
    }

    if ( !tf.open() )
        return false;

    SpectreExporter *exporter = spectre_exporter_new( m_internalDocument, exportFormat );
    SpectreStatus exportStatus = spectre_exporter_begin( exporter, tf.fileName().toLatin1().constData() );

    int i = 0;
    while ( i < pageList.count() && exportStatus == SPECTRE_STATUS_SUCCESS )
    {
        exportStatus = spectre_exporter_do_page( exporter, pageList.at( i ) - 1 );
        i++;
    }

    SpectreStatus endStatus = SPECTRE_STATUS_EXPORTER_ERROR;
    if (exportStatus == SPECTRE_STATUS_SUCCESS)
        endStatus = spectre_exporter_end( exporter );

    spectre_exporter_free( exporter );

    const QString fileName = tf.fileName();
    tf.close();

    if ( exportStatus == SPECTRE_STATUS_SUCCESS && endStatus == SPECTRE_STATUS_SUCCESS )
    {
        tf.setAutoRemove( false );
        int ret = Okular::FilePrinter::printFile( printer, fileName, document()->orientation(),
                                                  Okular::FilePrinter::SystemDeletesFiles,
                                                  Okular::FilePrinter::ApplicationSelectsPages,
                                                  document()->bookmarkedPageRange() );
        if ( ret >= 0 ) result = true;
    }

    return result;
}

bool GSGenerator::loadDocument( const QString & fileName, QVector< Okular::Page * > & pagesVector )
{
    cache_AAtext = documentMetaData(TextAntialiasMetaData, true).toBool();
    cache_AAgfx = documentMetaData(GraphicsAntialiasMetaData, true).toBool();

    m_internalDocument = spectre_document_new();
    spectre_document_load(m_internalDocument, QFile::encodeName(fileName).constData());
    const SpectreStatus loadStatus = spectre_document_status(m_internalDocument);
    if (loadStatus != SPECTRE_STATUS_SUCCESS)
    {
        qCDebug(OkularSpectreDebug) << "ERR:" << spectre_status_to_string(loadStatus);
        spectre_document_free(m_internalDocument);
        m_internalDocument = nullptr;
        return false;
    }
    pagesVector.resize( spectre_document_get_n_pages(m_internalDocument) );
    qCDebug(OkularSpectreDebug) << "Page count:" << pagesVector.count();
    return loadPages(pagesVector);
}

bool GSGenerator::doCloseDocument()
{
    spectre_document_free(m_internalDocument);
    m_internalDocument = nullptr;

    return true;
}

void GSGenerator::slotImageGenerated(QImage *img, Okular::PixmapRequest *request)
{
    // This can happen as GSInterpreterCMD is a singleton and on creation signals all the slots
    // of all the generators attached to it
    if (request != m_request) return;

    if ( !request->page()->isBoundingBoxKnown() )
        updatePageBoundingBox( request->page()->number(), Okular::Utils::imageBoundingBox( img ) );

    m_request = nullptr;
    QPixmap *pix = new QPixmap(QPixmap::fromImage(*img));
    delete img;
    request->page()->setPixmap( request->observer(), pix );
    signalPixmapRequestDone( request );
}

bool GSGenerator::loadPages( QVector< Okular::Page * > & pagesVector )
{
    for (uint i = 0; i < spectre_document_get_n_pages(m_internalDocument); i++)
    {
        SpectrePage     *page;
        int              width = 0, height = 0;
        SpectreOrientation pageOrientation = SPECTRE_ORIENTATION_PORTRAIT;
        page = spectre_document_get_page (m_internalDocument, i);
        if (spectre_document_status (m_internalDocument)) {
            qCDebug(OkularSpectreDebug) << "Error getting page" << i << spectre_status_to_string(spectre_document_status(m_internalDocument));
        } else {
            spectre_page_get_size(page, &width, &height);
            pageOrientation = spectre_page_get_orientation(page);
        }
        spectre_page_free(page);
        if (pageOrientation % 2 == 1) qSwap(width, height);
        pagesVector[i] = new Okular::Page(i, width, height, orientation(pageOrientation));
    }
    return pagesVector.count() > 0;
}

void GSGenerator::generatePixmap( Okular::PixmapRequest * req )
{
    qCDebug(OkularSpectreDebug) << "receiving" << *req;

    SpectrePage *page = spectre_document_get_page(m_internalDocument, req->pageNumber());

    GSRendererThread *renderer = GSRendererThread::getCreateRenderer();

    GSRendererThreadRequest gsreq(this);
    gsreq.spectrePage = page;
    gsreq.platformFonts = GSSettings::platformFonts();
    int graphicsAA = 1;
    int textAA = 1;
    if (cache_AAgfx) graphicsAA = 4;
    if (cache_AAtext) textAA = 4;
    gsreq.textAAbits = textAA;
    gsreq.graphicsAAbits = graphicsAA;

    gsreq.orientation = req->page()->orientation();
    if (req->page()->rotation() == Okular::Rotation90 ||
        req->page()->rotation() == Okular::Rotation270)
    {
        gsreq.magnify = qMax( (double)req->height() / req->page()->width(),
                              (double)req->width() / req->page()->height() );
    }
    else
    {
        gsreq.magnify = qMax( (double)req->width() / req->page()->width(),
                              (double)req->height() / req->page()->height() );
    }
    gsreq.request = req;
    m_request = req;
    renderer->addRequest(gsreq);
}

bool GSGenerator::canGeneratePixmap() const
{
    return !m_request;
}

Okular::DocumentInfo GSGenerator::generateDocumentInfo( const QSet<Okular::DocumentInfo::Key> &keys ) const
{
    Okular::DocumentInfo docInfo;
    if ( keys.contains( Okular::DocumentInfo::Title ) )
        docInfo.set( Okular::DocumentInfo::Title, spectre_document_get_title(m_internalDocument) );
    if ( keys.contains( Okular::DocumentInfo::Author ) )
        docInfo.set( Okular::DocumentInfo::Author, spectre_document_get_for(m_internalDocument) );
    if ( keys.contains( Okular::DocumentInfo::Creator ) )
        docInfo.set( Okular::DocumentInfo::Creator, spectre_document_get_creator(m_internalDocument) );
    if ( keys.contains( Okular::DocumentInfo::CreationDate ) )
        docInfo.set( Okular::DocumentInfo::CreationDate, spectre_document_get_creation_date(m_internalDocument) );
    if ( keys.contains( Okular::DocumentInfo::CustomKeys ) )
        docInfo.set( QStringLiteral("dscversion"), spectre_document_get_format(m_internalDocument), i18n("Document version") );

    if ( keys.contains( Okular::DocumentInfo::MimeType ) )
    {
        int languageLevel = spectre_document_get_language_level(m_internalDocument);
        if (languageLevel > 0) docInfo.set( QStringLiteral("langlevel"), QString::number(languageLevel), i18n("Language Level") );
        if (spectre_document_is_eps(m_internalDocument))
            docInfo.set( Okular::DocumentInfo::MimeType, QStringLiteral("image/x-eps") );
        else
            docInfo.set( Okular::DocumentInfo::MimeType, QStringLiteral("application/postscript") );
    }

    if ( keys.contains( Okular::DocumentInfo::Pages ) )
        docInfo.set( Okular::DocumentInfo::Pages, QString::number(spectre_document_get_n_pages(m_internalDocument)) );

    return docInfo;
}

Okular::Rotation GSGenerator::orientation(SpectreOrientation pageOrientation) const
{
    switch (pageOrientation)
    {
        case SPECTRE_ORIENTATION_PORTRAIT:
            return Okular::Rotation0;
        case SPECTRE_ORIENTATION_LANDSCAPE:
            return Okular::Rotation90;
        case SPECTRE_ORIENTATION_REVERSE_PORTRAIT:
            return Okular::Rotation180;
        case SPECTRE_ORIENTATION_REVERSE_LANDSCAPE:
            return Okular::Rotation270;
    }
// get rid of warnings, should never happen
    return Okular::Rotation0;
}

QVariant GSGenerator::metaData(const QString &key, const QVariant &option) const
{
    Q_UNUSED(option)
    if (key == QLatin1String("DocumentTitle"))
    {
        const char *title = spectre_document_get_title(m_internalDocument);
        if (title)
            return QString::fromLatin1(title);
    }
    return QVariant();
}

#include "generator_ghostview.moc"
