/**
 * Copyright 2009  Michael Leupold <lemma@confuego.org>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) version 3, or any
 * later version accepted by the membership of KDE e.V. (or its
 * successor approved by the membership of KDE e.V.), which shall
 * act as a proxy defined in Section 6 of version 3 of the license.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library.  If not, see <http://www.gnu.org/licenses/>.
 */

#include "pluginxslt.h"
#include "xsltransformation.h"
#include "xmlkioentitiesloader.h"

#include <QtCore/QReadWriteLock>
#include <QtCore/QReadLocker>
#include <QtCore/QTimer>
#include <KAboutData>
#include <KAction>
#include <KActionCollection>
#include <KHTMLPart>
#include <KPluginFactory>
#include <DOM/Element>

#include <KDebug>

static KAboutData createAboutData()
{
    KAboutData aboutData("pluginxslt", "pluginxslt",
                         ki18n("Konqueror XSLT Plugin"), "0.1",
                         ki18n("Enables XSL transformations in KHTML"),
                         KAboutData::License_LGPL,
                         ki18n("Copyright (C) 2009 Michael Leupold"));
    aboutData.addAuthor(ki18n("Michael Leupold"), ki18n("Author"),
                        "lemma@confuego.org");
    return aboutData;
}
K_PLUGIN_FACTORY(PluginXsltFactory, registerPlugin<PluginXslt>();)
K_EXPORT_PLUGIN(PluginXsltFactory(createAboutData()))

// for security reasons do only one transformation at a time
// (security contexts are static)
static QReadWriteLock xsltLock;

class LibXmlStaticCleaner
{
public:
    ~LibXmlStaticCleaner()
    {
        // I'd prefer this to be somewhere else but I haven't found
        // anything in libxml/xslt that makes it easier to use in a
        // plugin/library
        xsltCleanupGlobals();
        xmlCleanupParser();
    }
private:
    static LibXmlStaticCleaner s_cleaner;
};
LibXmlStaticCleaner LibXmlStaticCleaner::s_cleaner;

PluginXslt::PluginXslt(QObject *parent, const QVariantList&)
    : Plugin(parent)
{
/*    QAction *a = actionCollection()->addAction("xslTransform");

    a->setText(i18n("XSL Transformation"));
    connect(a, SIGNAL(triggered()), this, SLOT(slotXslTransform()));*/

    // build list of known IDs
    m_publicIdToXsl["-//OASIS//DTD DocBook XML V4.1.2//EN"] = "http://docbook.sourceforge.net/release/xsl/current/html/docbook.xsl";
    m_xmlnsToXsl["http://docbook.org/ns/docbook"] = "http://docbook.sourceforge.net/release/xsl/current/xhtml/docbook.xsl";

    KHTMLPart *part = qobject_cast<KHTMLPart *>(parent);
    if (part) {
        connect(part, SIGNAL(completed()), this, SLOT(slotXslTransform()));
    }
}

PluginXslt::~PluginXslt()
{
}

QString PluginXslt::findStyleSheetUrl(const DOM::Document &doc) const
{
    QRegExp rxType("^\\s*type\\s*\\=\\s*\\\"text/xsl\\\"\\s*$", Qt::CaseInsensitive);
    QRegExp rxHref("^\\s*href\\s*\\=\\s*\\\"([^\\\"]*)\\\"\\s*$", Qt::CaseInsensitive);
    bool isXsl = false;
    QString styleUrl;
    for (unsigned long i = 0; i < doc.childNodes().length(); ++i) {
        DOM::Node node(doc.childNodes().item(i));
        if (node.nodeType() == DOM::Node::PROCESSING_INSTRUCTION_NODE &&
            node.nodeName() == "xml-stylesheet") {

            QString nodeValue(node.nodeValue().string());
            QList<QString> attributes(nodeValue.split(QRegExp("\\s"),
                                                      QString::SkipEmptyParts));
            Q_FOREACH(const QString &attribute, attributes) {
                if (rxType.exactMatch(attribute)) {
                    isXsl = true;
                } else if (rxHref.exactMatch(attribute)) {
                    styleUrl = rxHref.cap(1);
                }
            }
            // TODO: could contain an additional check about the other parameters
            //       of <?xml-stylesheet?>
            if (isXsl && !styleUrl.isEmpty()) {
                break;
            }
            isXsl = false;
            styleUrl.clear();
        }
    }
    if (styleUrl.isEmpty() && !doc.doctype().isNull()) {
        // determine the style based on the document type
        QString publicId(doc.doctype().publicId().string());
        QString systemId(doc.doctype().systemId().string());
        // try to match systemId first
        if (m_systemIdToXsl.contains(systemId)) {
            return m_systemIdToXsl.value(systemId);
        } else if (m_publicIdToXsl.contains(publicId)) {
            return m_publicIdToXsl.value(publicId);
        }
    }
    if (styleUrl.isEmpty()) {
        // determine the style based on the main xml namespace
        QString xmlns(doc.documentElement().getAttribute("xmlns").string());
        if (m_xmlnsToXsl.contains(xmlns)) {
            return m_xmlnsToXsl.value(xmlns);
        }
    }
    return styleUrl;
}

void PluginXslt::slotXslTransform()
{
    // NOTE: Lock! Unless someone finds a way for proper security with
    //       several transforms running at once.
    QReadLocker locker(&xsltLock);
    
    KHTMLPart *part = qobject_cast<KHTMLPart *>(parent());
    if (!part) {
        return;
    }
    
    DOM::Document doc(part->document());

    // don't handle HTML documents
    if (doc.isHTMLDocument()) {
        return;
    }

    // install custom entity loader
    KioEntityLoaderInstaller loaderInstaller;

    // FIXME: if this loads a style from the web, it is asynchronous.
    //        it will most likely crash if the part vanishes while
    //        the style is being processed.

    KUrl styleUrl = findStyleSheetUrl(doc);
    if (styleUrl.isRelative()) {
        KUrl newStyleUrl = part->url();
        newStyleUrl.setFileName(styleUrl.toEncoded());
        styleUrl = newStyleUrl;
    }

    XslTransformation transformation;
    if (transformation.setInput(part->url(), part->documentSource()) &&
        transformation.loadStylesheet(styleUrl) &&
        transformation.transform() &&
        !transformation.hasError()) {
        
        // set proper output mimetype
        KParts::OpenUrlArguments args = part->arguments();
        args.setMimeType(transformation.mimeType());
        part->setArguments(args);

        part->begin(part->url());
        part->write(transformation.output());
        part->end();
    } else {
        // TODO: display this in some gui
        QList<TransformationError> errors(transformation.errors());
        Q_FOREACH(const TransformationError &error, errors) {
            kDebug() << "m_errorType" << error.m_errorType;
            kDebug() << "m_file" << error.m_file;
            kDebug() << "m_message" << error.m_message;
        }
    }
}

#include "pluginxslt.moc"
