/* -*- 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 <sal/config.h>

#include <o3tl/any.hxx>
#include <o3tl/safeint.hxx>
#include <osl/diagnose.h>
#include <comphelper/eventattachermgr.hxx>
#include <comphelper/sequence.hxx>
#include <com/sun/star/beans/theIntrospection.hpp>
#include <com/sun/star/io/XObjectInputStream.hpp>
#include <com/sun/star/io/XPersistObject.hpp>
#include <com/sun/star/io/XObjectOutputStream.hpp>
#include <com/sun/star/io/XMarkableStream.hpp>
#include <com/sun/star/lang/WrappedTargetRuntimeException.hpp>
#include <com/sun/star/lang/XInitialization.hpp>
#include <com/sun/star/reflection/theCoreReflection.hpp>
#include <com/sun/star/reflection/XIdlClass.hpp>
#include <com/sun/star/reflection/XIdlReflection.hpp>
#include <com/sun/star/reflection/XIdlMethod.hpp>
#include <com/sun/star/script/CannotConvertException.hpp>
#include <com/sun/star/script/Converter.hpp>
#include <com/sun/star/script/XEventAttacher2.hpp>
#include <com/sun/star/script/XEventAttacherManager.hpp>
#include <com/sun/star/script/XScriptListener.hpp>
#include <cppuhelper/weak.hxx>
#include <comphelper/interfacecontainer4.hxx>
#include <cppuhelper/exc_hlp.hxx>
#include <cppuhelper/implbase.hxx>
#include <rtl/ref.hxx>

#include <deque>
#include <mutex>
#include <algorithm>
#include <utility>

using namespace com::sun::star::uno;
using namespace com::sun::star::io;
using namespace com::sun::star::lang;
using namespace com::sun::star::beans;
using namespace com::sun::star::script;
using namespace com::sun::star::reflection;
using namespace cppu;


namespace comphelper
{

namespace {

struct AttachedObject_Impl
{
    Reference< XInterface >                 xTarget;
    std::vector< Reference< XEventListener > > aAttachedListenerSeq;
    Any                                     aHelper;
};

struct AttacherIndex_Impl
{
    std::deque< ScriptEventDescriptor > aEventList;
    std::deque< AttachedObject_Impl > aObjList;
};


class ImplEventAttacherManager
    : public WeakImplHelper< XEventAttacherManager, XPersistObject >
{
    friend class AttacherAllListener_Impl;
    std::deque< AttacherIndex_Impl >  aIndex;
    std::mutex m_aMutex;
    // Container for the ScriptListener
    OInterfaceContainerHelper4<XScriptListener> aScriptListeners;
    // Instance of EventAttacher
    Reference< XEventAttacher2 >        xAttacher;
    Reference< XComponentContext >      mxContext;
    Reference< XIdlReflection >         mxCoreReflection;
    Reference< XTypeConverter >         xConverter;
    sal_Int16                           nVersion;
public:
    ImplEventAttacherManager( const Reference< XIntrospection > & rIntrospection,
                              const Reference< XComponentContext >& rContext );

    // Methods of XEventAttacherManager
    virtual void SAL_CALL registerScriptEvent(sal_Int32 Index, const ScriptEventDescriptor& ScriptEvent) override;
    virtual void SAL_CALL registerScriptEvents(sal_Int32 Index, const Sequence< ScriptEventDescriptor >& ScriptEvents) override;
    virtual void SAL_CALL revokeScriptEvent(sal_Int32 Index, const OUString& ListenerType, const OUString& EventMethod, const OUString& removeListenerParam) override;
    virtual void SAL_CALL revokeScriptEvents(sal_Int32 Index) override;
    virtual void SAL_CALL insertEntry(sal_Int32 Index) override;
    virtual void SAL_CALL removeEntry(sal_Int32 Index) override;
    virtual Sequence< ScriptEventDescriptor > SAL_CALL getScriptEvents(sal_Int32 Index) override;
    virtual void SAL_CALL attach(sal_Int32 Index, const Reference< XInterface >& Object, const Any& Helper) override;
    virtual void SAL_CALL detach(sal_Int32 nIndex, const Reference< XInterface >& xObject) override;
    virtual void SAL_CALL addScriptListener(const Reference< XScriptListener >& aListener) override;
    virtual void SAL_CALL removeScriptListener(const Reference< XScriptListener >& Listener) override;

    // Methods of XPersistObject
    virtual OUString SAL_CALL getServiceName() override;
    virtual void SAL_CALL write(const Reference< XObjectOutputStream >& OutStream) override;
    virtual void SAL_CALL read(const Reference< XObjectInputStream >& InStream) override;

private:
    void registerScriptEvent(std::unique_lock<std::mutex>&, sal_Int32 Index, const ScriptEventDescriptor& ScriptEvent);
    void registerScriptEvents(std::unique_lock<std::mutex>&, sal_Int32 Index, const Sequence< ScriptEventDescriptor >& ScriptEvents);
    void attach(std::unique_lock<std::mutex>&, sal_Int32 Index, const Reference< XInterface >& Object, const Any& Helper);
    void detach(std::unique_lock<std::mutex>&, sal_Int32 nIndex, const Reference< XInterface >& xObject);
    void insertEntry(std::unique_lock<std::mutex>&, sal_Int32 Index);

    /// @throws Exception
    const Reference< XIdlReflection > & getReflection(std::unique_lock<std::mutex>&);

    /** checks if <arg>_nIndex</arg> is a valid index, throws an <type>IllegalArgumentException</type> if not
    @param _nIndex
        the index to check
    @return
        the iterator pointing to the position indicated by the index
    */
    std::deque<AttacherIndex_Impl>::iterator implCheckIndex( sal_Int32 _nIndex );
};


// Implementation of an EventAttacher-subclass 'AllListeners', which
// only passes individual events of the general AllListeners.
class AttacherAllListener_Impl : public WeakImplHelper< XAllListener >
{
    rtl::Reference<ImplEventAttacherManager> mxManager;
    OUString const                           aScriptType;
    OUString const                           aScriptCode;

    /// @throws CannotConvertException
    void convertToEventReturn( Any & rRet, const Type & rRetType );
public:
    AttacherAllListener_Impl( ImplEventAttacherManager* pManager_, OUString aScriptType_,
                                OUString aScriptCode_ );

    // Methods of XAllListener
    virtual void SAL_CALL firing(const AllEventObject& Event) override;
    virtual Any SAL_CALL approveFiring(const AllEventObject& Event) override;

    // Methods of XEventListener
    virtual void SAL_CALL disposing(const EventObject& Source) override;
};

}

AttacherAllListener_Impl::AttacherAllListener_Impl
(
    ImplEventAttacherManager*   pManager_,
    OUString              aScriptType_,
    OUString              aScriptCode_
)
    : mxManager( pManager_ )
    , aScriptType(std::move( aScriptType_ ))
    , aScriptCode(std::move( aScriptCode_ ))
{
}


// Methods of XAllListener
void SAL_CALL AttacherAllListener_Impl::firing(const AllEventObject& Event)
{
    ScriptEvent aScriptEvent;
    aScriptEvent.Source         = static_cast<OWeakObject *>(mxManager.get()); // get correct XInterface
    aScriptEvent.ListenerType   = Event.ListenerType;
    aScriptEvent.MethodName     = Event.MethodName;
    aScriptEvent.Arguments      = Event.Arguments;
    aScriptEvent.Helper         = Event.Helper;
    aScriptEvent.ScriptType     = aScriptType;
    aScriptEvent.ScriptCode     = aScriptCode;

    // Iterate over all listeners and pass events.
    std::unique_lock l(mxManager->m_aMutex);
    mxManager->aScriptListeners.notifyEach( l, &XScriptListener::firing, aScriptEvent );
}


// Convert to the standard event return
void AttacherAllListener_Impl::convertToEventReturn( Any & rRet, const Type & rRetType )
{
    // no return value? Set to the specified values
    if( rRet.getValueTypeClass() == TypeClass_VOID )
    {
        switch( rRetType.getTypeClass()  )
        {
            case TypeClass_INTERFACE:
                {
                rRet <<= Reference< XInterface >();
                }
                break;

            case TypeClass_BOOLEAN:
                rRet <<= true;
                break;

            case TypeClass_STRING:
                rRet <<= OUString();
                break;

            case TypeClass_FLOAT:           rRet <<= float(0);  break;
            case TypeClass_DOUBLE:          rRet <<= 0.0;  break;
            case TypeClass_BYTE:            rRet <<= sal_uInt8(0);      break;
            case TypeClass_SHORT:           rRet <<= sal_Int16( 0 );    break;
            case TypeClass_LONG:            rRet <<= sal_Int32( 0 );    break;
            case TypeClass_UNSIGNED_SHORT:  rRet <<= sal_uInt16( 0 );   break;
            case TypeClass_UNSIGNED_LONG:   rRet <<= sal_uInt32( 0 );   break;

            default:
                OSL_ASSERT(false);
                break;
        }
    }
    else if( !rRet.getValueType().equals( rRetType ) )
    {
        if( !mxManager->xConverter.is() )
            throw CannotConvertException();
        rRet = mxManager->xConverter->convertTo( rRet, rRetType );
    }
}

// Methods of XAllListener
Any SAL_CALL AttacherAllListener_Impl::approveFiring( const AllEventObject& Event )
{
    ScriptEvent aScriptEvent;
    aScriptEvent.Source         = static_cast<OWeakObject *>(mxManager.get()); // get correct XInterface
    aScriptEvent.ListenerType   = Event.ListenerType;
    aScriptEvent.MethodName     = Event.MethodName;
    aScriptEvent.Arguments      = Event.Arguments;
    aScriptEvent.Helper         = Event.Helper;
    aScriptEvent.ScriptType     = aScriptType;
    aScriptEvent.ScriptCode     = aScriptCode;

    Any aRet;
    // Iterate over all listeners and pass events.
    std::unique_lock l(mxManager->m_aMutex);
    OInterfaceIteratorHelper4 aIt( l, mxManager->aScriptListeners );
    while( aIt.hasMoreElements() )
    {
        // cannot hold lock over call to approveFiring, since it might recurse back into us
        l.unlock();
        aRet = aIt.next()->approveFiring( aScriptEvent );
        l.lock();
        try
        {
            Reference< XIdlClass > xListenerType = mxManager->getReflection(l)->
                        forName( Event.ListenerType.getTypeName() );
            Reference< XIdlMethod > xMeth = xListenerType->getMethod( Event.MethodName );
            if( xMeth.is() )
            {
                Reference< XIdlClass > xRetType = xMeth->getReturnType();
                Type aRetType(xRetType->getTypeClass(), xRetType->getName());
                convertToEventReturn( aRet, aRetType );
            }

            switch( aRet.getValueTypeClass()  )
            {
                case TypeClass_INTERFACE:
                    {
                    // Interface not null, return
                    Reference< XInterface > x;
                    aRet >>= x;
                    if( x.is() )
                        return aRet;
                    }
                    break;

                case TypeClass_BOOLEAN:
                    // FALSE -> Return
                    if( !(*o3tl::forceAccess<bool>(aRet)) )
                        return aRet;
                    break;

                case TypeClass_STRING:
                    // none empty string -> return
                    if( !o3tl::forceAccess<OUString>(aRet)->isEmpty() )
                        return aRet;
                    break;

                    // none zero number -> return
                case TypeClass_FLOAT:           if( *o3tl::forceAccess<float>(aRet) )    return aRet; break;
                case TypeClass_DOUBLE:          if( *o3tl::forceAccess<double>(aRet) )   return aRet; break;
                case TypeClass_BYTE:            if( *o3tl::forceAccess<sal_Int8>(aRet) )    return aRet; break;
                case TypeClass_SHORT:           if( *o3tl::forceAccess<sal_Int16>(aRet) )    return aRet; break;
                case TypeClass_LONG:            if( *o3tl::forceAccess<sal_Int32>(aRet) )    return aRet; break;
                case TypeClass_UNSIGNED_SHORT:  if( *o3tl::forceAccess<sal_uInt16>(aRet) )   return aRet; break;
                case TypeClass_UNSIGNED_LONG:   if( *o3tl::forceAccess<sal_uInt32>(aRet) )   return aRet; break;

                default:
                    OSL_ASSERT(false);
                    break;
            }
        }
        catch (const CannotConvertException&)
        {
            // silent ignore conversions errors from a script call
            Reference< XIdlClass > xListenerType = mxManager->getReflection(l)->
                        forName( Event.ListenerType.getTypeName() );
            Reference< XIdlMethod > xMeth = xListenerType->getMethod( Event.MethodName );
            if( xMeth.is() )
            {
                Reference< XIdlClass > xRetType = xMeth->getReturnType();
                Type aRetType(xRetType->getTypeClass(), xRetType->getName());
                aRet.clear();
                try
                {
                    convertToEventReturn( aRet, aRetType );
                }
                catch (const CannotConvertException& e)
                {
                    css::uno::Any anyEx = cppu::getCaughtException();
                    throw css::lang::WrappedTargetRuntimeException(
                        "wrapped CannotConvertException " + e.Message,
                        css::uno::Reference<css::uno::XInterface>(), anyEx);
                }
            }
        }
    }
    return aRet;
}

// Methods of XEventListener
void SAL_CALL AttacherAllListener_Impl::disposing(const EventObject& )
{
    // It is up to the container to release the object
}

// Constructor method for EventAttacherManager
Reference< XEventAttacherManager > createEventAttacherManager( const Reference< XComponentContext > & rxContext )
{
    Reference< XIntrospection > xIntrospection = theIntrospection::get( rxContext );
    return new ImplEventAttacherManager( xIntrospection, rxContext );
}


ImplEventAttacherManager::ImplEventAttacherManager( const Reference< XIntrospection > & rIntrospection,
                                                    const Reference< XComponentContext >& rContext )
    : mxContext( rContext )
    , nVersion(0)
{
    if ( rContext.is() )
    {
        Reference< XInterface > xIFace( rContext->getServiceManager()->createInstanceWithContext(
             u"com.sun.star.script.EventAttacher"_ustr, rContext)  );
        if ( xIFace.is() )
        {
            xAttacher.set( xIFace, UNO_QUERY );
        }
        xConverter = Converter::create(rContext);
    }

    Reference< XInitialization > xInit( xAttacher, UNO_QUERY );
    if( xInit.is() )
    {
        xInit->initialize({ Any(rIntrospection) });
    }
}

const Reference< XIdlReflection > & ImplEventAttacherManager::getReflection(std::unique_lock<std::mutex>&)
{
    // Do we already have a service? If not, create one.
    if( !mxCoreReflection.is() )
    {
        mxCoreReflection = theCoreReflection::get(mxContext);
    }
    return mxCoreReflection;
}


std::deque< AttacherIndex_Impl >::iterator ImplEventAttacherManager::implCheckIndex( sal_Int32 _nIndex )
{
    if ( (_nIndex < 0) || (o3tl::make_unsigned(_nIndex) >= aIndex.size()) )
        throw IllegalArgumentException(u"wrong index"_ustr, static_cast<cppu::OWeakObject*>(this), 1);

    std::deque<AttacherIndex_Impl>::iterator aIt = aIndex.begin() + _nIndex;
    return aIt;
}

// Methods of XEventAttacherManager
void SAL_CALL ImplEventAttacherManager::registerScriptEvent
(
    sal_Int32 nIndex,
    const ScriptEventDescriptor& ScriptEvent
)
{
    std::unique_lock l(m_aMutex);
    registerScriptEvent(l, nIndex, ScriptEvent);
}

void ImplEventAttacherManager::registerScriptEvent
(
    std::unique_lock<std::mutex>&,
    sal_Int32 nIndex,
    const ScriptEventDescriptor& ScriptEvent
)
{
    // Examine the index and apply the array
    std::deque<AttacherIndex_Impl>::iterator aIt = implCheckIndex( nIndex );

    ScriptEventDescriptor aEvt = ScriptEvent;
    sal_Int32 nLastDot = aEvt.ListenerType.lastIndexOf('.');
    if (nLastDot != -1)
        aEvt.ListenerType = aEvt.ListenerType.copy(nLastDot+1);
    aIt->aEventList.push_back( aEvt );

    // register new Event
    for( auto& rObj : aIt->aObjList )
    {
        Reference< XAllListener > xAll =
            new AttacherAllListener_Impl( this, ScriptEvent.ScriptType, ScriptEvent.ScriptCode );
        try
        {
            rObj.aAttachedListenerSeq.push_back( xAttacher->attachSingleEventListener( rObj.xTarget, xAll,
                        rObj.aHelper, ScriptEvent.ListenerType,
                        ScriptEvent.AddListenerParam, ScriptEvent.EventMethod ) );
        }
        catch( Exception& )
        {
        }
    }
}


void SAL_CALL ImplEventAttacherManager::registerScriptEvents
(
    sal_Int32 nIndex,
    const Sequence< ScriptEventDescriptor >& ScriptEvents
)
{
    std::unique_lock l(m_aMutex);
    registerScriptEvents(l, nIndex, ScriptEvents);
}

void ImplEventAttacherManager::registerScriptEvents
(
    std::unique_lock<std::mutex>& l,
    sal_Int32 nIndex,
    const Sequence< ScriptEventDescriptor >& ScriptEvents
)
{
    // Examine the index and apply the array
    std::deque< AttachedObject_Impl > aList = implCheckIndex( nIndex )->aObjList;
    for( const auto& rObj : aList )
        detach( l, nIndex, rObj.xTarget );

    sal_Int32 nLen = ScriptEvents.getLength();
    for( sal_Int32 i = 0 ; i < nLen ; i++ )
        registerScriptEvent(l, nIndex, ScriptEvents[i]);

    for( const auto& rObj : aList )
        attach( l, nIndex, rObj.xTarget, rObj.aHelper );
}


void SAL_CALL ImplEventAttacherManager::revokeScriptEvent
(
    sal_Int32 nIndex,
    const OUString& ListenerType,
    const OUString& EventMethod,
    const OUString& ToRemoveListenerParam
)
{
    std::unique_lock l(m_aMutex);

    std::deque<AttacherIndex_Impl>::iterator aIt = implCheckIndex( nIndex );

    std::deque< AttachedObject_Impl > aList = aIt->aObjList;
    for( const auto& rObj : aList )
        detach( l, nIndex, rObj.xTarget );

    std::u16string_view aLstType = ListenerType;
    size_t nLastDot = aLstType.rfind('.');
    if (nLastDot != std::u16string_view::npos)
        aLstType = aLstType.substr(nLastDot+1);

    auto aEvtIt = std::find_if(aIt->aEventList.begin(), aIt->aEventList.end(),
        [&aLstType, &EventMethod, &ToRemoveListenerParam](const ScriptEventDescriptor& rEvent) {
            return aLstType              == rEvent.ListenerType
                && EventMethod           == rEvent.EventMethod
                && ToRemoveListenerParam == rEvent.AddListenerParam;
        });
    if (aEvtIt != aIt->aEventList.end())
        aIt->aEventList.erase( aEvtIt );

    for( const auto& rObj : aList )
        attach( l, nIndex, rObj.xTarget, rObj.aHelper );
}


void SAL_CALL ImplEventAttacherManager::revokeScriptEvents(sal_Int32 nIndex )
{
    std::unique_lock l(m_aMutex);
    std::deque<AttacherIndex_Impl>::iterator aIt = implCheckIndex( nIndex );

    std::deque< AttachedObject_Impl > aList = aIt->aObjList;
    for( const auto& rObj : aList )
        detach( l, nIndex, rObj.xTarget );
    aIt->aEventList.clear();
    for( const auto& rObj : aList )
        attach( l, nIndex, rObj.xTarget, rObj.aHelper );
}


void SAL_CALL ImplEventAttacherManager::insertEntry(sal_Int32 nIndex)
{
    std::unique_lock l(m_aMutex);
    if( nIndex < 0 )
        throw IllegalArgumentException(u"negative index"_ustr, static_cast<cppu::OWeakObject*>(this), 1);

    insertEntry(l, nIndex);
}

void ImplEventAttacherManager::insertEntry(std::unique_lock<std::mutex>&, sal_Int32 nIndex)
{
    if ( o3tl::make_unsigned(nIndex) >= aIndex.size() )
        aIndex.resize(nIndex+1);

    AttacherIndex_Impl aTmp;
    aIndex.insert( aIndex.begin() + nIndex, aTmp );
}

void SAL_CALL ImplEventAttacherManager::removeEntry(sal_Int32 nIndex)
{
    std::unique_lock l(m_aMutex);
    std::deque<AttacherIndex_Impl>::iterator aIt = implCheckIndex( nIndex );

    std::deque< AttachedObject_Impl > aList = aIt->aObjList;
    for( const auto& rObj : aList )
        detach( l, nIndex, rObj.xTarget );

    aIndex.erase( aIt );
}


Sequence< ScriptEventDescriptor > SAL_CALL ImplEventAttacherManager::getScriptEvents(sal_Int32 nIndex)
{
    std::unique_lock l(m_aMutex);
    std::deque<AttacherIndex_Impl>::iterator aIt = implCheckIndex( nIndex );
    return comphelper::containerToSequence(aIt->aEventList);
}


void SAL_CALL ImplEventAttacherManager::attach(sal_Int32 nIndex, const Reference< XInterface >& xObject, const Any & Helper)
{
    std::unique_lock l(m_aMutex);
    if( nIndex < 0 || !xObject.is() )
        throw IllegalArgumentException(u"negative index, or null object"_ustr, static_cast<cppu::OWeakObject*>(this), -1);
    attach(l, nIndex, xObject, Helper);
}

void ImplEventAttacherManager::attach(std::unique_lock<std::mutex>& l, sal_Int32 nIndex, const Reference< XInterface >& xObject, const Any & Helper)
{
    if( o3tl::make_unsigned(nIndex) >= aIndex.size() )
    {
        // read older files
        if( nVersion != 1 )
            throw IllegalArgumentException();
        insertEntry( l, nIndex );
        attach( l, nIndex, xObject, Helper );
        return;
    }

    std::deque< AttacherIndex_Impl >::iterator aCurrentPosition = aIndex.begin() + nIndex;

    AttachedObject_Impl aTmp;
    aTmp.xTarget = xObject;
    aTmp.aHelper = Helper;
    aCurrentPosition->aObjList.push_back( aTmp );

    AttachedObject_Impl & rCurObj = aCurrentPosition->aObjList.back();
    rCurObj.aAttachedListenerSeq = std::vector< Reference< XEventListener > >( aCurrentPosition->aEventList.size() );

    if (aCurrentPosition->aEventList.empty())
        return;

    Sequence<css::script::EventListener> aEvents(aCurrentPosition->aEventList.size());
    css::script::EventListener* p = aEvents.getArray();
    size_t i = 0;
    for (const auto& rEvent : aCurrentPosition->aEventList)
    {
        css::script::EventListener aListener;
        aListener.AllListener =
            new AttacherAllListener_Impl(this, rEvent.ScriptType, rEvent.ScriptCode);
        aListener.Helper = rCurObj.aHelper;
        aListener.ListenerType = rEvent.ListenerType;
        aListener.EventMethod = rEvent.EventMethod;
        aListener.AddListenerParam = rEvent.AddListenerParam;
        p[i++] = std::move(aListener);
    }

    try
    {
        rCurObj.aAttachedListenerSeq = comphelper::sequenceToContainer<std::vector<Reference< XEventListener >>>(
            xAttacher->attachMultipleEventListeners(rCurObj.xTarget, aEvents));
    }
    catch (const Exception&)
    {
        // Fail gracefully.
    }
}


void SAL_CALL ImplEventAttacherManager::detach(sal_Int32 nIndex, const Reference< XInterface >& xObject)
{
    std::unique_lock l(m_aMutex);
    //return;
    if( nIndex < 0 || o3tl::make_unsigned(nIndex) >= aIndex.size() || !xObject.is() )
        throw IllegalArgumentException(u"bad index or null object"_ustr, static_cast<cppu::OWeakObject*>(this), 1);
    detach(l, nIndex, xObject);
}

void ImplEventAttacherManager::detach(std::unique_lock<std::mutex>&, sal_Int32 nIndex, const Reference< XInterface >& xObject)
{
    std::deque< AttacherIndex_Impl >::iterator aCurrentPosition = aIndex.begin() + nIndex;
    auto aObjIt = std::find_if(aCurrentPosition->aObjList.begin(), aCurrentPosition->aObjList.end(),
        [&xObject](const AttachedObject_Impl& rObj) { return rObj.xTarget == xObject; });
    if (aObjIt == aCurrentPosition->aObjList.end())
        return;

    sal_Int32 i = 0;
    for( const auto& rEvt : aCurrentPosition->aEventList )
    {
        if( aObjIt->aAttachedListenerSeq[i].is() )
        {
            try
            {
                xAttacher->removeListener( aObjIt->xTarget, rEvt.ListenerType,
                                       rEvt.AddListenerParam, aObjIt->aAttachedListenerSeq[i] );
            }
            catch( Exception& )
            {
            }
        }
        ++i;
    }
    aCurrentPosition->aObjList.erase( aObjIt );
}

void SAL_CALL ImplEventAttacherManager::addScriptListener(const Reference< XScriptListener >& aListener)
{
    std::unique_lock l(m_aMutex);
    aScriptListeners.addInterface( l, aListener );
}

void SAL_CALL ImplEventAttacherManager::removeScriptListener(const Reference< XScriptListener >& aListener)
{
    std::unique_lock l(m_aMutex);
    aScriptListeners.removeInterface( l, aListener );
}


// Methods of XPersistObject
OUString SAL_CALL ImplEventAttacherManager::getServiceName()
{
    return u"com.sun.star.uno.script.EventAttacherManager"_ustr;
}

void SAL_CALL ImplEventAttacherManager::write(const Reference< XObjectOutputStream >& OutStream)
{
    std::unique_lock l(m_aMutex);
    // Don't run without XMarkableStream
    Reference< XMarkableStream > xMarkStream( OutStream, UNO_QUERY );
    if( !xMarkStream.is() )
        return;

    // Write out the version
    OutStream->writeShort( 2 );

    // Remember position for length
    sal_Int32 nObjLenMark = xMarkStream->createMark();
    OutStream->writeLong( 0 );

    OutStream->writeLong( aIndex.size() );

    // Write out sequences
    for( const auto& rIx : aIndex )
    {
        OutStream->writeLong( rIx.aEventList.size() );
        for( const auto& rDesc : rIx.aEventList )
        {
            OutStream->writeUTF( rDesc.ListenerType );
            OutStream->writeUTF( rDesc.EventMethod );
            OutStream->writeUTF( rDesc.AddListenerParam );
            OutStream->writeUTF( rDesc.ScriptType );
            OutStream->writeUTF( rDesc.ScriptCode );
        }
    }

    // The length is now known
    sal_Int32 nObjLen = xMarkStream->offsetToMark( nObjLenMark ) -4;
    xMarkStream->jumpToMark( nObjLenMark );
    OutStream->writeLong( nObjLen );
    xMarkStream->jumpToFurthest();
    xMarkStream->deleteMark( nObjLenMark );
}

void SAL_CALL ImplEventAttacherManager::read(const Reference< XObjectInputStream >& InStream)
{
    std::unique_lock l(m_aMutex);
    // Don't run without XMarkableStream
    Reference< XMarkableStream > xMarkStream( InStream, UNO_QUERY );
    if( !xMarkStream.is() )
        return;

    // Read in the version
    nVersion = InStream->readShort();

    // At first there's the data according to version 1 --
    // this part needs to be kept in later versions.
    sal_Int32 nLen = InStream->readLong();

    // Position for comparative purposes
    sal_Int32 nObjLenMark = xMarkStream->createMark();

    // Number of read sequences
    sal_Int32 nItemCount = InStream->readLong();

    for( sal_Int32 i = 0 ; i < nItemCount ; i++ )
    {
        insertEntry( l, i );
        // Read the length of the sequence
        sal_Int32 nSeqLen = InStream->readLong();

        // Display the sequences and read the descriptions
        Sequence< ScriptEventDescriptor > aSEDSeq( nSeqLen );
        ScriptEventDescriptor* pArray = aSEDSeq.getArray();
        for( sal_Int32 j = 0 ; j < nSeqLen ; j++ )
        {
            ScriptEventDescriptor& rDesc = pArray[ j ];
            rDesc.ListenerType = InStream->readUTF();
            rDesc.EventMethod = InStream->readUTF();
            rDesc.AddListenerParam = InStream->readUTF();
            rDesc.ScriptType = InStream->readUTF();
            rDesc.ScriptCode = InStream->readUTF();
        }
        registerScriptEvents( l, i, aSEDSeq );
    }

    // Have we read the specified length?
    sal_Int32 nRealLen = xMarkStream->offsetToMark( nObjLenMark );
    if( nRealLen != nLen )
    {
        // Only if the StreamVersion is > 1 and the date still follows, can
        // this be true. Otherwise, something is completely gone.
        if( nRealLen > nLen || nVersion == 1 )
        {
            OSL_FAIL( "ImplEventAttacherManager::read(): Fatal Error, wrong object length" );
        }
        else
        {   // TODO: Examine if caching the dates would be useful
            // But for now, it's easier to skip it.
            sal_Int32 nSkipCount = nLen - nRealLen;
            InStream->skipBytes( nSkipCount );
        }
    }
    xMarkStream->jumpToFurthest();
    xMarkStream->deleteMark( nObjLenMark );
}

} // namespace comphelper


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