/* -*- 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 "accessiblewrapper.hxx"

#include <com/sun/star/accessibility/AccessibleEventId.hpp>
#include <com/sun/star/accessibility/AccessibleStateType.hpp>

using namespace ::comphelper;
using namespace ::com::sun::star::accessibility;
using namespace ::com::sun::star::uno;
using namespace ::com::sun::star::lang;


namespace accessibility
{
OWrappedAccessibleChildrenManager::OWrappedAccessibleChildrenManager( const Reference< XComponentContext >& _rxContext )
    :m_xContext( _rxContext )
    ,m_bTransientChildren( true )
{
}


OWrappedAccessibleChildrenManager::~OWrappedAccessibleChildrenManager( )
{
}


void OWrappedAccessibleChildrenManager::setTransientChildren( bool _bSet )
{
    m_bTransientChildren = _bSet;
}


void OWrappedAccessibleChildrenManager::setOwningAccessible( const Reference< XAccessible >& _rxAcc )
{
    OSL_ENSURE( !m_aOwningAccessible.get().is(), "OWrappedAccessibleChildrenManager::setOwningAccessible: to be called only once!" );
    m_aOwningAccessible = WeakReference< XAccessible >( _rxAcc );
}


void OWrappedAccessibleChildrenManager::removeFromCache( const Reference< XAccessible >& _rxKey )
{
    AccessibleMap::iterator aRemovedPos = m_aChildrenMap.find( _rxKey );
    if ( m_aChildrenMap.end() != aRemovedPos )
    {   // it was cached
        // remove ourself as event listener
        Reference< XComponent > xComp( aRemovedPos->first, UNO_QUERY );
        if( xComp.is() )
            xComp->removeEventListener( this );
        // and remove the entry from the map
        m_aChildrenMap.erase( aRemovedPos );
    }
}


void OWrappedAccessibleChildrenManager::invalidateAll( )
{
    // remove as event listener from the map elements
    for( const auto& rChild : m_aChildrenMap )
    {
        Reference< XComponent > xComp( rChild.first, UNO_QUERY );
        if( xComp.is() )
            xComp->removeEventListener( this );
    }
    // clear the map
    m_aChildrenMap.clear();
}


Reference< XAccessible > OWrappedAccessibleChildrenManager::getAccessibleWrapperFor(
    const Reference< XAccessible >& _rxKey )
{
    if( !_rxKey.is() )
        return {};

    // do we have this child in the cache?
    AccessibleMap::const_iterator aPos = m_aChildrenMap.find( _rxKey );
    if ( m_aChildrenMap.end() != aPos )
        return aPos->second;

    // not found in the cache, and allowed to create
    // -> new wrapper
    rtl::Reference<OAccessibleWrapper> xValue
        = new OAccessibleWrapper(m_xContext, _rxKey, m_aOwningAccessible);

    // see if we do cache children
    if ( !m_bTransientChildren )
    {
        m_aChildrenMap.emplace(_rxKey, xValue);

        // listen for disposals of inner children - this may happen when the inner context
        // is the owner for the inner children (it will dispose these children, and of course
        // not our wrapper for these children)
        Reference< XComponent > xComp( _rxKey, UNO_QUERY );
        if ( xComp.is() )
            xComp->addEventListener( this );
    }

    return xValue;
}


void OWrappedAccessibleChildrenManager::dispose()
{
    // dispose our children
    for( const auto& rChild : m_aChildrenMap )
    {
        Reference< XComponent > xComp( rChild.first, UNO_QUERY );
        if( xComp.is() )
            xComp->removeEventListener( this );

        if( rChild.second.is() )
        {
            Reference<XComponent> xContextComponent(rChild.second->getContextNoCreate(),
                                                    css::uno::UNO_QUERY);
            if (xContextComponent.is())
                xContextComponent->dispose();
        }
    }

    // clear our children
    m_aChildrenMap.clear();
}


void OWrappedAccessibleChildrenManager::implTranslateChildEventValue( const Any& _rInValue, Any& _rOutValue )
{
    _rOutValue.clear();
    Reference< XAccessible > xChild;
    if ( _rInValue >>= xChild )
        _rOutValue <<= getAccessibleWrapperFor( xChild );
}


void OWrappedAccessibleChildrenManager::translateAccessibleEvent( const AccessibleEventObject& _rEvent, AccessibleEventObject& _rTranslatedEvent )
{
    // just in case we can't translate some of the values:
    _rTranslatedEvent.NewValue = _rEvent.NewValue;
    _rTranslatedEvent.OldValue = _rEvent.OldValue;

    switch ( _rEvent.EventId )
    {
        case AccessibleEventId::CHILD:
        case AccessibleEventId::ACTIVE_DESCENDANT_CHANGED:
        case AccessibleEventId::CONTROLLED_BY_RELATION_CHANGED:
        case AccessibleEventId::CONTROLLER_FOR_RELATION_CHANGED:
        case AccessibleEventId::LABEL_FOR_RELATION_CHANGED:
        case AccessibleEventId::LABELED_BY_RELATION_CHANGED:
        case AccessibleEventId::CONTENT_FLOWS_FROM_RELATION_CHANGED:
        case AccessibleEventId::CONTENT_FLOWS_TO_RELATION_CHANGED:
            // these are events where both the old and the new value contain child references
            implTranslateChildEventValue( _rEvent.OldValue, _rTranslatedEvent.OldValue );
            implTranslateChildEventValue( _rEvent.NewValue, _rTranslatedEvent.NewValue );
            break;

        case AccessibleEventId::NAME_CHANGED:
        case AccessibleEventId::DESCRIPTION_CHANGED:
        case AccessibleEventId::ACTION_CHANGED:
        case AccessibleEventId::STATE_CHANGED:
        case AccessibleEventId::BOUNDRECT_CHANGED:
        case AccessibleEventId::INVALIDATE_ALL_CHILDREN:
        case AccessibleEventId::SELECTION_CHANGED:
        case AccessibleEventId::VISIBLE_DATA_CHANGED:
        case AccessibleEventId::VALUE_CHANGED:
        case AccessibleEventId::MEMBER_OF_RELATION_CHANGED:
        case AccessibleEventId::CARET_CHANGED:
        case AccessibleEventId::TEXT_CHANGED:
        case AccessibleEventId::HYPERTEXT_CHANGED:
        case AccessibleEventId::TABLE_CAPTION_CHANGED:
        case AccessibleEventId::TABLE_COLUMN_DESCRIPTION_CHANGED:
        case AccessibleEventId::TABLE_COLUMN_HEADER_CHANGED:
        case AccessibleEventId::TABLE_MODEL_CHANGED:
        case AccessibleEventId::TABLE_ROW_DESCRIPTION_CHANGED:
        case AccessibleEventId::TABLE_ROW_HEADER_CHANGED:
        case AccessibleEventId::TABLE_SUMMARY_CHANGED:
        // these Ids are also missed: SUB_WINDOW_OF_RELATION_CHANGED & TEXT_ATTRIBUTE_CHANGED
        case AccessibleEventId::TEXT_SELECTION_CHANGED:
            // nothing to translate
            break;

        default:
            OSL_FAIL( "OWrappedAccessibleChildrenManager::translateAccessibleEvent: unknown (or unexpected) event id!" );
            break;
    }
}


void OWrappedAccessibleChildrenManager::handleChildNotification( const AccessibleEventObject& _rEvent )
{
    if ( AccessibleEventId::INVALIDATE_ALL_CHILDREN == _rEvent.EventId )
    {   // clear our child map
        invalidateAll( );
    }
    else if ( AccessibleEventId::CHILD == _rEvent.EventId )
    {
        // check if the removed or replaced element is cached
        Reference< XAccessible > xRemoved;
        if ( _rEvent.OldValue >>= xRemoved )
            removeFromCache( xRemoved );
    }
}


void SAL_CALL OWrappedAccessibleChildrenManager::disposing( const EventObject& _rSource )
{
    // this should come from one of the inner XAccessible's of our children
    Reference< XAccessible > xSource( _rSource.Source, UNO_QUERY );
    AccessibleMap::iterator aDisposedPos = m_aChildrenMap.find( xSource );
#if OSL_DEBUG_LEVEL > 0
    if ( m_aChildrenMap.end() == aDisposedPos )
    {
        OSL_FAIL( "OWrappedAccessibleChildrenManager::disposing: where did this come from?" );
        // helper for diagnostics
        Reference< XAccessible > xOwningAccessible( m_aOwningAccessible );
        Reference< XAccessibleContext > xContext;
        try
        {
            if ( xOwningAccessible.is() )
                xContext = xOwningAccessible->getAccessibleContext();
            if ( xContext.is() )
            {
                //TODO: do something
                //OUString sName = xContext->getAccessibleName();
                //OUString sDescription = xContext->getAccessibleDescription();
                //sal_Int32 nPlaceYourBreakpointHere = 0;
            }
        }
        catch( const Exception& /*e*/ )
        {
            // silent this, it's only diagnostics which failed
        }
    }
#endif
    if ( m_aChildrenMap.end() != aDisposedPos )
    {
        m_aChildrenMap.erase( aDisposedPos );
    }
}

OAccessibleWrapper::OAccessibleWrapper( const Reference< XComponentContext >& _rxContext,
        const Reference< XAccessible >& _rxInnerAccessible, const Reference< XAccessible >& _rxParentAccessible )
    :OAccessibleWrapper_Base( )
    ,OComponentProxyAggregation( _rxContext, Reference< XComponent >( _rxInnerAccessible, UNO_QUERY ) )
    ,m_xParentAccessible( _rxParentAccessible )
    ,m_xInnerAccessible( _rxInnerAccessible )
{
}


OAccessibleWrapper::~OAccessibleWrapper( )
{
    if ( !m_rBHelper.bDisposed )
    {
        acquire();  // to prevent duplicate dtor calls
        dispose();
    }
}


IMPLEMENT_FORWARD_XTYPEPROVIDER2( OAccessibleWrapper, OComponentProxyAggregation, OAccessibleWrapper_Base )
IMPLEMENT_FORWARD_REFCOUNT( OAccessibleWrapper, OComponentProxyAggregation )


Any OAccessibleWrapper::queryInterface( const Type& _rType )
{
    // #111089# instead of the inner XAccessible the proxy XAccessible must be returned
    Any aReturn = OAccessibleWrapper_Base::queryInterface( _rType );
    if ( !aReturn.hasValue() )
        aReturn = OComponentProxyAggregation::queryInterface( _rType );

    return aReturn;
}


Reference< XAccessibleContext > OAccessibleWrapper::getContextNoCreate( ) const
{
    return m_aContext.get();
}

Reference< XAccessibleContext > SAL_CALL OAccessibleWrapper::getAccessibleContext(  )
{
    // see if the context is still alive (we cache it)
    rtl::Reference<OAccessibleContextWrapper> xContext = m_aContext.get();
    if ( !xContext.is() )
    {
        // create a new context
        Reference< XAccessibleContext > xInnerContext = m_xInnerAccessible->getAccessibleContext( );
        if ( xInnerContext.is() )
        {
            xContext = new OAccessibleContextWrapper(getComponentContext(), xInnerContext, this, m_xParentAccessible);
            // cache it
            m_aContext = unotools::WeakReference<OAccessibleContextWrapper>(xContext);
        }
    }

    return xContext;
}

OAccessibleContextWrapperHelper::OAccessibleContextWrapperHelper(
            const Reference< XComponentContext >& _rxContext,
            ::cppu::OBroadcastHelper& _rBHelper,
            const Reference< XAccessibleContext >& _rxInnerAccessibleContext,
            const Reference< XAccessible >& _rxOwningAccessible,
            const Reference< XAccessible >& _rxParentAccessible )
    :OComponentProxyAggregationHelper( _rxContext, _rBHelper )
    ,m_xInnerContext( _rxInnerAccessibleContext )
    ,m_xOwningAccessible( _rxOwningAccessible )
    ,m_xParentAccessible( _rxParentAccessible )
    // initialize the mapper for our children
    ,m_xChildMapper( new OWrappedAccessibleChildrenManager( getComponentContext() ) )
{
    // determine if we're allowed to cache children
    sal_Int64 aStates = m_xInnerContext->getAccessibleStateSet( );
    m_xChildMapper->setTransientChildren( aStates & AccessibleStateType::MANAGES_DESCENDANTS );

    m_xChildMapper->setOwningAccessible( m_xOwningAccessible );
}


void OAccessibleContextWrapperHelper::aggregateProxy( oslInterlockedCount& _rRefCount, ::cppu::OWeakObject& _rDelegator )
{
    Reference< XComponent > xInnerComponent( m_xInnerContext, UNO_QUERY );
    OSL_ENSURE( xInnerComponent.is(), "OComponentProxyAggregation::aggregateProxy: accessible is no XComponent!" );
    if ( xInnerComponent.is() )
        componentAggregateProxyFor( xInnerComponent, _rRefCount, _rDelegator );

    // add as event listener to the inner context, because we want to multiplex the AccessibleEvents
    osl_atomic_increment( &_rRefCount );
    {
        Reference< XAccessibleEventBroadcaster > xBroadcaster( m_xInner, UNO_QUERY );
        if ( xBroadcaster.is() )
            xBroadcaster->addAccessibleEventListener( this );
    }
    osl_atomic_decrement( &_rRefCount );
}


OAccessibleContextWrapperHelper::~OAccessibleContextWrapperHelper( )
{
    OSL_ENSURE( m_rBHelper.bDisposed, "OAccessibleContextWrapperHelper::~OAccessibleContextWrapperHelper: you should ensure (in your dtor) that the object is disposed!" );
}


Any SAL_CALL OAccessibleContextWrapperHelper::queryInterface( const Type& _rType )
{
    Any aReturn = OComponentProxyAggregationHelper::queryInterface( _rType );
    if ( !aReturn.hasValue() )
        aReturn = OAccessibleContextWrapperHelper_Base::queryInterface( _rType );
    return aReturn;
}


IMPLEMENT_FORWARD_XTYPEPROVIDER2( OAccessibleContextWrapperHelper, OComponentProxyAggregationHelper, OAccessibleContextWrapperHelper_Base )

void SAL_CALL OAccessibleContextWrapperHelper::notifyEvent( const AccessibleEventObject& _rEvent )
{
#if OSL_DEBUG_LEVEL > 0
    if ( AccessibleEventId::STATE_CHANGED == _rEvent.EventId )
    {
        bool bChildTransienceChanged = false;
        sal_Int64 nChangeState = 0;
        if ( _rEvent.OldValue >>= nChangeState )
            bChildTransienceChanged = bChildTransienceChanged || AccessibleStateType::MANAGES_DESCENDANTS == nChangeState;
        if ( _rEvent.NewValue >>= nChangeState )
            bChildTransienceChanged = bChildTransienceChanged || AccessibleStateType::MANAGES_DESCENDANTS == nChangeState;
        OSL_ENSURE( !bChildTransienceChanged, "OAccessibleContextWrapperHelper::notifyEvent: MANAGES_DESCENDANTS is not expected to change during runtime!" );
            // if this asserts, then we would need to update our m_bTransientChildren flag here,
            // as well as (potentially) our child cache
    }
#endif
    AccessibleEventObject aTranslatedEvent( _rEvent );

    {
        ::osl::MutexGuard aGuard( m_rBHelper.rMutex );

        // translate the event
        queryInterface( cppu::UnoType<XInterface>::get() ) >>= aTranslatedEvent.Source;
        m_xChildMapper->translateAccessibleEvent( _rEvent, aTranslatedEvent );

        // see if any of these notifications affect our child manager
        m_xChildMapper->handleChildNotification( _rEvent );

        if ( aTranslatedEvent.NewValue == m_xInner )
            aTranslatedEvent.NewValue <<= aTranslatedEvent.Source;
        if ( aTranslatedEvent.OldValue == m_xInner )
            aTranslatedEvent.OldValue <<= aTranslatedEvent.Source;
    }

    notifyTranslatedEvent( aTranslatedEvent );
}


void SAL_CALL OAccessibleContextWrapperHelper::dispose()
{
    ::osl::MutexGuard aGuard( m_rBHelper.rMutex );

    // stop multiplexing events
    Reference< XAccessibleEventBroadcaster > xBroadcaster( m_xInner, UNO_QUERY );
    OSL_ENSURE( xBroadcaster.is(), "OAccessibleContextWrapperHelper::disposing(): inner context is no broadcaster!" );
    if ( xBroadcaster.is() )
        xBroadcaster->removeAccessibleEventListener( this );

    // dispose the child cache/map
    m_xChildMapper->dispose();

    // let the base class dispose the inner component
    OComponentProxyAggregationHelper::dispose();
}


void SAL_CALL OAccessibleContextWrapperHelper::disposing( const EventObject& _rEvent )
{
    // simply disambiguate this
    OComponentProxyAggregationHelper::disposing( _rEvent );
}

IMPLEMENT_FORWARD_XINTERFACE2( OAccessibleContextWrapper, OAccessibleContextWrapper_CBase, OAccessibleContextWrapperHelper )


IMPLEMENT_FORWARD_XTYPEPROVIDER2( OAccessibleContextWrapper, OAccessibleContextWrapper_CBase, OAccessibleContextWrapperHelper )


OAccessibleContextWrapper::OAccessibleContextWrapper( const Reference< XComponentContext >& _rxContext,
        const Reference< XAccessibleContext >& _rxInnerAccessibleContext, const Reference< XAccessible >& _rxOwningAccessible,
        const Reference< XAccessible >& _rxParentAccessible )
    :OAccessibleContextWrapper_CBase( m_aMutex )
    ,OAccessibleContextWrapperHelper( _rxContext, rBHelper, _rxInnerAccessibleContext, _rxOwningAccessible, _rxParentAccessible )
    ,m_nNotifierClient( 0 )
{
    aggregateProxy( m_refCount, *this );
}


OAccessibleContextWrapper::~OAccessibleContextWrapper()
{
}


sal_Int64 SAL_CALL OAccessibleContextWrapper::getAccessibleChildCount(  )
{
    return m_xInnerContext->getAccessibleChildCount();
}


Reference< XAccessible > SAL_CALL OAccessibleContextWrapper::getAccessibleChild( sal_Int64 i )
{
    // get the child of the wrapped component
    Reference<XAccessible> xInnerChild = m_xInnerContext->getAccessibleChild(i);
    return m_xChildMapper->getAccessibleWrapperFor(xInnerChild);
}


Reference< XAccessible > SAL_CALL OAccessibleContextWrapper::getAccessibleParent(  )
{
    return m_xParentAccessible;
}


sal_Int64 SAL_CALL OAccessibleContextWrapper::getAccessibleIndexInParent(  )
{
    return m_xInnerContext->getAccessibleIndexInParent();
}


sal_Int16 SAL_CALL OAccessibleContextWrapper::getAccessibleRole(  )
{
    return m_xInnerContext->getAccessibleRole();
}


OUString SAL_CALL OAccessibleContextWrapper::getAccessibleDescription(  )
{
    return m_xInnerContext->getAccessibleDescription();
}


OUString SAL_CALL OAccessibleContextWrapper::getAccessibleName(  )
{
    return m_xInnerContext->getAccessibleName();
}


Reference< XAccessibleRelationSet > SAL_CALL OAccessibleContextWrapper::getAccessibleRelationSet(  )
{
    return m_xInnerContext->getAccessibleRelationSet();
        // TODO: if this relation set would contain relations to siblings, we would normally need
        // to wrap them, too...
}


sal_Int64 SAL_CALL OAccessibleContextWrapper::getAccessibleStateSet(  )
{
    return m_xInnerContext->getAccessibleStateSet();
}


Locale SAL_CALL OAccessibleContextWrapper::getLocale(  )
{
    return m_xInnerContext->getLocale();
}


void OAccessibleContextWrapper::notifyTranslatedEvent( const AccessibleEventObject& _rEvent )
{
    if ( m_nNotifierClient )
        AccessibleEventNotifier::addEvent( m_nNotifierClient, _rEvent );
}


void SAL_CALL OAccessibleContextWrapper::addAccessibleEventListener( const Reference< XAccessibleEventListener >& _rxListener )
{
    ::osl::MutexGuard aGuard( m_aMutex );
    if ( !m_nNotifierClient )
        m_nNotifierClient = AccessibleEventNotifier::registerClient( );
    AccessibleEventNotifier::addEventListener( m_nNotifierClient, _rxListener );
}


void SAL_CALL OAccessibleContextWrapper::removeAccessibleEventListener( const Reference< XAccessibleEventListener >& _rxListener )
{
    ::osl::MutexGuard aGuard( m_aMutex );
    if ( m_nNotifierClient )
    {
        if ( 0 == AccessibleEventNotifier::removeEventListener( m_nNotifierClient, _rxListener ) )
        {
            AccessibleEventNotifier::TClientId nId( m_nNotifierClient );
            m_nNotifierClient = 0;
            AccessibleEventNotifier::revokeClient( nId );
        }
    }
}


void OAccessibleContextWrapper::implDisposing(const css::lang::EventObject* pEvent)
{
    AccessibleEventNotifier::TClientId nClientId( 0 );

    // --- <mutex lock> -----------------------------------------
    {
        ::osl::MutexGuard aGuard( m_aMutex );

        // prepare notifying our AccessibleListeners
        if ( m_nNotifierClient )
        {
            nClientId = m_nNotifierClient;
            m_nNotifierClient = 0;
        }
    }
    // --- </mutex lock> -----------------------------------------

    // let the base class do
    if (pEvent)
        OAccessibleContextWrapperHelper::disposing(*pEvent);
    else
        OAccessibleContextWrapperHelper::dispose();

    // notify the disposal
    if ( nClientId )
        AccessibleEventNotifier::revokeClientNotifyDisposing( nClientId, *this );
}

void SAL_CALL OAccessibleContextWrapper::disposing()
{
    implDisposing(nullptr);
}

void SAL_CALL OAccessibleContextWrapper::disposing(const css::lang::EventObject& rEvent)
{
    assert(rEvent.Source == Reference<XInterface>(m_xInnerContext, UNO_QUERY)
           && "OAccessibleContextWrapper::disposing called with event source that's not the "
              "wrapped a11y context");

    implDisposing(&rEvent);
}
}   // namespace accessibility


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