/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of the Collabora Office 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 <comphelper/OAccessible.hxx>
#include <comphelper/accessiblecontexthelper.hxx>
#include <osl/diagnose.h>
#include <com/sun/star/accessibility/IllegalAccessibleComponentStateException.hpp>
#include <comphelper/accessibleeventnotifier.hxx>
#include <comphelper/solarmutex.hxx>


namespace comphelper
{

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

OAccessible::OAccessible()
    : WeakComponentImplHelper(GetMutex())
    , m_nClientId(0)
{
}

OAccessible::~OAccessible()
{
        // this ensures that the lock, which may be already destroyed as part of the derivee,
        // is not used anymore

    ensureDisposed();
}

void OAccessible::disposing()
{
    // rhbz#1001768: de facto this class is locked by SolarMutex;
    // do not lock m_Mutex because it may cause deadlock
    osl::Guard<SolarMutex> aGuard(SolarMutex::get());

    if ( m_nClientId )
    {
        AccessibleEventNotifier::revokeClientNotifyDisposing( m_nClientId, *this );
        m_nClientId=0;
    }
}

css::uno::Reference<com::sun::star::accessibility::XAccessibleContext>
OAccessible::getAccessibleContext()
{
    OExternalLockGuard aGuard(this);
    return this;
}

void
OAccessible::addAccessibleEventListener(const Reference<XAccessibleEventListener>& _rxListener)
{
    osl::Guard<SolarMutex> aGuard(SolarMutex::get());
        // don't use the OContextEntryGuard - it will throw an exception if we're not alive
        // anymore, while the most recent specification for XComponent states that we should
        // silently ignore the call in such a situation

    if (!_rxListener.is())
        return;

    if ( !isAlive() )
    {
        _rxListener->disposing( EventObject( *this ) );
        return;
    }

    if (!m_nClientId)
        m_nClientId = AccessibleEventNotifier::registerClient();

    AccessibleEventNotifier::addEventListener(m_nClientId, _rxListener);
}

void
OAccessible::removeAccessibleEventListener(const Reference<XAccessibleEventListener>& _rxListener)
{
    osl::Guard<SolarMutex> aGuard(SolarMutex::get());
        // don't use the OContextEntryGuard - it will throw an exception if we're not alive
        // anymore, while the most recent specification for XComponent states that we should
        // silently ignore the call in such a situation
    if ( !isAlive() )
        return;

    if ( !(_rxListener.is() && m_nClientId) )
        return;

    sal_Int32 nListenerCount = AccessibleEventNotifier::removeEventListener( m_nClientId, _rxListener );
    if ( !nListenerCount )
    {
        // no listeners anymore
        // -> revoke ourself. This may lead to the notifier thread dying (if we were the last client),
        // and at least to us not firing any events anymore, in case somebody calls
        // NotifyAccessibleEvent, again
        AccessibleEventNotifier::revokeClient( m_nClientId );
        m_nClientId = 0;
    }
}

void OAccessible::NotifyAccessibleEvent(const sal_Int16 _nEventId, const Any& _rOldValue,
                                        const Any& _rNewValue, sal_Int32 nIndexHint)
{
    if ( !m_nClientId )
        // if we don't have a client id for the notifier, then we don't have listeners, then
        // we don't need to notify anything
        return;

    // build an event object
    AccessibleEventObject aEvent(*this, _nEventId, _rNewValue, _rOldValue, nIndexHint);

    // let the notifier handle this event
    AccessibleEventNotifier::addEvent( m_nClientId, aEvent );
}

bool OAccessible::hasAccessibleListeners() const { return m_nClientId != 0; }

bool OAccessible::isAlive() const { return !rBHelper.bDisposed && !rBHelper.bInDispose; }

void OAccessible::ensureAlive() const
{
    if( !isAlive() )
        throw DisposedException();
}

void OAccessible::ensureDisposed()
{
    if ( !rBHelper.bDisposed )
    {
        OSL_ENSURE(0 == m_refCount, "OAccessible::ensureDisposed: this method _has_ to be called "
                                    "from without your dtor only!");
        acquire();
        dispose();
    }
}

OUString OAccessible::getAccessibleId() { return OUString(); }

sal_Int64 OAccessible::getAccessibleIndexInParent()
{
    OExternalLockGuard aGuard( this );

    try
    {
        Reference< XAccessibleContext > xParentContext( implGetParentContext() );
        if (!xParentContext.is())
            return -1;

        //  iterate over parent's children and search for this object
        sal_Int64 nChildCount = xParentContext->getAccessibleChildCount();
        for (sal_Int64 nChild = 0; nChild < nChildCount; ++nChild)
        {
            Reference< XAccessible > xChild( xParentContext->getAccessibleChild( nChild ) );
            if (xChild.is() && xChild->getAccessibleContext().get() == this)
                return nChild;
        }
    }
    catch( const Exception& )
    {
        OSL_FAIL("OAccessible::getAccessibleIndexInParent: caught an exception!");
    }

    return -1;
}

Locale OAccessible::getLocale()
{
    // simply ask the parent
    Reference< XAccessible > xParent = getAccessibleParent();
    Reference< XAccessibleContext > xParentContext;
    if ( xParent.is() )
        xParentContext = xParent->getAccessibleContext();

    if ( !xParentContext.is() )
        throw IllegalAccessibleComponentStateException( OUString(), *this );

    return xParentContext->getLocale();
}

Reference<XAccessibleContext> OAccessible::implGetParentContext()
{
    Reference< XAccessible > xParent = getAccessibleParent();
    Reference< XAccessibleContext > xParentContext;
    if ( xParent.is() )
        xParentContext = xParent->getAccessibleContext();
    return xParentContext;
}

bool OAccessible::containsPoint(const awt::Point& _rPoint)
{
    OExternalLockGuard aGuard( this );
    awt::Rectangle aBounds( implGetBounds() );
    return  ( _rPoint.X >= 0 )
        &&  ( _rPoint.Y >= 0 )
        &&  ( _rPoint.X < aBounds.Width )
        &&  ( _rPoint.Y < aBounds.Height );
}

awt::Point OAccessible::getLocation()
{
    OExternalLockGuard aGuard( this );
    awt::Rectangle aBounds( implGetBounds() );
    return awt::Point( aBounds.X, aBounds.Y );
}

awt::Point OAccessible::getLocationOnScreen()
{
    OExternalLockGuard aGuard( this );

    awt::Point aScreenLoc( 0, 0 );

    Reference< XAccessibleComponent > xParentComponent( implGetParentContext(), UNO_QUERY );
    OSL_ENSURE(xParentComponent.is(), "OAccessible::getLocationOnScreen: no parent component!");
    if ( xParentComponent.is() )
    {
        awt::Point aParentScreenLoc( xParentComponent->getLocationOnScreen() );
        awt::Point aOwnRelativeLoc( getLocation() );
        aScreenLoc.X = aParentScreenLoc.X + aOwnRelativeLoc.X;
        aScreenLoc.Y = aParentScreenLoc.Y + aOwnRelativeLoc.Y;
    }

    return aScreenLoc;
}

awt::Size OAccessible::getSize()
{
    OExternalLockGuard aGuard( this );
    awt::Rectangle aBounds( implGetBounds() );
    return awt::Size( aBounds.Width, aBounds.Height );
}

awt::Rectangle OAccessible::getBounds()
{
    OExternalLockGuard aGuard( this );
    return implGetBounds();
}

OUString OAccessible::getTitledBorderText()
{
    OExternalLockGuard aGuard(this);
    return OUString();
}

OUString OAccessible::getToolTipText()
{
    OExternalLockGuard aGuard(this);
    return OUString();
}

}   // namespace comphelper


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