/***
   This file is part of fly-ocr.

   Copyright (C) 2016-2020 Sandro Mani <manisandro@gmail.com>
   Copyright (C) 2021 LLC "RusBITech-Astra" (https://astralinux.ru)

    Based on gImageReader source code by Sandro Mani under the GNU
    General Public License (GPL) version 3 or later

   Foobar 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 3 of the License, or
   (at your option) any later version.

   Foobar 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 General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with fly-ocr. If not, see <https://www.gnu.org/licenses/>.
***/

#include "configsettings.h"
#include "displayer.h"
#include "hocrdocument.h"
#include "hocrodtexporter.h"
#include "mainwindow.h"
#include "sourcemanager.h"
#include "utils.h"

#include <QDesktopServices>
#include <QFileInfo>
#include <QMessageBox>
#include <QUrl>
#include <QUuid>
#include <QXmlStreamWriter>
#include <quazipfile.h>

static QString manifestNS("urn:oasis:names:tc:opendocument:xmlns:manifest:1.0");
static QString officeNS("urn:oasis:names:tc:opendocument:xmlns:office:1.0");
static QString textNS ("urn:oasis:names:tc:opendocument:xmlns:text:1.0");
static QString styleNS ("urn:oasis:names:tc:opendocument:xmlns:style:1.0");
static QString foNS ("urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0");
static QString tableNS ("urn:oasis:names:tc:opendocument:xmlns:table:1.0");
static QString drawNS ("urn:oasis:names:tc:opendocument:xmlns:drawing:1.0");
static QString xlinkNS ("http://www.w3.org/1999/xlink");
static QString svgNS ("urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0");

bool HOCROdtExporter::run(const HOCRDocument* hocrdocument, const QString& outname, const ExporterSettings* /*settings*/)
{
    QuaZip zip(outname);
    if (!zip.open( QuaZip::mdCreate )) {
        QMessageBox::warning(MAIN, tr("Export failed"), tr("The ODT export failed: unable to write output file."));
        return false;
    }
    int pageCount = hocrdocument->pageCount();
    MainWindow::ProgressMonitor monitor(2 * pageCount);
    MAIN->showProgress(&monitor);
    Utils::busyTask([&] {
        // Image files
        QMap<const HOCRItem*, QString> imageFiles;
        for (int i = 0; i < pageCount; ++i)
        {
            monitor.increaseProgress();
            const HOCRPage* page = hocrdocument->page(i);
            if (!page->isEnabled()) {
                continue;
            }
            /*bool success = false;
            QMetaObject::invokeMethod(this, "setSource", Qt::BlockingQueuedConnection, Q_RETURN_ARG(bool, success), Q_ARG(QString, page->sourceFile()), Q_ARG(int, page->pageNr()), Q_ARG(int, page->resolution()), Q_ARG(double, page->angle()));
            if (!success) {
                continue;
            }*/
            for (const HOCRItem* item : page->children()) {
                writeImage(zip, imageFiles, item);
            }
        }

        // Mimetype
        QuaZipFile* mimetype = new QuaZipFile(&zip);
        mimetype->open(QIODevice::WriteOnly, QuaZipNewInfo("mimetype"));
        mimetype->write("application/vnd.oasis.opendocument.text");
        mimetype->close();

        // Manifest
        QuaZipFile* manifest = new QuaZipFile(&zip);
        manifest->open(QIODevice::WriteOnly, QuaZipNewInfo("META-INF/manifest.xml"));
        QXmlStreamWriter manifestWriter(manifest);
        manifestWriter.writeNamespace(manifestNS, "manifest");
        manifestWriter.writeStartDocument();
        manifestWriter.writeStartElement(manifestNS, "manifest");
        manifestWriter.writeAttribute(manifestNS, "version", "1.2");

        manifestWriter.writeEmptyElement(manifestNS, "file-entry");
        manifestWriter.writeAttribute(manifestNS, "media-type", "application/vnd.oasis.opendocument.text");
        manifestWriter.writeAttribute(manifestNS, "full-path", "/");

        manifestWriter.writeEmptyElement(manifestNS, "file-entry");
        manifestWriter.writeAttribute(manifestNS, "media-type", "text/xml");
        manifestWriter.writeAttribute(manifestNS, "full-path", "content.xml");

        for (const QString& imageFile : imageFiles.values())
        {
            manifestWriter.writeEmptyElement(manifestNS, "file-entry");
            manifestWriter.writeAttribute(manifestNS, "media-type", "image/png");
            manifestWriter.writeAttribute(manifestNS, "full-path", imageFile);
        }

        manifestWriter.writeEndElement(); // manifest
        manifestWriter.writeEndDocument();
        manifest->close();

        // Content
        QuaZipFile* content = new QuaZipFile(&zip);
        content->open(QIODevice::WriteOnly, QuaZipNewInfo("content.xml"));
        QXmlStreamWriter writer(content);

        writer.writeNamespace(officeNS, "office");
        writer.writeNamespace(textNS, "text");
        writer.writeNamespace(styleNS, "style");
        writer.writeNamespace(foNS, "fo");
        writer.writeNamespace(drawNS, "draw");
        writer.writeNamespace(xlinkNS, "xlink");
        writer.writeNamespace(svgNS, "svg");
        writer.writeStartDocument();
        writer.writeStartElement(officeNS, "document-content");
        writer.writeAttribute(officeNS, "version", "1.2");

        // - Font face decls
        writer.writeStartElement(officeNS, "font-face-decls");
        QSet<QString> families;
        for (int i = 0; i < pageCount; ++i)
        {
            const HOCRPage* page = hocrdocument->page(i);
            if (page->isEnabled()) {
                writeFontFaceDecls(families, page, writer);
            }
        }
        writer.writeEndElement();

        // - Page styles
        writer.writeStartElement(officeNS, "automatic-styles");
        for (int i = 0; i < pageCount; ++i)
        {
            const HOCRPage* page = hocrdocument->page(i);
            if (page->isEnabled()) {
                writer.writeStartElement(styleNS, "page-layout");
                writer.writeAttribute(styleNS, "name", QString("PL%1").arg(i));
                writer.writeEmptyElement(styleNS, "page-layout-properties");
                writer.writeAttribute(foNS, "page-width", QString("%1in").arg(page->bbox().width() / double(page->resolution())));
                writer.writeAttribute(foNS, "page-height", QString("%1in").arg(page->bbox().height() / double(page->resolution())));
                writer.writeAttribute(styleNS, "print-orientation", page->bbox().width() > page->bbox().height() ? "landscape" : "portrait");
                writer.writeAttribute(styleNS, "writing-mode", "lr-tb");
                writer.writeAttribute(foNS, "margin-top", "0in");
                writer.writeAttribute(foNS, "margin-bottom", "0in");
                writer.writeAttribute(foNS, "margin-left", "0in");
                writer.writeAttribute(foNS, "margin-right", "0in");
                writer.writeEndElement(); // page-layout
            }
        }
        writer.writeEndElement(); // automatic-styles
        writer.writeStartElement(officeNS, "master-styles");
        for (int i = 0; i < pageCount; ++i)
        {
            if (hocrdocument->page(i)->isEnabled()) {
                writer.writeEmptyElement(styleNS, "master-page");
                writer.writeAttribute(styleNS, "name", QString("MP%1").arg(i));
                writer.writeAttribute(styleNS, "page-layout-name", QString("PL%1").arg(i));
            }
        }
        writer.writeEndElement(); // master-styles

        // - Styles
        writer.writeStartElement(officeNS, "automatic-styles");

        // -- Standard paragraph
        writer.writeStartElement(styleNS, "style");
        writer.writeAttribute(styleNS, "name", "P");
        writer.writeAttribute(styleNS, "parent-style-name", "Standard");
        writer.writeEndElement();

        // -- Page paragraphs
        for (int i = 0; i < pageCount; ++i)
        {
            if (hocrdocument->page(i)->isEnabled()) {
                writer.writeStartElement(styleNS, "style");
                writer.writeAttribute(styleNS, "name", QString("PP%1").arg(i));
                writer.writeAttribute(styleNS, "family", "paragraph");
                writer.writeAttribute(styleNS, "parent-style-name", "Standard");
                writer.writeAttribute(styleNS, "master-page-name", QString("MP%1").arg(i));
                writer.writeEmptyElement(styleNS, "paragraph-properties");
                writer.writeAttribute(foNS, "break-before", "page");
                writer.writeEndElement();
            }
        }

        // -- Frame, absolutely positioned on page
        writer.writeStartElement(styleNS, "style");
        writer.writeAttribute(styleNS, "name", "F");
        writer.writeAttribute(styleNS, "family", "graphic");
        writer.writeStartElement(styleNS, "graphic-properties");
        writer.writeAttribute(drawNS, "stroke", "none");
        writer.writeAttribute(drawNS, "fill", "none");
        writer.writeAttribute(styleNS, "vertical-pos", "from-top");
        writer.writeAttribute(styleNS, "vertical-rel", "page");
        writer.writeAttribute(styleNS, "horizontal-pos", "from-left");
        writer.writeAttribute(styleNS, "horizontal-rel", "page");
        writer.writeAttribute(styleNS, "flow-with-text", "false");
        writer.writeEndElement();
        writer.writeEndElement();

        // -- Text styles
        int counter = 0;
        QMap<QString, QMap<double, QString>> fontStyles;
        for (int i = 0; i < pageCount; ++i)
        {
            const HOCRPage* page = hocrdocument->page(i);
            if (page->isEnabled()) {
                for (const HOCRItem* item : page->children()) {
                    writeFontStyles(fontStyles, item, writer, counter);
                }
            }
        }

        writer.writeEndElement(); // automatic-styles

        // - Body
        writer.writeStartElement(officeNS, "body");
        writer.writeStartElement(officeNS, "text");

        int pageCounter = 1;
        for (int i = 0; i < pageCount; ++i)
        {
            monitor.increaseProgress();
            const HOCRPage* page = hocrdocument->page(i);
            if (!page->isEnabled()) {
                continue;
            }
            writer.writeStartElement(textNS, "p");
            writer.writeAttribute(textNS, "style-name", QString("PP%1").arg(i));
            for (const HOCRItem* item : page->children()) {
                printItem(writer, item, pageCounter, page->resolution(), fontStyles, imageFiles);
            }
            writer.writeEndElement();
            ++pageCounter;
        }

        writer.writeEndElement(); // text
        writer.writeEndElement(); // body
        writer.writeEndElement(); // document-content
        writer.writeEndDocument();
        content->close();
        return true;
    }, tr("Exporting to ODT..."));
    MAIN->hideProgress();

    zip.close();

    bool openAfterExport = ConfigSettings::get<SwitchSetting>("openafterexport")->getValue();
    if (openAfterExport) {
        QDesktopServices::openUrl(QUrl::fromLocalFile(outname));
    }

    return true;
}

void HOCROdtExporter::writeImage(QuaZip& zip, QMap<const HOCRItem*, QString>& images, const HOCRItem* item)
{
    if (!item->isEnabled()) {
        return;
    }
    if (item->itemClass() == "ocr_graphic") {
        QImage image;
        QMetaObject::invokeMethod(this, "getSelection",  Qt::BlockingQueuedConnection, Q_RETURN_ARG(QImage, image), Q_ARG(QRect, item->bbox()));
        QString filename = QString("Pictures/%1.png").arg(QUuid::createUuid().toString());
        QuaZipFile* file = new QuaZipFile(&zip);
        if (file->open(QIODevice::WriteOnly, QuaZipNewInfo(filename))) {
            image.save(file, "png");
            images.insert(item, filename);
        }
    }
}

void HOCROdtExporter::writeFontFaceDecls(QSet<QString>& families, const HOCRItem* item, QXmlStreamWriter& writer)
{
    if (!item->isEnabled()) {
        return;
    }
    if (item->itemClass() == "ocrx_word") {
        QString fontFamily = item->fontFamily();
        if (!families.contains(fontFamily)) {
            if (!fontFamily.isEmpty()) {
                writer.writeEmptyElement(styleNS, "font-face");
                writer.writeAttribute(styleNS, "name", fontFamily);
                writer.writeAttribute(svgNS, "font-family", fontFamily);
            }
            families.insert(fontFamily);
        }
    } else {
        for (const HOCRItem* child : item->children()) {
            writeFontFaceDecls(families, child, writer);
        }
    }
}

void HOCROdtExporter::writeFontStyles(QMap<QString, QMap<double, QString>>& styles, const HOCRItem* item, QXmlStreamWriter& writer, int& counter)
{
    if (!item->isEnabled()) {
        return;
    }
    if (item->itemClass() == "ocrx_word") {
        QString fontKey = item->fontFamily() + (item->fontBold() ? "@bold" : QString()) + (item->fontItalic() ? "@italic" : QString());
        if (!styles.contains(fontKey) || !styles[fontKey].contains(item->fontSize())) {
            QString styleName = QString("T%1").arg(++counter);
            writer.writeStartElement(styleNS, "style");
            writer.writeAttribute(styleNS, "name", styleName);
            writer.writeAttribute(styleNS, "family", "text");
            writer.writeStartElement(styleNS, "text-properties");
            writer.writeAttribute(styleNS, "font-name", item->fontFamily());
            writer.writeAttribute(foNS, "font-size", QString("%1pt").arg(item->fontSize()));
            if (item->fontBold()) {
                writer.writeAttribute(foNS, "font-weight", "bold");
            }
            if (item->fontItalic()) {
                writer.writeAttribute(foNS, "font-style", "italic");
            }
            writer.writeEndElement(); // text-properties
            writer.writeEndElement(); // style

            styles[fontKey].insert(item->fontSize(), styleName);
        }
    } else {
        for (const HOCRItem* child : item->children()) {
            writeFontStyles(styles, child, writer, counter);
        }
    }
}

void HOCROdtExporter::printItem(QXmlStreamWriter& writer, const HOCRItem* item, int pageNr, int dpi, const QMap<QString, QMap<double, QString>>& fontStyleNames, const QMap<const HOCRItem*, QString>& images)
{
    if (!item->isEnabled()) {
        return;
    }
    QString itemClass = item->itemClass();
    const QRect& bbox = item->bbox();
    if (itemClass == "ocr_graphic") {
        writer.writeStartElement(drawNS, "frame");
        writer.writeAttribute(drawNS, "style-name", "F");
        writer.writeAttribute(textNS, "anchor-type", "page");
        writer.writeAttribute(textNS, "anchor-page-number", QString::number(pageNr));
        writer.writeAttribute(svgNS, "x", QString("%1in").arg(bbox.x() / double(dpi)));
        writer.writeAttribute(svgNS, "y", QString("%1in").arg(bbox.y() / double(dpi)));
        writer.writeAttribute(svgNS, "width", QString("%1in").arg(bbox.width() / double(dpi)));
        writer.writeAttribute(svgNS, "height", QString("%1in").arg(bbox.height() / double(dpi)));
        writer.writeAttribute(drawNS, "z-index", "0");

        writer.writeStartElement(drawNS, "image");
        writer.writeAttribute(xlinkNS, "href", images[item]);
        writer.writeAttribute(xlinkNS, "type", "simple");
        writer.writeAttribute(xlinkNS, "show", "embed");
        writer.writeEndElement(); // image

        writer.writeEndElement(); // frame
    } else if (itemClass == "ocr_par") {
        writer.writeStartElement(drawNS, "frame");
        writer.writeAttribute(drawNS, "style-name", "F");
        writer.writeAttribute(textNS, "anchor-type", "page");
        writer.writeAttribute(textNS, "anchor-page-number", QString::number(pageNr));
        writer.writeAttribute(svgNS, "x", QString("%1in").arg(bbox.x() / double(dpi)));
        writer.writeAttribute(svgNS, "y", QString("%1in").arg(bbox.y() / double(dpi)));
        writer.writeAttribute(svgNS, "width", QString("%1in").arg(bbox.width() / double(dpi)));
        writer.writeAttribute(svgNS, "height", QString("%1in").arg(bbox.height() / double(dpi)));
        writer.writeAttribute(drawNS, "z-index", "1");

        writer.writeStartElement(drawNS, "text-box");

        writer.writeStartElement(textNS, "p");
        writer.writeAttribute(textNS, "style-name", "P");
        for (const HOCRItem* child : item->children()) {
            printItem(writer, child, pageNr, dpi, fontStyleNames, images);

            if (child->itemClass() == "ocr_line" && child != item->children().last())
                writer.writeEmptyElement(textNS, "line-break");
        }
        writer.writeEndElement();

        writer.writeEndElement(); // text-box
        writer.writeEndElement(); // frame
    } else if (itemClass == "ocr_line" && item->hasText()) {
        QString fontKey = item->fontFamily() + (item->fontBold() ? "@bold" : "") + (item->fontItalic() ? "@italic" : "");
        QString currentFontStyleName = fontStyleNames[fontKey][item->fontSize()];
        writer.writeStartElement(textNS, "span");
        writer.writeAttribute(textNS, "style-name", currentFontStyleName);
        writer.writeCharacters(item->text());
        writer.writeEndElement();
        writer.writeEmptyElement(textNS, "line-break");
    } else if (itemClass == "ocr_line") {
        const HOCRItem* firstWord = nullptr;
        int iChild = 0, nChilds = item->children().size();
        for (; iChild < nChilds; ++iChild) {
            const HOCRItem* child = item->children()[iChild];
            if (child->isEnabled()) {
                firstWord = child;
                break;
            }
        }
        if (!firstWord) {
            return;
        }
        QString fontKey = firstWord->fontFamily() + (firstWord->fontBold() ? "@bold" : "") + (firstWord->fontItalic() ? "@italic" : "");
        QString currentFontStyleName = fontStyleNames[fontKey][firstWord->fontSize()];
        writer.writeStartElement(textNS, "span");
        writer.writeAttribute(textNS, "style-name", currentFontStyleName);
        writer.writeCharacters(firstWord->text());
        ++iChild;
        for (; iChild < nChilds; ++iChild) {
            const HOCRItem* child = item->children()[iChild];
            if (!child->isEnabled()) {
                continue;
            }
            fontKey = child->fontFamily() + (child->fontBold() ? "@bold" : "") + (child->fontItalic() ? "@italic" : "");
            QString fontStyleName = fontStyleNames[fontKey][child->fontSize()];
            if (fontStyleName != currentFontStyleName) {
                currentFontStyleName = fontStyleName;
                writer.writeEndElement();
                writer.writeStartElement(textNS, "span");
                writer.writeAttribute(textNS, "style-name", currentFontStyleName);
            }
            writer.writeCharacters(" ");
            writer.writeCharacters(child->text());
        }
        writer.writeEndElement();
        //writer.writeEmptyElement(textNS, "line-break");
    } else {
        for (const HOCRItem* child : item->children()) {
            printItem(writer, child, pageNr, dpi, fontStyleNames, images);
        }
    }
}

bool HOCROdtExporter::setSource(const QString& sourceFile, int page, int dpi, double angle)
{
    Q_UNUSED(sourceFile);
    //if (MAIN->sourceManager()->add(sourceFile, true)) {
    MAIN->displayer()->setup(&page, &dpi, &angle);
    return true;
    /*} else {
        return false;
    }*/
}

QImage HOCROdtExporter::getSelection(const QRect& bbox)
{
    Displayer* displayer = MAIN->displayer();
    return displayer->getImage(bbox.translated(displayer->getSceneBoundingRect().toRect().topLeft()));
}
