/*
    SPDX-FileCopyrightText: 2002 Frerich Raabe <raabe@kde.org>

    SPDX-License-Identifier: GPL-2.0-or-later
*/

#include "toc.h"

#include "docentry.h"
#include "khc_debug.h"

#include <QApplication>
#include <QFileInfo>
#include <QIcon>
#include <QStandardPaths>
#include <QTextStream>

#include <KProcess>
#include <KXmlGuiWindow>
#include <docbookxslt.h>

#include <sys/stat.h>

using namespace KHC;

class TOCItem : public NavigatorItem
{
    public:
        TOCItem( TOC *parent, QTreeWidgetItem *parentItem, QTreeWidgetItem *after, const QString &text );

        const TOC *toc() const { return m_toc; }

    private:
        TOC *m_toc = nullptr;
};

class TOCChapterItem : public TOCItem
{
    public:
        TOCChapterItem( TOC *toc, NavigatorItem *parent, QTreeWidgetItem *after, const QString &title,
                const QString &name );

        virtual QString url();

    private:
        QString m_name;
};

class TOCSectionItem : public TOCItem
{
    public:
        TOCSectionItem( TOC *toc, TOCChapterItem *parent, QTreeWidgetItem *after, const QString &title,
                const QString &name );

        virtual QString url();

    private:
        QString m_name;
};

bool TOC::m_alreadyWarned = false;

TOC::TOC( NavigatorItem *parentItem )
    : m_parentItem(parentItem)
{
}

void TOC::build( const QString &file )
{
    QFileInfo fileInfo( file );
    QString fileName = fileInfo.absoluteFilePath();
    const QStringList resourceDirs = KDocTools::documentationDirs();
    QStringList::ConstIterator it = resourceDirs.begin();
    QStringList::ConstIterator end = resourceDirs.end();
    for ( ; it != end; ++it ) {
        if ( fileName.startsWith( *it ) ) {
            fileName.remove( 0, ( *it ).length() );
            break;
        }
    }

    QString cacheFile = fileName.replace( QLatin1Char('/'), QStringLiteral("__") );
#ifdef Q_WS_WIN
    cacheFile.replace( QLatin1Char(':'), QStringLiteral("_") );
#endif
    m_cacheFile = QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + QStringLiteral("/help/") + cacheFile ;
    m_sourceFile = file;

    if ( cacheStatus() == NeedRebuild )
        buildCache();
    else
        fillTree();
}

TOC::CacheStatus TOC::cacheStatus() const
{
    if ( !QFile::exists( m_cacheFile ) ||
         sourceFileCTime() != cachedCTime() )
        return NeedRebuild;

    return CacheOk;
}

int TOC::sourceFileCTime() const
{
    struct stat stat_buf;
    stat( QFile::encodeName( m_sourceFile ).data(), &stat_buf );

    return stat_buf.st_ctime;
}

int TOC::cachedCTime() const
{
    QFile f( m_cacheFile );
    if ( !f.open( QIODevice::ReadOnly ) )
        return 0;

    QDomDocument doc;
    if ( !doc.setContent( &f ) )
        return 0;

    QDomComment timestamp = doc.documentElement().lastChild().toComment();

    return timestamp.data().trimmed().toInt();
}

void TOC::buildCache()
{
    KXmlGuiWindow *mainWindow = dynamic_cast<KXmlGuiWindow *>( qobject_cast<QApplication*>(qApp)->activeWindow() );

    KProcess *meinproc = new KProcess;
    connect(meinproc, QOverload<int, QProcess::ExitStatus>::of(&KProcess::finished), this, &TOC::meinprocExited);

    *meinproc << QStandardPaths::findExecutable(QStringLiteral( "meinproc6" ) );
    *meinproc << QStringLiteral("--stylesheet") << QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("khelpcenter/table-of-contents.xslt") );
    *meinproc << QStringLiteral("--output") << m_cacheFile;
    *meinproc << m_sourceFile;

    meinproc->setOutputChannelMode(KProcess::OnlyStderrChannel);
    meinproc->start();
    if (!meinproc->waitForStarted()) {
        qCWarning(KHC_LOG) << "could not start process" << meinproc->program();
        if (mainWindow && !m_alreadyWarned) {
            ; // add warning message box with don't display again option 
            m_alreadyWarned = true;
        }
        delete meinproc;
    }
}

void TOC::meinprocExited( int exitCode, QProcess::ExitStatus exitStatus)
{
    KProcess *meinproc = static_cast<KProcess *>(sender());
    KXmlGuiWindow *mainWindow = dynamic_cast<KXmlGuiWindow *>( qobject_cast<QApplication*>(qApp)->activeWindow() );

    if ( exitStatus == QProcess::CrashExit || exitCode != 0 ) {
        qCWarning(KHC_LOG) << "running" << meinproc->program() << "failed with exitCode" << exitCode;
        qCWarning(KHC_LOG) << "stderr output:" << meinproc->readAllStandardError();
        if (mainWindow && !m_alreadyWarned) {
            ; // add warning message box with don't display again option 
            m_alreadyWarned = true;
        }
        delete meinproc;
        return;
    }

    delete meinproc;

    // add a timestamp to the meinproc4 created xml file
    QFile f( m_cacheFile );
    if ( !f.open( QIODevice::ReadWrite ) )
        return;

    QDomDocument doc;
    if ( !doc.setContent( &f ) )
        return;

    QDomComment timestamp = doc.createComment( QString::number( sourceFileCTime() ) );
    doc.documentElement().appendChild( timestamp );

    // write back updated xml content 
    f.seek( 0 );
    QTextStream stream( &f );
#ifdef Q_WS_WIN
    /*
      the problem that on german systems umlauts are displayed as '?' for unknown (Qt'r related ?) reasons
      is caused by wrong encoding type conversations and has been fixed in kdelibs/kdoctools
      To have propper encoding tags in the xml file, QXmlDocument::save() is used. 
    */
    doc.save(stream, 1, QDomNode::EncodingFromTextStream);

#else
    stream << doc.toString();
#endif
    f.close();
    fillTree();
}

void TOC::fillTree()
{
    QFile f( m_cacheFile );
    if ( !f.open( QIODevice::ReadOnly ) )
        return;

    QDomDocument doc;
    if ( !doc.setContent( &f ) )
        return;

    TOCChapterItem *chapItem = nullptr;
    QDomNodeList chapters = doc.documentElement().elementsByTagName( QStringLiteral("chapter") );
    for ( int chapterCount = 0; chapterCount < chapters.count(); chapterCount++ ) {
        QDomElement chapElem = chapters.item( chapterCount ).toElement();
        QDomElement chapTitleElem = childElement( chapElem, QStringLiteral( "title" ) );
        QString chapTitle = chapTitleElem.text().simplified();
        QDomElement chapRefElem = childElement( chapElem, QStringLiteral( "anchor" ) );
        QString chapRef = chapRefElem.text().trimmed();

        chapItem = new TOCChapterItem( this, m_parentItem, chapItem, chapTitle, chapRef );

        TOCSectionItem *sectItem = nullptr;
        QDomNodeList sections = chapElem.elementsByTagName( QStringLiteral("section") );
        for ( int sectCount = 0; sectCount < sections.count(); sectCount++ ) {
            QDomElement sectElem = sections.item( sectCount ).toElement();
            QDomElement sectTitleElem = childElement( sectElem, QStringLiteral( "title" ) );
            const QString sectTitle = sectTitleElem.text().simplified();
            QDomElement sectRefElem = childElement( sectElem, QStringLiteral( "anchor" ) );
            const QString sectRef = sectRefElem.text().trimmed();

            sectItem = new TOCSectionItem( this, chapItem, sectItem, sectTitle, sectRef );
        }
    }
}

QDomElement TOC::childElement( const QDomElement &element, const QString &name )
{
    QDomElement e;
    for ( e = element.firstChild().toElement(); !e.isNull(); e = e.nextSibling().toElement() )
        if ( e.tagName() == name )
            break;
    return e;
}

void TOC::slotItemSelected( QTreeWidgetItem *item )
{
    TOCItem *tocItem;
    if ( ( tocItem = dynamic_cast<TOCItem *>( item ) ) )
        Q_EMIT itemSelected( tocItem->entry()->url() );

    item->setExpanded( !item->isExpanded() );
}

TOCItem::TOCItem( TOC *toc, QTreeWidgetItem *parentItem, QTreeWidgetItem *after, const QString &text )
    : NavigatorItem( new DocEntry( text ), parentItem, after )
{
        setAutoDeleteDocEntry( true );
    m_toc = toc;
}

TOCChapterItem::TOCChapterItem( TOC *toc, NavigatorItem *parent, QTreeWidgetItem *after,
                const QString &title, const QString &name )
    : TOCItem( toc, parent, after, title ),
    m_name( name )
{
    setExpanded( false );
    entry()->setUrl(url());
}

QString TOCChapterItem::url()
{
    return QLatin1String("help:") + toc()->application() + QLatin1Char('/') + m_name 
               + QLatin1String(".html");
}

TOCSectionItem::TOCSectionItem( TOC *toc, TOCChapterItem *parent, QTreeWidgetItem *after,
                const QString &title, const QString &name )
    : TOCItem( toc, parent, after, title ),
    m_name( name )
{
    setIcon( 0, QIcon::fromTheme( QStringLiteral("text-plain") ) );
    entry()->setUrl(url());
}

QString TOCSectionItem::url()
{
    if ( static_cast<TOCSectionItem *>( parent()->child(0) ) == this )
        return static_cast<TOCChapterItem *>( parent() )->url() + QLatin1Char('#') + m_name;

    return QStringLiteral("help:") + toc()->application() + QLatin1Char('/') + m_name + QStringLiteral(".html");
}

#include "moc_toc.cpp"

// vim:ts=2:sw=2:et

