/***************************************************************************
 *   Copyright (C) 2006-2009 by Luigi Toscano <luigi.toscano@tiscali.it>   *
 *                                                                         *
 *   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 <core/action.h>
#include <core/document.h>
#include <core/fileprinter.h>
#include <core/page.h>
#include <core/sourcereference.h>
#include <core/textpage.h>

#include "generator_dvi.h"
#include "debug_dvi.h"
#include "dviFile.h"
#include "dviPageInfo.h"
#include "dviRenderer.h"
#include "pageSize.h"
#include "dviexport.h"
#include "TeXFont.h"

#include <QApplication>
#include <QDir>
#include <QString>
#include <QUrl>
#include <QVector>
#include <QStack>
#include <QTemporaryFile>
#include <QMutex>

#include <KAboutData>
#include <QDebug>
#include <KLocalizedString>

#ifdef DVI_OPEN_BUSYLOOP
#include <QThread>
#endif

OKULAR_EXPORT_PLUGIN(DviGenerator, "libokularGenerator_dvi.json")

DviGenerator::DviGenerator( QObject *parent, const QVariantList &args ) : Okular::Generator( parent, args ),
  m_fontExtracted( false ), m_docSynopsis( nullptr ), m_dviRenderer( nullptr )
{
    setFeature( Threaded );
    setFeature( TextExtraction );
    setFeature( FontInfo );
    setFeature( PrintPostscript );
    if ( Okular::FilePrinter::ps2pdfAvailable() )
        setFeature( PrintToFile );
}

bool DviGenerator::loadDocument( const QString & fileName, QVector< Okular::Page * > &pagesVector )
{
    //qCDebug(OkularDviDebug) << "file:" << fileName;
    QUrl base( QUrl::fromLocalFile(fileName) );

    (void)userMutex();

    m_dviRenderer = new dviRenderer(documentMetaData(TextHintingMetaData, QVariant()).toBool());
    connect(m_dviRenderer, &dviRenderer::error, this, &DviGenerator::error);
    connect(m_dviRenderer, &dviRenderer::warning, this, &DviGenerator::warning);
    connect(m_dviRenderer, &dviRenderer::notice, this, &DviGenerator::notice);
#ifdef DVI_OPEN_BUSYLOOP
    static const ushort s_waitTime = 800; // milliseconds
    static const int s_maxIterations = 10;
    int iter = 0;
    for ( ; !m_dviRenderer->isValidFile( fileName ) && iter < s_maxIterations; ++iter )
    {
        qCDebug(OkularDviDebug).nospace() << "file not valid after iteration #" << iter << "/" << s_maxIterations << ", waiting for " << s_waitTime;
        QThread::msleep(s_waitTime);
    }
    if ( iter >= s_maxIterations && !m_dviRenderer->isValidFile( fileName ) )
    {
        qCDebug(OkularDviDebug) << "file still not valid after" << s_maxIterations;
        delete m_dviRenderer;
        m_dviRenderer = 0;
        return false;
    }
#else
    if ( !m_dviRenderer->isValidFile( fileName ) )
    {
        delete m_dviRenderer;
        m_dviRenderer = nullptr;
        return false;
    }
#endif
    if ( ! m_dviRenderer->setFile( fileName, base ) )
    {
        delete m_dviRenderer;
        m_dviRenderer = nullptr;
        return false;
    }

    qCDebug(OkularDviDebug) << "# of pages:" << m_dviRenderer->dviFile->total_pages;

    m_resolution = dpi().height();
    loadPages( pagesVector );

    return true;
}

bool DviGenerator::doCloseDocument()
{
    delete m_docSynopsis;
    m_docSynopsis = nullptr;
    delete m_dviRenderer;
    m_dviRenderer = nullptr;

    m_linkGenerated.clear();
    m_fontExtracted = false;

    return true;
}

void DviGenerator::fillViewportFromAnchor( Okular::DocumentViewport &vp,
                                           const Anchor anch, const Okular::Page *page ) const
{
    fillViewportFromAnchor( vp, anch, page->width(), page->height() );
}

void DviGenerator::fillViewportFromAnchor( Okular::DocumentViewport &vp,
                                           const Anchor anch, int pW, int pH ) const
{
    vp.pageNumber = anch.page - 1;


    SimplePageSize ps = m_dviRenderer->sizeOfPage( vp.pageNumber );
    double resolution = 0;
    if (ps.isValid()) resolution = (double)(pW)/ps.width().getLength_in_inch();
    else resolution = m_resolution;

    double py = (double)anch.distance_from_top.getLength_in_inch()*resolution + 0.5; 
 
    vp.rePos.normalizedX = 0.5;
    vp.rePos.normalizedY = py/(double)pH;
    vp.rePos.enabled = true;
    vp.rePos.pos = Okular::DocumentViewport::Center;
}

QLinkedList<Okular::ObjectRect*> DviGenerator::generateDviLinks( const dviPageInfo *pageInfo )
{
    QLinkedList<Okular::ObjectRect*> dviLinks;

    int pageWidth = pageInfo->width, pageHeight = pageInfo->height;
    
    for ( const Hyperlink &dviLink : qAsConst(pageInfo->hyperLinkList) )
    {
        QRect boxArea = dviLink.box;
        double nl = (double)boxArea.left() / pageWidth,
               nt = (double)boxArea.top() / pageHeight,
               nr = (double)boxArea.right() / pageWidth,
               nb = (double)boxArea.bottom() / pageHeight;
        
        QString linkText = dviLink.linkText;
        if ( linkText.startsWith(QLatin1String("#")) )
            linkText = linkText.mid( 1 );
        Anchor anch = m_dviRenderer->findAnchor( linkText );

        Okular::Action *okuLink = nullptr;

        /* distinguish between local (-> anchor) and remote links */
        if (anch.isValid())
        {
            /* internal link */
            Okular::DocumentViewport vp;
            fillViewportFromAnchor( vp, anch, pageWidth, pageHeight );

            okuLink = new Okular::GotoAction( QLatin1String(""), vp );
        }
        else
        {
            okuLink = new Okular::BrowseAction( QUrl::fromUserInput( dviLink.linkText ) );
        }
        if ( okuLink ) 
        {
            Okular::ObjectRect *orlink = new Okular::ObjectRect( nl, nt, nr, nb, 
                                        false, Okular::ObjectRect::Action, okuLink );
            dviLinks.push_front( orlink );
        }

    }
    return dviLinks; 
}

QImage DviGenerator::image( Okular::PixmapRequest *request )
{

    dviPageInfo *pageInfo = new dviPageInfo();
    pageSize ps;
    QImage ret;

    pageInfo->width = request->width();
    pageInfo->height = request->height();

    pageInfo->pageNumber = request->pageNumber() + 1;

//  pageInfo->resolution = m_resolution;

    QMutexLocker lock( userMutex() );

    if ( m_dviRenderer )
    {
        SimplePageSize s = m_dviRenderer->sizeOfPage( pageInfo->pageNumber );

/*       if ( s.width() != pageInfo->width) */
        //   if (!useDocumentSpecifiedSize)
        //    s = userPreferredSize;

        if (s.isValid())
        {
            pageInfo->resolution = (double)(pageInfo->width)/s.width().getLength_in_inch();
        }
        else
        {
            pageInfo->resolution = (double)(pageInfo->width)/ps.width().getLength_in_inch();
        }

        m_dviRenderer->drawPage( pageInfo );

        if ( ! pageInfo->img.isNull() )
        {
            qCDebug(OkularDviDebug) << "Image OK";

            ret = pageInfo->img;

            if ( !m_linkGenerated[ request->pageNumber() ] )
            {
                request->page()->setObjectRects( generateDviLinks( pageInfo ) );
                m_linkGenerated[ request->pageNumber() ] = true;
            }
        }
    }

    lock.unlock();

    delete pageInfo;

    return ret;
}

Okular::TextPage* DviGenerator::textPage( Okular::TextRequest *request )
{
    const Okular::Page *page = request->page();

    qCDebug(OkularDviDebug);
    dviPageInfo *pageInfo = new dviPageInfo();
    pageSize ps;
    
    pageInfo->width=page->width();
    pageInfo->height=page->height();

    pageInfo->pageNumber = page->number() + 1;

    pageInfo->resolution = m_resolution;

    QMutexLocker lock( userMutex() );

    // get page text from m_dviRenderer
    Okular::TextPage *ktp = nullptr;
    if ( m_dviRenderer )
    {
        SimplePageSize s = m_dviRenderer->sizeOfPage( pageInfo->pageNumber );
        pageInfo->resolution = (double)(pageInfo->width)/ps.width().getLength_in_inch();

        m_dviRenderer->getText( pageInfo );
        lock.unlock();

        ktp = extractTextFromPage( pageInfo );
    }
    delete pageInfo;
    return ktp;
}

Okular::TextPage *DviGenerator::extractTextFromPage( dviPageInfo *pageInfo )
{
    QList<Okular::TextEntity*> textOfThePage;

    QVector<TextBox>::ConstIterator it = pageInfo->textBoxList.constBegin();
    QVector<TextBox>::ConstIterator itEnd = pageInfo->textBoxList.constEnd();

    int pageWidth = pageInfo->width, pageHeight = pageInfo->height;

    for ( ; it != itEnd ; ++it )
    {
        TextBox curTB = *it;
 
#if 0
        qCDebug(OkularDviDebug) << "orientation: " << orientation
                 << ", curTB.box: " << curTB.box
                 << ", ( " << pageWidth << "," << pageHeight << " )" 
               <<endl;
#endif
        textOfThePage.push_back( new Okular::TextEntity( curTB.text,
              new Okular::NormalizedRect( curTB.box, pageWidth, pageHeight ) ) );
    }

    Okular::TextPage* ktp = new Okular::TextPage( textOfThePage );

    return ktp;
}

Okular::DocumentInfo DviGenerator::generateDocumentInfo( const QSet<Okular::DocumentInfo::Key> &keys ) const
{
    Okular::DocumentInfo docInfo;

    if ( keys.contains( Okular::DocumentInfo::MimeType ) )
        docInfo.set( Okular::DocumentInfo::MimeType, QStringLiteral("application/x-dvi") );

    QMutexLocker lock( userMutex() );

    if ( m_dviRenderer && m_dviRenderer->dviFile )
    {
        dvifile *dvif = m_dviRenderer->dviFile;

        // read properties from dvif
        //docInfo.set( "filename", dvif->filename, i18n("Filename") );
        if ( keys.contains( Okular::DocumentInfo::CustomKeys ) )
            docInfo.set( QStringLiteral("generatorDate"), dvif->generatorString, i18n("Generator/Date") );
        if ( keys.contains( Okular::DocumentInfo::Pages ) )
            docInfo.set( Okular::DocumentInfo::Pages, QString::number( dvif->total_pages ) );
    }
    return docInfo;
}

const Okular::DocumentSynopsis *DviGenerator::generateDocumentSynopsis()
{
    if ( m_docSynopsis )
        return m_docSynopsis;

    m_docSynopsis = new Okular::DocumentSynopsis();

    userMutex()->lock();

    QVector<PreBookmark> prebookmarks = m_dviRenderer->getPrebookmarks();

    userMutex()->unlock();

    if ( prebookmarks.isEmpty() ) 
        return m_docSynopsis;

    QStack<QDomElement> stack;

    QVector<PreBookmark>::ConstIterator it = prebookmarks.constBegin();
    QVector<PreBookmark>::ConstIterator itEnd = prebookmarks.constEnd();
    for( ; it != itEnd; ++it ) 
    {
        QDomElement domel = m_docSynopsis->createElement( (*it).title );
        Anchor a = m_dviRenderer->findAnchor((*it).anchorName);
        if ( a.isValid() )
        {
            Okular::DocumentViewport vp;
 
            const Okular::Page *p = document()->page( a.page - 1 );

            fillViewportFromAnchor( vp, a, (int)p->width(), (int)p->height() );
            domel.setAttribute( QStringLiteral("Viewport"), vp.toString() );
        }
        if ( stack.isEmpty() )
            m_docSynopsis->appendChild( domel );
        else 
        {
            stack.top().appendChild( domel );
            stack.pop();
        }
        for ( int i = 0; i < (*it).noOfChildren; ++i )
            stack.push( domel );
    }

    return m_docSynopsis;
}

Okular::FontInfo::List DviGenerator::fontsForPage( int page )
{
    Q_UNUSED( page );

    Okular::FontInfo::List list;

    // the list of the fonts is extracted once 
    if ( m_fontExtracted )
        return list;

    if ( m_dviRenderer && m_dviRenderer->dviFile &&
         m_dviRenderer->dviFile->font_pool )
    {
        const QList<TeXFontDefinition *> fonts = m_dviRenderer->dviFile->font_pool->fontList;
        for (const TeXFontDefinition *font : fonts)
        {
            Okular::FontInfo of;
            QString name;
            int zoom = (int)(font->enlargement*100 + 0.5);
#ifdef HAVE_FREETYPE
            if ( font->getFullFontName().isEmpty() ) 
            {
                name = QStringLiteral( "%1, %2%" )
                        .arg( font->fontname )
                        .arg( zoom );
            }
            else
            {
                name = QStringLiteral( "%1 (%2), %3%" ) 
                        .arg( font->fontname, font->getFullFontName(), QString::number(zoom) ); 
            }
#else
            name = QString( "%1, %2%" )
                    .arg( font->fontname )
                    .arg( zoom );
#endif
            of.setName( name );

            QString fontFileName;
            if (!(font->flags & TeXFontDefinition::FONT_VIRTUAL)) {
                if ( font->font != nullptr )
                    fontFileName = font->font->errorMessage;
                else
                    fontFileName = i18n("Font file not found");

                if ( fontFileName.isEmpty() )
                    fontFileName = font->filename;
            }

            of.setFile( fontFileName );

            Okular::FontInfo::FontType ft;
            switch ( font->getFontType() )
            {
                case TeXFontDefinition::TEX_PK:
                    ft = Okular::FontInfo::TeXPK;
                    break;
                case TeXFontDefinition::TEX_VIRTUAL:
                    ft = Okular::FontInfo::TeXVirtual;
                    break;
                case TeXFontDefinition::TEX_FONTMETRIC:
                    ft = Okular::FontInfo::TeXFontMetric;
                    break;
                case TeXFontDefinition::FREETYPE:
                    ft = Okular::FontInfo::TeXFreeTypeHandled;
                    break;
            }
            of.setType( ft );

            // DVI has not the concept of "font embedding"
            of.setEmbedType( Okular::FontInfo::NotEmbedded );
            of.setCanBeExtracted( false );

            list.append( of );
        }

        m_fontExtracted = true;

    }

    return list;
}

void DviGenerator::loadPages( QVector< Okular::Page * > &pagesVector )
{
    QSize pageRequiredSize;

    int numofpages = m_dviRenderer->dviFile->total_pages;
    pagesVector.resize( numofpages );

    m_linkGenerated.fill( false, numofpages );

    //qCDebug(OkularDviDebug) << "resolution:" << m_resolution << ", dviFile->preferred?";

    /* get the suggested size */
    if ( m_dviRenderer->dviFile->suggestedPageSize )
    {
        pageRequiredSize = m_dviRenderer->dviFile->suggestedPageSize->sizeInPixel(
               m_resolution );
    }
    else
    {
        pageSize ps;
        pageRequiredSize = ps.sizeInPixel( m_resolution );
    }

    for ( int i = 0; i < numofpages; ++i )
    {
        //qCDebug(OkularDviDebug) << "getting status of page" << i << ":";

        if ( pagesVector[i] )
        {
            delete pagesVector[i];
        }

        Okular::Page * page = new Okular::Page( i,
                                        pageRequiredSize.width(),
                                        pageRequiredSize.height(),
                                        Okular::Rotation0 );

        pagesVector[i] = page;
    }
    qCDebug(OkularDviDebug) << "pagesVector successfully inizialized!";

    // filling the pages with the source references rects
    const QVector<DVI_SourceFileAnchor>& sourceAnchors = m_dviRenderer->sourceAnchors();
    QVector< QLinkedList< Okular::SourceRefObjectRect * > > refRects( numofpages );
    for ( const DVI_SourceFileAnchor &sfa : sourceAnchors )
    {
        if ( sfa.page < 1 || (int)sfa.page > numofpages )
            continue;

        Okular::NormalizedPoint p( -1.0, (double)sfa.distance_from_top.getLength_in_pixel( dpi().height() ) / (double)pageRequiredSize.height() );
        Okular::SourceReference * sourceRef = new Okular::SourceReference( sfa.fileName, sfa.line );
        refRects[ sfa.page - 1 ].append( new Okular::SourceRefObjectRect( p, sourceRef ) );
    }
    for ( int i = 0; i < refRects.size(); ++i )
        if ( !refRects.at(i).isEmpty() )
            pagesVector[i]->setSourceReferences( refRects.at(i) );
}

bool DviGenerator::print( QPrinter& printer )
{
    // Create tempfile to write to
    QTemporaryFile tf(QDir::tempPath() + QLatin1String("/okular_XXXXXX.ps" ));
    if ( !tf.open() )
        return false;

    const QList<int> pageList = Okular::FilePrinter::pageList( printer, 
                                 m_dviRenderer->totalPages(),
                                 document()->currentPage() + 1,
                                 document()->bookmarkedPageList() );
    QString pages;
    QStringList printOptions;
    // List of pages to print.
    for ( const int p : pageList )
    {
        pages += QStringLiteral(",%1").arg(p);
    }
    if ( !pages.isEmpty() )
        printOptions << QStringLiteral("-pp") << pages.mid(1);

    QEventLoop el;
    m_dviRenderer->setEventLoop( &el );
    m_dviRenderer->exportPS( tf.fileName(), printOptions, &printer, document()->orientation() );

    tf.close();

    // Error messages are handled by the generator - ugly, but it works.
    return true; 
}

QVariant DviGenerator::metaData( const QString & key, const QVariant & option ) const
{
    if ( key == QLatin1String("NamedViewport") && !option.toString().isEmpty() )
    {
        const Anchor anchor = m_dviRenderer->parseReference( option.toString() );
        if ( anchor.isValid() )
        {
            const Okular::Page *page = document()->page( anchor.page - 1 );
            Q_ASSERT_X( page, "DviGenerator::metaData()", "NULL page as result of valid Anchor" );
            Okular::DocumentViewport viewport;
            fillViewportFromAnchor( viewport, anchor, page );
            if ( viewport.isValid() )
            {
                return viewport.toString();
            }
        }
    }
    return QVariant();
}

Q_LOGGING_CATEGORY(OkularDviDebug, "org.kde.okular.generators.dvi.core", QtWarningMsg)
Q_LOGGING_CATEGORY(OkularDviShellDebug, "org.kde.okular.generators.dvi.shell", QtWarningMsg)

#include "generator_dvi.moc"
