/* This file is part of the KDE project
   SPDX-FileCopyrightText: 2006 Martin Pfeiffer <hubipete@gmx.net>
                 2009 Jeremias Epperlein <jeeree@web.de>

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

#include "KoFormulaShape.h"

#include <QUrl>

#include <KMessageBox>
#include <kguiitem.h>

#include <KoDocumentResourceManager.h>
#include <KoOdfLoadingContext.h>
#include <KoShapeLoadingContext.h>
#include <KoShapeSavingContext.h>
#include <KoStore.h>
#include <KoXmlNS.h>
#include <KoXmlReader.h>
#include <KoXmlWriter.h>

#include "FormulaData.h"
#include "FormulaDebug.h"
#include "FormulaDocument.h"
#include "FormulaElement.h"
#include "FormulaRenderer.h"

KoFormulaShape::KoFormulaShape(KoDocumentResourceManager *documentResourceManager)
    : KoFrameShape(KoXmlNS::draw, "object")
{
    FormulaElement *element = new FormulaElement();
    m_formulaData = new FormulaData(element);
    m_formulaRenderer = new FormulaRenderer();
    m_isInline = false;

    m_document = new FormulaDocument(this);
    m_resourceManager = documentResourceManager;
}

KoFormulaShape::~KoFormulaShape()
{
    delete m_formulaData;
    delete m_formulaRenderer;
}

void KoFormulaShape::paint(QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &)
{
    painter.save();
    applyConversion(painter, converter); // apply zooming and coordinate translation
    m_formulaRenderer->layoutElement(m_formulaData->formulaElement());
    m_formulaRenderer->paintElement(painter, m_formulaData->formulaElement()); // paint the formula
    painter.restore();
}

void KoFormulaShape::updateLayout()
{
    m_formulaRenderer->layoutElement(m_formulaData->formulaElement());

    KoShape::setSize(m_formulaData->formulaElement()->boundingRect().size());
}

BasicElement *KoFormulaShape::elementAt(const QPointF &p)
{
    return m_formulaData->formulaElement()->childElementAt(p);
}

void KoFormulaShape::resize(const QSizeF &)
{ /* do nothing as FormulaShape is fixed size */
}

FormulaData *KoFormulaShape::formulaData() const
{
    return m_formulaData;
}

FormulaRenderer *KoFormulaShape::formulaRenderer() const
{
    return m_formulaRenderer;
}

bool KoFormulaShape::loadOdf(const KoXmlElement &element, KoShapeLoadingContext &context)
{
    debugFormula << "Loading ODF in Formula";
    loadOdfAttributes(element, context, OdfAllAttributes);
    return loadOdfFrame(element, context);
}

bool KoFormulaShape::loadOdfFrameElement(const KoXmlElement &element, KoShapeLoadingContext &context)
{
    // If this formula is embedded and not inline, then load the embedded document.
    if (element.tagName() == "object" && element.hasAttributeNS(KoXmlNS::xlink, "href")) {
        m_isInline = false;

        // This calls loadOdfEmbedded().
        return loadEmbeddedDocument(context.odfLoadingContext().store(), element, context.odfLoadingContext());
    }

    // It's not a frame:object, so it must be inline.
    const KoXmlElement &topLevelElement = KoXml::namedItemNS(element, KoXmlNS::math, "math");
    if (topLevelElement.isNull()) {
        warnFormula << "no math element as first child";
        return false;
    }

    // Create a new root element, load the formula and replace the old one.
    FormulaElement *formulaElement = new FormulaElement();
    formulaElement->readMathML(topLevelElement);
    delete m_formulaData->formulaElement();
    m_formulaData->setFormulaElement(formulaElement);
    m_formulaData->notifyDataChange(nullptr, false);

    m_isInline = true;

    return true;
}

bool KoFormulaShape::loadEmbeddedDocument(KoStore *store, const KoXmlElement &objectElement, const KoOdfLoadingContext &odfLoadingContext)
{
    if (!objectElement.hasAttributeNS(KoXmlNS::xlink, "href")) {
        errorFormula << "Object element has no valid xlink:href attribute";
        return false;
    }

    QString url = objectElement.attributeNS(KoXmlNS::xlink, "href");

    // It can happen that the url is empty e.g. when it is a
    // presentation:placeholder.
    if (url.isEmpty()) {
        return true;
    }

    QString tmpURL;
    if (url[0] == '#')
        url.remove(0, 1);

#define INTERNAL_PROTOCOL "intern"
#define STORE_PROTOCOL "tar"

    if (QUrl::fromUserInput(url).isRelative()) {
        if (url.startsWith("./"))
            tmpURL = QString(INTERNAL_PROTOCOL) + ":/" + url.mid(2);
        else
            tmpURL = QString(INTERNAL_PROTOCOL) + ":/" + url;
    } else
        tmpURL = url;

    QString path = tmpURL;
    if (tmpURL.startsWith(INTERNAL_PROTOCOL)) {
        path = store->currentPath();
        if (!path.isEmpty() && !path.endsWith('/'))
            path += '/';
        QString relPath = QUrl::fromUserInput(tmpURL).path();
        path += QStringView{relPath}.mid(1); // remove leading '/'
    }
    if (!path.endsWith('/'))
        path += '/';

    const QString mimeType = odfLoadingContext.mimeTypeForPath(path);
    // debugFormula << "path for manifest file=" << path << "mimeType=" << mimeType;
    if (mimeType.isEmpty()) {
        // debugFormula << "Manifest doesn't have media-type for" << path;
        return false;
    }

    const bool isOdf = mimeType.startsWith("application/vnd.oasis.opendocument");
    if (!isOdf) {
        tmpURL += "/maindoc.xml";
        // debugFormula << "tmpURL adjusted to" << tmpURL;
    }

    // debugFormula << "tmpURL=" << tmpURL;

    bool res = true;
    if (tmpURL.startsWith(STORE_PROTOCOL) || tmpURL.startsWith(INTERNAL_PROTOCOL) || QUrl::fromUserInput(tmpURL).isRelative()) {
        if (isOdf) {
            store->pushDirectory();
            Q_ASSERT(tmpURL.startsWith(INTERNAL_PROTOCOL));
            QString relPath = QUrl::fromUserInput(tmpURL).path().mid(1);
            store->enterDirectory(relPath);
            res = m_document->loadOasisFromStore(store);
            store->popDirectory();
        } else {
            if (tmpURL.startsWith(INTERNAL_PROTOCOL))
                tmpURL = QUrl::fromUserInput(tmpURL).path().mid(1);
            res = m_document->loadFromStore(store, tmpURL);
        }
        m_document->setStoreInternal(true);
    } else {
        // Reference to an external document. Hmmm...
        m_document->setStoreInternal(false);
        QUrl url = QUrl::fromUserInput(tmpURL);
        if (!url.isLocalFile()) {
            // QApplication::restoreOverrideCursor();

            // For security reasons we need to ask confirmation if the
            // url is remote.
            int result = KMessageBox::warningTwoActionsCancel(nullptr,
                                                              i18n("This document contains an external link to a remote document\n%1", tmpURL),
                                                              i18n("Confirmation Required"),
                                                              KGuiItem(i18n("Download")),
                                                              KGuiItem(i18n("Skip")));

            if (result == KMessageBox::Cancel) {
                // d->m_parent->setErrorMessage("USER_CANCELED");
                return false;
            }
            if (result == KMessageBox::PrimaryAction)
                res = m_document->openUrl(url);
            // and if == No, res will still be false so we'll use a kounavail below
        } else
            res = m_document->openUrl(url);
    }

    if (!res) {
        // QString errorMessage = m_document->errorMessage();
        return false;
    }

    tmpURL.clear();

    return res;
}

bool KoFormulaShape::loadOdfEmbedded(const KoXmlElement &topLevelElement, KoShapeLoadingContext &context)
{
    Q_UNUSED(context);
    debugFormula << topLevelElement.nodeName();

#if 0
    const KoXmlElement &topLevelElement = KoXml::namedItemNS(element, "http://www.w3.org/1998/Math/MathML", "math");
    if (topLevelElement.isNull()) {
        warnFormula << "no math element as first child";
        return false;
    }
#endif
    // Create a new root element, load the formula and replace the old one.
    FormulaElement *formulaElement = new FormulaElement();
    formulaElement->readMathML(topLevelElement);
    delete m_formulaData->formulaElement();
    m_formulaData->setFormulaElement(formulaElement);
    m_formulaData->notifyDataChange(nullptr, false);

    return true;
}

void KoFormulaShape::saveOdf(KoShapeSavingContext &context) const
{
    // FIXME: Add saving of embedded document if m_isInline is false;

    debugFormula << "Saving ODF in Formula";
    KoXmlWriter &writer = context.xmlWriter();
    writer.startElement("draw:frame");
    saveOdfAttributes(context, OdfAllAttributes);
    writer.startElement("draw:object");
    // TODO add some namespace magic to avoid adding "math:" namespace everywhere
    formulaData()->formulaElement()->writeMathML(&context.xmlWriter());
    writer.endElement(); // draw:object
    writer.endElement(); // draw:frame
}

KoDocumentResourceManager *KoFormulaShape::resourceManager() const
{
    return m_resourceManager;
}
