/* This file is part of the KDE project
   Copyright (C) 2006 Martin Pfeiffer <hubipete@gmx.net>
                 2009 Jeremias Epperlein <jeeree@web.de>

   This library is free software; you can redistribute it and/or
   modify it under the terms of the GNU Library General Public
   License as published by the Free Software Foundation; either
   version 2 of the License, or (at your option) any later version.

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

   You should have received a copy of the GNU Library General Public License
   along with this library; see the file COPYING.LIB.  If not, write to
   the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
   Boston, MA 02110-1301, USA.
*/

#include "KoFormulaShape.h"

#include <QUrl>

#include <kmessagebox.h>
#include <kguiitem.h>

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

#include "FormulaDebug.h"
#include "FormulaDocument.h"
#include "FormulaData.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(0, 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 += 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::warningYesNoCancel(
                0, 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::Yes )
                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(0, 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;
}
