/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of the LibreOffice project.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * This file incorporates work covered by the following license notice:
 *
 *   Licensed to the Apache Software Foundation (ASF) under one or more
 *   contributor license agreements. See the NOTICE file distributed
 *   with this work for additional information regarding copyright
 *   ownership. The ASF licenses this file to you under the Apache
 *   License, Version 2.0 (the "License"); you may not use this file
 *   except in compliance with the License. You may obtain a copy of
 *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
 */

#include <com/sun/star/document/XEventsSupplier.hpp>
#include <com/sun/star/container/XNameReplace.hpp>
#include <tools/urlobj.hxx>
#include <osl/diagnose.h>
#include <sal/log.hxx>
#include <o3tl/string_view.hxx>

#include <sax/tools/converter.hxx>

#include <xmloff/xmltoken.hxx>
#include <xmloff/xmlimp.hxx>
#include <xmloff/xmlnamespace.hxx>
#include <xmloff/xmluconv.hxx>
#include <xmloff/namespacemap.hxx>
#include "eventimp.hxx"

using namespace ::com::sun::star;
using namespace ::com::sun::star::xml;
using namespace ::com::sun::star::xml::sax;
using namespace ::com::sun::star::uno;
using namespace ::com::sun::star::drawing;
using namespace ::com::sun::star::beans;
using namespace ::com::sun::star::document;
using namespace ::com::sun::star::container;
using namespace ::com::sun::star::presentation;
using namespace ::xmloff::token;

SvXMLEnumMapEntry<ClickAction> const aXML_EventActions_EnumMap[] =
{
    { XML_NONE,             ClickAction_NONE    },
    { XML_PREVIOUS_PAGE,    ClickAction_PREVPAGE },
    { XML_NEXT_PAGE,        ClickAction_NEXTPAGE },
    { XML_FIRST_PAGE,       ClickAction_FIRSTPAGE },
    { XML_LAST_PAGE,        ClickAction_LASTPAGE },
    { XML_HIDE,             ClickAction_INVISIBLE },
    { XML_STOP,             ClickAction_STOPPRESENTATION },
    { XML_EXECUTE,          ClickAction_PROGRAM },
    { XML_SHOW,             ClickAction_BOOKMARK },
    { XML_SHOW,             ClickAction_DOCUMENT },
    { XML_EXECUTE_MACRO,    ClickAction_MACRO },
    { XML_VERB,             ClickAction_VERB },
    { XML_FADE_OUT,         ClickAction_VANISH },
    { XML_SOUND,            ClickAction_SOUND },
    { XML_TOKEN_INVALID, ClickAction(0) }
};

SdXMLEventContextData::SdXMLEventContextData(const Reference< XShape >& rxShape)
    : mxShape(rxShape), mbValid(false), mbScript(false)
    , meClickAction(ClickAction_NONE), meEffect(EK_none)
    , meDirection(ED_none), mnStartScale(100), meSpeed(AnimationSpeed_MEDIUM)
    , mnVerb(0), mbPlayFull(false)
{
}

namespace {

class SdXMLEventContext : public SvXMLImportContext
{
public:
    SdXMLEventContextData maData;

public:

    SdXMLEventContext( SvXMLImport& rImport, sal_Int32 nElement, const Reference< XFastAttributeList>& xAttrList, const Reference< XShape >& rxShape );

    virtual css::uno::Reference< css::xml::sax::XFastContextHandler > SAL_CALL createFastChildContext(
        sal_Int32 nElement,
        const css::uno::Reference< css::xml::sax::XFastAttributeList >& AttrList ) override;
    virtual void SAL_CALL endFastElement(sal_Int32 nElement) override;
};

class XMLEventSoundContext : public SvXMLImportContext
{
public:

    XMLEventSoundContext( SvXMLImport& rImport, const Reference< XFastAttributeList >& xAttrList, SdXMLEventContext* pParent );
};

}

XMLEventSoundContext::XMLEventSoundContext( SvXMLImport& rImp, const Reference< XFastAttributeList >& xAttrList, SdXMLEventContext* pParent )
: SvXMLImportContext( rImp )
{
    for( auto& aIter : sax_fastparser::castToFastAttributeList(xAttrList) )
    {
        switch( aIter.getToken() )
        {
            case XML_ELEMENT(XLINK, XML_HREF):
                pParent->maData.msSoundURL = rImp.GetAbsoluteReference(aIter.toString());
                break;
            case XML_ELEMENT(PRESENTATION, XML_PLAY_FULL):
                pParent->maData.mbPlayFull = IsXMLToken( aIter, XML_TRUE );
                break;
            default:
                XMLOFF_WARN_UNKNOWN("xmloff", aIter);
        }
    }
}

SdXMLEventContext::SdXMLEventContext( SvXMLImport& rImp,
    sal_Int32 nElement,
    const Reference< XFastAttributeList >& xAttrList, const Reference< XShape >& rxShape )
    : SvXMLImportContext(rImp)
    , maData(rxShape)
{
    if( nElement == XML_ELEMENT(PRESENTATION, XML_EVENT_LISTENER) )
    {
        maData.mbValid = true;
    }
    else if( nElement == XML_ELEMENT(SCRIPT, XML_EVENT_LISTENER) )
    {
        maData.mbScript = true;
        maData.mbValid = true;
    }
    else
    {
        return;
    }

    // read attributes
    OUString sEventName;
    for( auto& aIter : sax_fastparser::castToFastAttributeList(xAttrList) )
    {
        switch( aIter.getToken() )
        {
        case XML_ELEMENT(PRESENTATION, XML_ACTION):
            SvXMLUnitConverter::convertEnum( maData.meClickAction, aIter.toView(), aXML_EventActions_EnumMap );
            break;
        case XML_ELEMENT(PRESENTATION, XML_EFFECT):
            SvXMLUnitConverter::convertEnum( maData.meEffect, aIter.toView(), aXML_AnimationEffect_EnumMap );
            break;
        case XML_ELEMENT(PRESENTATION, XML_DIRECTION):
            SvXMLUnitConverter::convertEnum( maData.meDirection, aIter.toView(), aXML_AnimationDirection_EnumMap );
            break;
        case XML_ELEMENT(PRESENTATION, XML_START_SCALE):
            {
                sal_Int32 nScale;
                if (::sax::Converter::convertPercent( nScale, aIter.toView() ))
                    maData.mnStartScale = static_cast<sal_Int16>(nScale);
            }
            break;
        case XML_ELEMENT(PRESENTATION, XML_SPEED):
            SvXMLUnitConverter::convertEnum( maData.meSpeed, aIter.toView(), aXML_AnimationSpeed_EnumMap );
            break;
        case XML_ELEMENT(PRESENTATION, XML_VERB):
            ::sax::Converter::convertNumber( maData.mnVerb, aIter.toView() );
            break;
        case XML_ELEMENT(SCRIPT, XML_EVENT_NAME):
            {
                sEventName = aIter.toString();
                sal_uInt16 nScriptPrefix =
                    GetImport().GetNamespaceMap().GetKeyByAttrValueQName(sEventName, &sEventName);
                maData.mbValid = XML_NAMESPACE_DOM == nScriptPrefix && sEventName == "click";
            }
            break;
        case XML_ELEMENT(SCRIPT, XML_LANGUAGE):
            {
                // language is not evaluated!
                OUString aScriptLanguage;
                maData.msLanguage = aIter.toString();
                sal_uInt16 nScriptPrefix = rImp.GetNamespaceMap().
                    GetKeyByAttrValueQName(maData.msLanguage, &aScriptLanguage);
                if( XML_NAMESPACE_OOO == nScriptPrefix )
                    maData.msLanguage = aScriptLanguage;
            }
            break;
        case XML_ELEMENT(SCRIPT, XML_MACRO_NAME):
            maData.msMacroName = aIter.toString();
            break;
        case XML_ELEMENT(XLINK, XML_HREF):
            {
                if ( maData.mbScript )
                {
                    maData.msMacroName = aIter.toString();
                }
                else
                {
                    const OUString aTmp =
                        rImp.GetAbsoluteReference(aIter.toString());
                    INetURLObject::translateToInternal( aTmp, maData.msBookmark,
                        INetURLObject::DecodeMechanism::Unambiguous );
                }
            }
            break;
        }
    }

    if( maData.mbValid )
        maData.mbValid = !sEventName.isEmpty();

    if (!maData.msMacroName.isEmpty())
        rImp.NotifyMacroEventRead();
}

css::uno::Reference< css::xml::sax::XFastContextHandler > SdXMLEventContext::createFastChildContext(
    sal_Int32 nElement,
    const css::uno::Reference< css::xml::sax::XFastAttributeList >& xAttrList )
{
    if( nElement == XML_ELEMENT(PRESENTATION, XML_SOUND) )
        return new XMLEventSoundContext( GetImport(), xAttrList, this );
    else
        XMLOFF_WARN_UNKNOWN_ELEMENT("xmloff", nElement);
    return nullptr;
}

void SdXMLEventContext::endFastElement(sal_Int32 )
{
    GetImport().GetShapeImport()->addShapeEvents(maData);
}

void SdXMLEventContextData::ApplyProperties()
{
    if( !mbValid )
        return;

    do
    {
        Reference< XEventsSupplier > xEventsSupplier( mxShape, UNO_QUERY );
        if( !xEventsSupplier.is() )
            break;

        Reference< XNameReplace > xEvents( xEventsSupplier->getEvents() );
        SAL_WARN_IF( !xEvents.is(), "xmloff", "XEventsSupplier::getEvents() returned NULL" );
        if( !xEvents.is() )
            break;

        OUString sAPIEventName;
        uno::Sequence< beans::PropertyValue > aProperties;

        sAPIEventName = "OnClick";

        if( mbScript )
            meClickAction = ClickAction_MACRO;

        sal_Int32 nPropertyCount = 2;
        switch( meClickAction )
        {
            case ClickAction_NONE:
            case ClickAction_PREVPAGE:
            case ClickAction_NEXTPAGE:
            case ClickAction_FIRSTPAGE:
            case ClickAction_LASTPAGE:
            case ClickAction_INVISIBLE:
            case ClickAction_STOPPRESENTATION:
                break;
            case ClickAction_PROGRAM:
            case ClickAction_VERB:
            case ClickAction_BOOKMARK:
            case ClickAction_DOCUMENT:
                nPropertyCount += 1;
                break;
            case ClickAction_MACRO:
                if ( msLanguage.equalsIgnoreAsciiCase("starbasic") )
                    nPropertyCount += 1;
                break;

            case ClickAction_SOUND:
                nPropertyCount += 2;
                break;

            case ClickAction_VANISH:
                nPropertyCount += 4;
                break;
            default:
                break;
        }

        aProperties.realloc( nPropertyCount );
        beans::PropertyValue* pProperties = aProperties.getArray();

        if( ClickAction_MACRO == meClickAction )
        {
            if ( msLanguage.equalsIgnoreAsciiCase("starbasic") )
            {
                OUString sLibrary;
                const OUString& rApp = GetXMLToken( XML_APPLICATION );
                const OUString& rDoc = GetXMLToken( XML_DOCUMENT );
                if( msMacroName.getLength() > rApp.getLength()+1 &&
                    o3tl::equalsIgnoreAsciiCase(msMacroName.subView(0,rApp.getLength()), rApp) &&
                    ':' == msMacroName[rApp.getLength()] )
                {
                    sLibrary = "StarOffice";
                    msMacroName = msMacroName.copy( rApp.getLength()+1 );
                }
                else if( msMacroName.getLength() > rDoc.getLength()+1 &&
                    o3tl::equalsIgnoreAsciiCase(msMacroName.subView(0,rDoc.getLength()), rDoc) &&
                    ':' == msMacroName[rDoc.getLength()] )
                {
                    sLibrary = rDoc;
                    msMacroName = msMacroName.copy( rDoc.getLength()+1 );
                }

                pProperties->Name = "EventType";
                pProperties->Handle = -1;
                pProperties->Value <<= u"StarBasic"_ustr;
                pProperties->State = beans::PropertyState_DIRECT_VALUE;
                pProperties++;

                pProperties->Name = "MacroName";
                pProperties->Handle = -1;
                pProperties->Value <<= msMacroName;
                pProperties->State = beans::PropertyState_DIRECT_VALUE;
                pProperties++;

                pProperties->Name = "Library";
                pProperties->Handle = -1;
                pProperties->Value <<= sLibrary;
                pProperties->State = beans::PropertyState_DIRECT_VALUE;
            }
            else
            {
                pProperties->Name = "EventType";
                pProperties->Handle = -1;
                pProperties->Value <<= u"Script"_ustr;
                pProperties->State = beans::PropertyState_DIRECT_VALUE;
                pProperties++;

                pProperties->Name = "Script";
                pProperties->Handle = -1;
                pProperties->Value <<= msMacroName;
                pProperties->State = beans::PropertyState_DIRECT_VALUE;
            }
        }
        else
        {
            pProperties->Name = "EventType";
            pProperties->Handle = -1;
            pProperties->Value <<= u"Presentation"_ustr;
            pProperties->State = beans::PropertyState_DIRECT_VALUE;
            pProperties++;

            // ClickAction_BOOKMARK and ClickAction_DOCUMENT share the same xml event
            // so check here if it's really a bookmark or maybe a document
            if( meClickAction == ClickAction_BOOKMARK )
            {
                if( !msBookmark.startsWith( "#" ) )
                    meClickAction = ClickAction_DOCUMENT;
            }

            pProperties->Name = "ClickAction";
            pProperties->Handle = -1;
            pProperties->Value <<= meClickAction;
            pProperties->State = beans::PropertyState_DIRECT_VALUE;
            pProperties++;

            switch( meClickAction )
            {
                case ClickAction_NONE:
                case ClickAction_PREVPAGE:
                case ClickAction_NEXTPAGE:
                case ClickAction_FIRSTPAGE:
                case ClickAction_LASTPAGE:
                case ClickAction_INVISIBLE:
                case ClickAction_STOPPRESENTATION:
                    break;

                case ClickAction_BOOKMARK:
                    msBookmark = msBookmark.copy(1);

                    [[fallthrough]];

                case ClickAction_DOCUMENT:
                case ClickAction_PROGRAM:
                    pProperties->Name = "Bookmark";
                    pProperties->Handle = -1;
                    pProperties->Value <<= msBookmark;
                    pProperties->State = beans::PropertyState_DIRECT_VALUE;
                    break;

                case ClickAction_VANISH:
                    pProperties->Name = "Effect";
                    pProperties->Handle = -1;
                    pProperties->Value <<= ImplSdXMLgetEffect( meEffect, meDirection, mnStartScale, true );
                    pProperties->State = beans::PropertyState_DIRECT_VALUE;
                    pProperties++;

                    pProperties->Name = "Speed";
                    pProperties->Handle = -1;
                    pProperties->Value <<= meSpeed;
                    pProperties->State = beans::PropertyState_DIRECT_VALUE;
                    pProperties++;

                    [[fallthrough]];

                case ClickAction_SOUND:
                    pProperties->Name = "SoundURL";
                    pProperties->Handle = -1;
                    pProperties->Value <<= msSoundURL;
                    pProperties->State = beans::PropertyState_DIRECT_VALUE;
                    pProperties++;

                    pProperties->Name = "PlayFull";
                    pProperties->Handle = -1;
                    pProperties->Value <<= mbPlayFull;
                    pProperties->State = beans::PropertyState_DIRECT_VALUE;
                    break;

                case ClickAction_VERB:
                    pProperties->Name = "Verb";
                    pProperties->Handle = -1;
                    pProperties->Value <<= mnVerb;
                    pProperties->State = beans::PropertyState_DIRECT_VALUE;
                    break;
                case ClickAction_MACRO:
                    OSL_FAIL("xmloff::SdXMLEventContext::EndElement(), ClickAction_MACRO must be handled in different if case");
                    break;
                default:
                    break;
            }
        }
        xEvents->replaceByName( sAPIEventName, uno::Any( aProperties ) );

    } while(false);
}


SdXMLEventsContext::SdXMLEventsContext( SvXMLImport& rImport, const Reference< XShape >& rxShape)
: SvXMLImportContext(rImport), mxShape( rxShape )
{
}

SdXMLEventsContext::~SdXMLEventsContext()
{
}

css::uno::Reference< css::xml::sax::XFastContextHandler > SdXMLEventsContext::createFastChildContext(
    sal_Int32 nElement,
    const css::uno::Reference< css::xml::sax::XFastAttributeList >& xAttrList )
{
    return new SdXMLEventContext( GetImport(), nElement, xAttrList, mxShape );
}

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
