/***************************************************************************
 *   Copyright (C) 2005   by Piotr Szymanski <niedakh@gmail.com>           *
 *   Copyright (C) 2008   by Albert Astals Cid <aacid@kde.org>             *
 *   Copyright (C) 2017   Klarälvdalens Datakonsult AB, a KDAB Group       *
 *                        company, info@kdab.com. Work sponsored by the    *
 *                        LiMux project of the city of Munich              *
 *                                                                         *
 *   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.h"
#include "generator_p.h"
#include "observer.h"

#include <QApplication>
#include <qeventloop.h>
#include <QPrinter>

#include <QDebug>
#include <QIcon>
#include <QMimeDatabase>
#include <QTimer>
#include <KLocalizedString>

#ifdef WITH_KWALLET
#include <kwallet.h>
#endif

#include "document.h"
#include "document_p.h"
#include "page.h"
#include "page_p.h"
#include "textpage.h"
#include "utils.h"

using namespace Okular;

GeneratorPrivate::GeneratorPrivate()
    : m_document( nullptr ),
      mPixmapGenerationThread( nullptr ), mTextPageGenerationThread( nullptr ),
      mPixmapReady( true ), mTextPageReady( true ),
      m_closing( false ), m_closingLoop( nullptr ),
      m_dpi(72.0, 72.0)
{
    qRegisterMetaType<Okular::Page*>();
}

GeneratorPrivate::~GeneratorPrivate()
{
    if ( mPixmapGenerationThread )
        mPixmapGenerationThread->wait();

    delete mPixmapGenerationThread;

    if ( mTextPageGenerationThread )
        mTextPageGenerationThread->wait();

    delete mTextPageGenerationThread;
}

PixmapGenerationThread* GeneratorPrivate::pixmapGenerationThread()
{
    if ( mPixmapGenerationThread )
        return mPixmapGenerationThread;

    Q_Q( Generator );
    mPixmapGenerationThread = new PixmapGenerationThread( q );
    QObject::connect( mPixmapGenerationThread, &PixmapGenerationThread::finished, q, [this] { pixmapGenerationFinished(); },
                      Qt::QueuedConnection );

    return mPixmapGenerationThread;
}

TextPageGenerationThread* GeneratorPrivate::textPageGenerationThread()
{
    if ( mTextPageGenerationThread )
        return mTextPageGenerationThread;

    Q_Q( Generator );
    mTextPageGenerationThread = new TextPageGenerationThread( q );
    QObject::connect( mTextPageGenerationThread, &TextPageGenerationThread::finished, q, [this] { textpageGenerationFinished(); },
                      Qt::QueuedConnection );

    return mTextPageGenerationThread;
}

void GeneratorPrivate::pixmapGenerationFinished()
{
    Q_Q( Generator );
    PixmapRequest *request = mPixmapGenerationThread->request();
    const QImage& img = mPixmapGenerationThread->image();
    mPixmapGenerationThread->endGeneration();

    QMutexLocker locker( threadsLock() );

    if ( m_closing )
    {
        mPixmapReady = true;
        delete request;
        if ( mTextPageReady )
        {
            locker.unlock();
            m_closingLoop->quit();
        }
        return;
    }


    if ( !request->shouldAbortRender() )
    {
        request->page()->setPixmap( request->observer(), new QPixmap( QPixmap::fromImage( img ) ), request->normalizedRect() );
        const int pageNumber = request->page()->number();

        if ( mPixmapGenerationThread->calcBoundingBox() )
            q->updatePageBoundingBox( pageNumber, mPixmapGenerationThread->boundingBox() );
    }
    else
    {
        // Cancel the text page generation too if it's still running
        if ( mTextPageGenerationThread && mTextPageGenerationThread->isRunning() ) {
            mTextPageGenerationThread->abortExtraction();
            mTextPageGenerationThread->wait();
        }
    }

    mPixmapReady = true;
    q->signalPixmapRequestDone( request );
}

void GeneratorPrivate::textpageGenerationFinished()
{
    Q_Q( Generator );
    Page *page = mTextPageGenerationThread->page();
    mTextPageGenerationThread->endGeneration();

    QMutexLocker locker( threadsLock() );
    mTextPageReady = true;

    if ( m_closing )
    {
        delete mTextPageGenerationThread->textPage();
        if ( mPixmapReady )
        {
            locker.unlock();
            m_closingLoop->quit();
        }
        return;
    }

    if ( mTextPageGenerationThread->textPage() )
    {
        TextPage *tp = mTextPageGenerationThread->textPage();
        page->setTextPage( tp );
        q->signalTextGenerationDone( page, tp );
    }
}

QMutex* GeneratorPrivate::threadsLock()
{
    return &m_threadsMutex;
}

QVariant GeneratorPrivate::metaData( const QString &, const QVariant & ) const
{
    return QVariant();
}

QImage GeneratorPrivate::image( PixmapRequest * )
{
    return QImage();
}


Generator::Generator(QObject* parent, const QVariantList &args)
    : Generator( *new GeneratorPrivate(), parent, args )
{
    // the delegated constructor does it all
}

Generator::Generator(GeneratorPrivate &dd, QObject *parent, const QVariantList &args)
    : QObject(parent), d_ptr(&dd)
{
    d_ptr->q_ptr = this;
    Q_UNUSED(args)
}

Generator::~Generator()
{
    delete d_ptr;
}

bool Generator::loadDocument( const QString & fileName, QVector< Page * > & pagesVector )
{
    Q_UNUSED(fileName);
    Q_UNUSED(pagesVector);

    return false;
}

bool Generator::loadDocumentFromData( const QByteArray &, QVector< Page * > & )
{
    return false;
}

Document::OpenResult Generator::loadDocumentWithPassword( const QString & fileName, QVector< Page * > & pagesVector, const QString & )
{
    return loadDocument( fileName, pagesVector ) ? Document::OpenSuccess : Document::OpenError;
}

Document::OpenResult Generator::loadDocumentFromDataWithPassword( const QByteArray & fileData, QVector< Page * > & pagesVector, const QString & )
{
    return loadDocumentFromData( fileData, pagesVector ) ? Document::OpenSuccess : Document::OpenError;
}

Generator::SwapBackingFileResult Generator::swapBackingFile( QString const &/*newFileName */, QVector<Okular::Page*> & /*newPagesVector*/ )
{
    return SwapBackingFileError;
}

bool Generator::closeDocument()
{
    Q_D( Generator );

    d->m_closing = true;

    d->threadsLock()->lock();
    if ( !( d->mPixmapReady && d->mTextPageReady ) )
    {
        QEventLoop loop;
        d->m_closingLoop = &loop;

        d->threadsLock()->unlock();

        loop.exec();

        d->m_closingLoop = nullptr;
    }
    else
    {
        d->threadsLock()->unlock();
    }

    bool ret = doCloseDocument();

    d->m_closing = false;

    return ret;
}

bool Generator::canGeneratePixmap() const
{
    Q_D( const Generator );
    return d->mPixmapReady;
}

void Generator::generatePixmap( PixmapRequest *request )
{
    Q_D( Generator );
    d->mPixmapReady = false;

    const bool calcBoundingBox = !request->isTile() && !request->page()->isBoundingBoxKnown();

    if ( request->asynchronous() && hasFeature( Threaded ) )
    {
        if ( d->textPageGenerationThread()->isFinished() && !canGenerateTextPage() )
        {
            // It can happen that the text generation has already finished but
            // mTextPageReady is still false because textpageGenerationFinished
            // didn't have time to run, if so queue ourselves
            QTimer::singleShot(0, this, [this, request] { generatePixmap(request); });
            return;
        }

        /**
         * We create the text page for every page that is visible to the
         * user, so he can use the text extraction tools without a delay.
         */
        if ( hasFeature( TextExtraction ) && !request->page()->hasTextPage() && canGenerateTextPage() && !d->m_closing ) {
            d->mTextPageReady = false;
            d->textPageGenerationThread()->setPage( request->page() );

            // dummy is used as a way to make sure the lambda gets disconnected each time it is executed
            // since not all the times the pixmap generation thread starts we want the text generation thread to also start
            QObject *dummy = new QObject();
            connect(d_ptr->pixmapGenerationThread(), &QThread::started, dummy, [this, dummy] {
                delete dummy;
                d_ptr->textPageGenerationThread()->startGeneration();
            });
        }
        // pixmap generation thread must be started *after* connect(), else we may miss the start signal and get lock-ups (see bug 396137)
        d->pixmapGenerationThread()->startGeneration( request, calcBoundingBox );

        return;
    }

    const QImage& img = image( request );
    request->page()->setPixmap( request->observer(), new QPixmap( QPixmap::fromImage( img ) ), request->normalizedRect() );
    const int pageNumber = request->page()->number();

    d->mPixmapReady = true;

    signalPixmapRequestDone( request );
    if ( calcBoundingBox )
        updatePageBoundingBox( pageNumber, Utils::imageBoundingBox( &img ) );
}

bool Generator::canGenerateTextPage() const
{
    Q_D( const Generator );
    return d->mTextPageReady;
}

void Generator::generateTextPage( Page *page )
{
    TextRequest treq( page );
    TextPage *tp = textPage( &treq );
    page->setTextPage( tp );
    signalTextGenerationDone( page, tp );
}

QImage Generator::image( PixmapRequest *request )
{
    Q_D( Generator );
    return d->image( request );
}

TextPage* Generator::textPage( TextRequest * )
{
    return nullptr;
}

DocumentInfo Generator::generateDocumentInfo(const QSet<DocumentInfo::Key> &keys) const
{
    Q_UNUSED(keys);

    return DocumentInfo();
}

const DocumentSynopsis * Generator::generateDocumentSynopsis()
{
    return nullptr;
}

FontInfo::List Generator::fontsForPage( int )
{
    return FontInfo::List();
}

const QList<EmbeddedFile*> * Generator::embeddedFiles() const
{
    return nullptr;
}

Generator::PageSizeMetric Generator::pagesSizeMetric() const
{
    return None;
}

bool Generator::isAllowed( Permission ) const
{
    return true;
}

void Generator::rotationChanged( Rotation, Rotation )
{
}

PageSize::List Generator::pageSizes() const
{
    return PageSize::List();
}

void Generator::pageSizeChanged( const PageSize &, const PageSize & )
{
}

bool Generator::print( QPrinter& )
{
    return false;
}

Generator::PrintError Generator::printError() const
{
    return UnknownPrintError;
}

void Generator::opaqueAction( const BackendOpaqueAction * /*action*/ )
{
}

QVariant Generator::metaData( const QString &key, const QVariant &option ) const
{
    Q_D( const Generator );
    return d->metaData( key, option );
}

ExportFormat::List Generator::exportFormats() const
{
    return ExportFormat::List();
}

bool Generator::exportTo( const QString&, const ExportFormat& )
{
    return false;
}

void Generator::walletDataForFile( const QString &fileName, QString *walletName, QString *walletFolder, QString *walletKey ) const
{
#ifdef WITH_KWALLET
    *walletKey = fileName.section( QLatin1Char('/'), -1, -1);
    *walletName = KWallet::Wallet::NetworkWallet();
    *walletFolder = QStringLiteral("KPdf");
#endif
}

bool Generator::hasFeature( GeneratorFeature feature ) const
{
    Q_D( const Generator );
    return d->m_features.contains( feature );
}

void Generator::signalPixmapRequestDone( PixmapRequest * request )
{
    Q_D( Generator );
    if ( d->m_document )
        d->m_document->requestDone( request );
    else
    {
        delete request;
    }
}

void Generator::signalTextGenerationDone( Page *page, TextPage *textPage )
{
    Q_D( Generator );
    if ( d->m_document )
        d->m_document->textGenerationDone( page );
    else
        delete textPage;
}

void Generator::signalPartialPixmapRequest( PixmapRequest *request, const QImage &image )
{
    if ( request->shouldAbortRender() )
        return;

    PagePrivate *pagePrivate = PagePrivate::get( request->page() );
    pagePrivate->setPixmap( request->observer(), new QPixmap( QPixmap::fromImage( image ) ), request->normalizedRect(), true /* isPartialPixmap */ );

    const int pageNumber = request->page()->number();
    request->observer()->notifyPageChanged( pageNumber, Okular::DocumentObserver::Pixmap );
}

const Document * Generator::document() const
{
    Q_D( const Generator );
    if ( d->m_document )
    {
        return d->m_document->m_parent;
    }
    return nullptr;
}

void Generator::setFeature( GeneratorFeature feature, bool on )
{
    Q_D( Generator );
    if ( on )
        d->m_features.insert( feature );
    else
        d->m_features.remove( feature );
}

QVariant Generator::documentMetaData( const QString &key, const QVariant &option ) const
{
    Q_D( const Generator );
    if ( !d->m_document )
        return QVariant();

    if (key == QLatin1String("PaperColor"))
        return documentMetaData(PaperColorMetaData, option);
    if (key == QLatin1String("GraphicsAntialias"))
        return documentMetaData(GraphicsAntialiasMetaData, option);
    if (key == QLatin1String("TextAntialias"))
        return documentMetaData(TextAntialiasMetaData, option);
    if (key == QLatin1String("TextHinting"))
        return documentMetaData(TextHintingMetaData, option);

    return QVariant();
}

QVariant Generator::documentMetaData( const DocumentMetaDataKey key, const QVariant &option ) const
{
    Q_D( const Generator );
    if ( !d->m_document )
        return QVariant();

    return d->m_document->documentMetaData( key, option );
}

QMutex* Generator::userMutex() const
{
    Q_D( const Generator );
    return &d->m_mutex;
}

void Generator::updatePageBoundingBox( int page, const NormalizedRect & boundingBox )
{
    Q_D( Generator );
    if ( d->m_document ) // still connected to document?
        d->m_document->setPageBoundingBox( page, boundingBox );
}

void Generator::requestFontData(const Okular::FontInfo & /*font*/, QByteArray * /*data*/)
{

}

void Generator::setDPI(const QSizeF & dpi) // clazy:exclude=function-args-by-value TODO remove the & when we do a BIC change elsewhere
{
     Q_D( Generator );
     d->m_dpi = dpi;
}

QSizeF Generator::dpi() const
{
     Q_D( const Generator );
     return d->m_dpi;
}

QAbstractItemModel * Generator::layersModel() const
{
    return nullptr;
}

TextRequest::TextRequest()
: d( new TextRequestPrivate )
{
    d->mPage = nullptr;
    d->mShouldAbortExtraction = 0;
}

TextRequest::TextRequest( Page *page )
: d( new TextRequestPrivate )
{
    d->mPage = page;
    d->mShouldAbortExtraction = 0;
}

TextRequest::~TextRequest()
{
    delete d;
}

Page *TextRequest::page() const
{
    return d->mPage;
}

bool TextRequest::shouldAbortExtraction() const
{
    return d->mShouldAbortExtraction != 0;
}

TextRequestPrivate *TextRequestPrivate::get(const TextRequest *req)
{
    return req->d;
}

PixmapRequest::PixmapRequest( DocumentObserver *observer, int pageNumber, int width, int height, int priority, PixmapRequestFeatures features )
  : d( new PixmapRequestPrivate )
{
    d->mObserver = observer;
    d->mPageNumber = pageNumber;
    d->mWidth = ceil(width * qApp->devicePixelRatio());
    d->mHeight = ceil(height * qApp->devicePixelRatio());
    d->mPriority = priority;
    d->mFeatures = features;
    d->mForce = false;
    d->mTile = false;
    d->mNormalizedRect = NormalizedRect();
    d->mPartialUpdatesWanted = false;
    d->mShouldAbortRender = 0;
}

PixmapRequest::~PixmapRequest()
{
    delete d;
}

DocumentObserver *PixmapRequest::observer() const
{
    return d->mObserver;
}

int PixmapRequest::pageNumber() const
{
    return d->mPageNumber;
}

int PixmapRequest::width() const
{
    return d->mWidth;
}

int PixmapRequest::height() const
{
    return d->mHeight;
}

int PixmapRequest::priority() const
{
    return d->mPriority;
}

bool PixmapRequest::asynchronous() const
{
    return d->mFeatures & Asynchronous;
}

bool PixmapRequest::preload() const
{
    return d->mFeatures & Preload;
}

Page* PixmapRequest::page() const
{
    return d->mPage;
}

void PixmapRequest::setTile( bool tile )
{
    d->mTile = tile;
}

bool PixmapRequest::isTile() const
{
    return d->mTile;
}

void PixmapRequest::setNormalizedRect( const NormalizedRect &rect )
{
    if ( d->mNormalizedRect == rect )
        return;

    d->mNormalizedRect = rect;
}

const NormalizedRect& PixmapRequest::normalizedRect() const
{
    return d->mNormalizedRect;
}

void PixmapRequest::setPartialUpdatesWanted(bool partialUpdatesWanted)
{
    d->mPartialUpdatesWanted = partialUpdatesWanted;
}

bool PixmapRequest::partialUpdatesWanted() const
{
    return d->mPartialUpdatesWanted;
}

bool PixmapRequest::shouldAbortRender() const
{
    return d->mShouldAbortRender != 0;
}

Okular::TilesManager* PixmapRequestPrivate::tilesManager() const
{
    return mPage->d->tilesManager(mObserver);
}

PixmapRequestPrivate *PixmapRequestPrivate::get(const PixmapRequest *req)
{
    return req->d;
}

void PixmapRequestPrivate::swap()
{
    qSwap( mWidth, mHeight );
}

class Okular::ExportFormatPrivate : public QSharedData
{
    public:
        ExportFormatPrivate( const QString &description, const QMimeType &mimeType, const QIcon &icon = QIcon() )
            : QSharedData(), mDescription( description ), mMimeType( mimeType ), mIcon( icon )
        {
        }
        ~ExportFormatPrivate()
        {
        }

        QString mDescription;
        QMimeType mMimeType;
        QIcon mIcon;
};

ExportFormat::ExportFormat()
    : d( new ExportFormatPrivate( QString(), QMimeType() ) )
{
}

ExportFormat::ExportFormat( const QString &description, const QMimeType &mimeType )
    : d( new ExportFormatPrivate( description, mimeType ) )
{
}

ExportFormat::ExportFormat( const QIcon &icon, const QString &description, const QMimeType &mimeType )
    : d( new ExportFormatPrivate( description, mimeType, icon ) )
{
}

ExportFormat::~ExportFormat()
{
}

ExportFormat::ExportFormat( const ExportFormat &other )
    : d( other.d )
{
}

ExportFormat& ExportFormat::operator=( const ExportFormat &other )
{
    if ( this == &other )
        return *this;

    d = other.d;

    return *this;
}

QString ExportFormat::description() const
{
    return d->mDescription;
}

QMimeType ExportFormat::mimeType() const
{
    return d->mMimeType;
}

QIcon ExportFormat::icon() const
{
    return d->mIcon;
}

bool ExportFormat::isNull() const
{
    return !d->mMimeType.isValid() || d->mDescription.isNull();
}

ExportFormat ExportFormat::standardFormat( StandardExportFormat type )
{
    QMimeDatabase db;
    switch ( type )
    {
        case PlainText:
            return ExportFormat( QIcon::fromTheme( QStringLiteral("text-x-generic") ), i18n( "Plain &Text..." ), db.mimeTypeForName( QStringLiteral("text/plain") ) );
            break;
        case PDF:
            return ExportFormat( QIcon::fromTheme( QStringLiteral("application-pdf") ), i18n( "PDF" ), db.mimeTypeForName( QStringLiteral("application/pdf") ) );
            break;
        case OpenDocumentText:
            return ExportFormat(
                QIcon::fromTheme( QStringLiteral("application-vnd.oasis.opendocument.text") ),
                i18nc( "This is the document format", "OpenDocument Text" ),
                db.mimeTypeForName( QStringLiteral("application/vnd.oasis.opendocument.text") ) );
	    break;
        case HTML:
            return ExportFormat( QIcon::fromTheme( QStringLiteral("text-html") ), i18nc( "This is the document format", "HTML" ), db.mimeTypeForName( QStringLiteral("text/html") ) );
            break;
    }
    return ExportFormat();
}

bool ExportFormat::operator==( const ExportFormat &other ) const
{
    return d == other.d;
}

bool ExportFormat::operator!=( const ExportFormat &other ) const
{
    return d != other.d;
}

QDebug operator<<( QDebug str, const Okular::PixmapRequest &req )
{
    PixmapRequestPrivate *reqPriv = PixmapRequestPrivate::get(&req);

    str << "PixmapRequest:" << &req;
    str << "- observer:" << (qulonglong)req.observer();
    str << "- page:" << req.pageNumber();
    str << "- width:" << req.width();
    str << "- height:" << req.height();
    str << "- priority:" << req.priority();
    str << "- async:" << ( req.asynchronous() ? "true" : "false" );
    str << "- tile:" << ( req.isTile() ? "true" : "false" );
    str << "- rect:" << req.normalizedRect();
    str << "- preload:" << ( req.preload() ? "true" : "false" );
    str << "- partialUpdates:" << ( req.partialUpdatesWanted() ? "true" : "false" );
    str << "- shouldAbort:" << ( req.shouldAbortRender() ? "true" : "false" );
    str << "- force:" << ( reqPriv->mForce ? "true" : "false" );
    return str;
}

/* kate: replace-tabs on; indent-width 4; */
