/* This file is part of the KDE project

   SPDX-FileCopyrightText: 2012 Mojtaba Shahi Senobari <mojtaba.shahi3000@gmail.com>
   SPDX-FileCopyrightText: 2012 Inge Wallin <inge@lysator.liu.se>

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

// Own
#include "exporthtml.h"

// Qt
#include <QBuffer>
#include <QPainter>
#include <QSvgGenerator>

// KF5
#include <KPluginFactory>

// Calligra
#include <KoFilterChain.h>
#include <KoXmlNS.h>
#include <KoXmlReader.h>

// This plugin
#include "HtmlExportDebug.h"
#include "HtmlFile.h"
#include "OdfParser.h"
#include "OdtHtmlConverter.h"

#include "WmfPainterBackend.h"

#include "EmfOutputPainterStrategy.h"
#include "EmfParser.h"
#include "SvmPainterBackend.h"
#include "SvmParser.h"

K_PLUGIN_FACTORY_WITH_JSON(ExportHtmlFactory, "calligra_filter_odt2html.json", registerPlugin<ExportHtml>();)

// Needed to instantiate the plugin factory.
#include "exporthtml.moc"

ExportHtml::ExportHtml(QObject *parent, const QVariantList &)
    : KoFilter(parent)
{
}

ExportHtml::~ExportHtml() = default;

KoFilter::ConversionStatus ExportHtml::convert(const QByteArray &from, const QByteArray &to)
{
    // Check mimetypes
    if (from != "application/vnd.oasis.opendocument.text" || to != "text/html") {
        return KoFilter::NotImplemented;
    }

    // Open the infile and return an error if it fails.
    KoStore *odfStore = KoStore::createStore(m_chain->inputFile(), KoStore::Read, "", KoStore::Auto);
    if (!odfStore->open("mimetype")) {
        errorHtml << "Unable to open input file!" << Qt::endl;
        delete odfStore;
        return KoFilter::FileNotFound;
    }
    odfStore->close();

    // Start the conversion
    KoFilter::ConversionStatus status;

    // ----------------------------------------------------------------
    // Parse input files

    OdfParser odfParser;

    // Parse meta.xml into m_metadata
    status = odfParser.parseMetadata(odfStore, m_metadata);
    if (status != KoFilter::OK) {
        delete odfStore;
        return status;
    }

    // Parse manifest
    status = odfParser.parseManifest(odfStore, m_manifest);
    if (status != KoFilter::OK) {
        delete odfStore;
        return status;
    }

    // ----------------------------------------------------------------
    // Create content files.

    // Create html contents.
    // m_imagesSrcList is an output parameter from the conversion.
    HtmlFile html;
    html.setPathPrefix("./");
    const QString outputFileName = m_chain->outputFile().section('/', -1);
    const int dotPosition = outputFileName.indexOf('.');
    html.setFilePrefix(outputFileName.left(dotPosition));
    html.setFileSuffix(dotPosition != -1 ? outputFileName.mid(dotPosition) : QString());
    OdtHtmlConverter converter;
    OdtHtmlConverter::ConversionOptions options = {
        false, // don't put styles in css file
        false, // don't break into chapters
        false // It is not mobi.
    };
    QHash<QString, QString> mediaFilesList;
    status = converter.convertContent(odfStore, m_metadata, &m_manifest, &options, &html, m_imagesSrcList, mediaFilesList);
    if (status != KoFilter::OK) {
        delete odfStore;
        return status;
    }

    // Extract images
    status = extractImages(odfStore, &html);
    if (status != KoFilter::OK) {
        delete odfStore;
        return status;
    }

    // ----------------------------------------------------------------
    // Write the finished html file to disk

    html.writeHtml(m_chain->outputFile());

    delete odfStore;

    return KoFilter::OK;
}

KoFilter::ConversionStatus ExportHtml::extractImages(KoStore *odfStore, HtmlFile *htmlFile)
{
    // Extract images and add them to htmlFile one by one
    QByteArray imgContent;
    int imgId = 1;
    foreach (const QString &imgSrc, m_imagesSrcList.keys()) {
        debugHtml << imgSrc;
        if (!odfStore->extractFile(imgSrc, imgContent)) {
            debugHtml << "Can not to extract file";
            return KoFilter::FileNotFound;
        }

#if 1
        htmlFile->addContentFile(("image" + QString::number(imgId)), // id
                                 (htmlFile->filePrefix() + imgSrc.section('/', -1)), // filename
                                 m_manifest.value(imgSrc).toUtf8(),
                                 imgContent);
#else

        VectorType type = vectorType(imgContent);
        QSizeF qSize = m_imagesSrcList.value(imgSrc);
        switch (type) {
        case ExportHtml::VectorTypeSvm: {
            debugHtml << "Svm file";
            QSize size(qSize.width(), qSize.height());
            QByteArray output;
            if (!convertSvm(imgContent, output, size)) {
                debugHtml << "Svm Parse error";
                return KoFilter::ParsingError;
            }

            epubFile->addContentFile(("image" + QString::number(imgId)), (epubFile->pathPrefix() + imgSrc.section('/', -1)), "image/svg+xml", output);
            break;
        }
        case ExportHtml::VectorTypeEmf: {
            debugHtml << "EMF file";
            QSize size(qSize.width(), qSize.height());
            QByteArray output;
            if (!convertEmf(imgContent, output, size)) {
                debugHtml << "EMF Parse error";
                return KoFilter::ParsingError;
            }

            epubFile->addContentFile(("image" + QString::number(imgId)), (epubFile->pathPrefix() + imgSrc.section('/', -1)), "image/svg+xml", output);
            break;
        }
        case ExportHtml::VectorTypeWmf: {
            debugHtml << "WMF file";
            QByteArray output;
            if (!convertWmf(imgContent, output, qSize)) {
                debugHtml << "WMF Parse error";
                return KoFilter::ParsingError;
            }

            epubFile->addContentFile(("image" + QString::number(imgId)), (epubFile->pathPrefix() + imgSrc.section('/', -1)), "image/svg+xml", output);
            break;
        }

            // If it's not one of the types we can convert, let's just
            // assume that the image can be used as it is. The user
            // will find out soon anyway when s/he tries to look at
            // the image.
        case ExportHtml::VectorTypeOther: {
            debugHtml << "Other file";
            epubFile->addContentFile(("image" + QString::number(imgId)),
                                     (epubFile->pathPrefix() + imgSrc.section('/', -1)),
                                     m_manifest.value(imgSrc).toUtf8(),
                                     imgContent);
            break;
        }

        default:
            debugHtml << "";
        }
#endif
    }
    return KoFilter::OK;
}

#if 0

bool ExportHtml::convertSvm(QByteArray &input, QByteArray &output, QSize size)
{

    QBuffer *outBuf = new QBuffer(&output);
    QSvgGenerator generator;
    generator.setOutputDevice(outBuf);
    generator.setSize(QSize(200, 200));
    generator.setTitle("Svg image");
    generator.setDescription("This is an svg image that is converted from svm by Calligra");

    Libsvm::SvmParser  svmParser;

    QPainter painter;

    if (!painter.begin(&generator)) {
        debugHtml << "Can not open the painter";
        return false;
    }

    painter.scale(50,50);
    Libsvm::SvmPainterBackend svmPainterBackend(&painter, size);
    svmParser.setBackend(&svmPainterBackend);
    if (!svmParser.parse(input)) {
        debugHtml << "Can not Parse the Svm file";
        return false;
    }
    painter.end();

    return true;
}

bool ExportHtml::convertEmf(QByteArray &input, QByteArray &output, QSize size)
{
    QBuffer *outBuf = new QBuffer(&output);
    QSvgGenerator generator;
    generator.setOutputDevice(outBuf);
    generator.setSize(QSize(200, 200));
    generator.setTitle("Svg image");
    generator.setDescription("This is an svg image that is converted from EMF by Calligra");

    Libemf::Parser  emfParser;

    QPainter painter;

    if (!painter.begin(&generator)) {
        debugHtml << "Can not open the painter";
        return false;
    }

    painter.scale(50,50);
    Libemf::OutputPainterStrategy  emfPaintOutput(painter, size, true );
    emfParser.setOutput( &emfPaintOutput );
    if (!emfParser.load(input)) {
        debugHtml << "Can not Parse the EMF file";
        return false;
    }
    painter.end();

    return true;
}

bool ExportHtml::convertWmf(QByteArray &input, QByteArray &output, QSizeF size)
{
    QBuffer *outBuf = new QBuffer(&output);
    QSvgGenerator generator;
    generator.setOutputDevice(outBuf);
    generator.setSize(QSize(200, 200));
    generator.setTitle("Svg image");
    generator.setDescription("This is an svg image that is converted from WMF by Calligra");

    QPainter painter;

    if (!painter.begin(&generator)) {
        debugHtml << "Can not open the painter";
        return false;
    }

    painter.scale(50,50);
    Libwmf::WmfPainterBackend  wmfPainter(&painter, size);
    if (!wmfPainter.load(input)) {
        debugHtml << "Can not Parse the WMF file";
        return false;
    }

    // Actually paint the WMF.
    painter.save();
    wmfPainter.play();
    painter.restore();
    painter.end();

    return true;
}

// ----------------------------------------------------------------
// These functions were taken from the vector shape.

ExportHtml::VectorType  ExportHtml::vectorType(QByteArray &content)
{
    if (isSvm(content))
        return ExportHtml::VectorTypeSvm;
    if (isEmf(content))
        return ExportHtml::VectorTypeEmf;
    if (isWmf(content))
        return ExportHtml::VectorTypeWmf;

    return ExportHtml::VectorTypeOther;
}

bool ExportHtml::isSvm(QByteArray &content)
{
    if (content.startsWith("VCLMTF"))
        return true;
    return false;
}

bool ExportHtml::isEmf(QByteArray &content)
{
    const char *data = content.constData();
    const int   size = content.count();

    // This is how the 'file' command identifies an EMF.
    // 1. Check type
    int offset = 0;
    int result = (int) data[offset];
    result |= (int) data[offset+1] << 8;
    result |= (int) data[offset+2] << 16;
    result |= (int) data[offset+3] << 24;

    qint32 mark = result;
    if (mark != 0x00000001) {
        return false;
    }

    // 2. An EMF has the string " EMF" at the start + offset 40.
    if (size > 44 && data[40] == ' ' && data[41] == 'E' && data[42] == 'M' && data[43] == 'F'){
        return true;
    }

    return false;
}

bool ExportHtml::isWmf(QByteArray &content)
{
    const char *data = content.constData();
    const int   size = content.count();

    if (size < 10)
        return false;

    // This is how the 'file' command identifies a WMF.
    if (data[0] == '\327' && data[1] == '\315' && data[2] == '\306' && data[3] == '\232'){
        return true;
    }

    if (data[0] == '\002' && data[1] == '\000' && data[2] == '\011' && data[3] == '\000'){
        return true;
    }

    if (data[0] == '\001' && data[1] == '\000' && data[2] == '\011' && data[3] == '\000'){
        return true;
    }

    return false;
}

#endif
