/**************************************************************
 * 
 * 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
 * 
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 * 
 *************************************************************/



// MARKER(update_precomp.py): autogen include statement, do not remove
#include "precompiled_framework.hxx"
#include <dispatch/helpagentdispatcher.hxx>
#include <threadhelp/readguard.hxx>
#include <threadhelp/writeguard.hxx>
#include <com/sun/star/awt/XWindow2.hpp>
#include <com/sun/star/awt/PosSize.hpp>
#include <com/sun/star/awt/Size.hpp>
#include <com/sun/star/awt/Rectangle.hpp>
#include <toolkit/helper/vclunohelper.hxx>
#include <svtools/helpopt.hxx>
#include <vcl/svapp.hxx>
#include <vcl/help.hxx>

namespace css = ::com::sun::star;

//........................................................................
namespace framework
{

//-----------------------------------------------
DEFINE_XINTERFACE_4(HelpAgentDispatcher                         ,
                    OWeakObject                                 ,
                    DIRECT_INTERFACE (css::lang::XTypeProvider ),
                    DIRECT_INTERFACE (css::frame::XDispatch    ),
                    DIRECT_INTERFACE (css::awt::XWindowListener),
                    DIRECT_INTERFACE (css::lang::XEventListener))

//-----------------------------------------------
DEFINE_XTYPEPROVIDER_2(HelpAgentDispatcher     ,
                       css::lang::XTypeProvider,
                       css::frame::XDispatch   )

//--------------------------------------------------------------------
HelpAgentDispatcher::HelpAgentDispatcher( const css::uno::Reference< css::frame::XFrame >& xParentFrame)
	: ThreadHelpBase	(&Application::GetSolarMutex())
	, m_sCurrentURL		(							  )
	, m_xContainerWindow(							  )
	, m_xAgentWindow	(							  )
	, m_aTimer			(							  )
	, m_xSelfHold		(							  )
{
	// It's required that this class has to be contructed with a valid frame.
	// And "valid" means: the frame must already bound to a valid container window.
	m_xContainerWindow = xParentFrame->getContainerWindow();
}

//--------------------------------------------------------------------
HelpAgentDispatcher::~HelpAgentDispatcher()
{
	implts_stopTimer();
	implts_ignoreCurrentURL();
	
	// Needed ... because it was create as "new VCLWindow()" ! Such windows must be disposed explicitly.
	css::uno::Reference< css::lang::XComponent > xAgentWindow(m_xAgentWindow, css::uno::UNO_QUERY);
	if (xAgentWindow.is())
		xAgentWindow->dispose();
}

//--------------------------------------------------------------------
void SAL_CALL HelpAgentDispatcher::dispatch(const css::util::URL& 								   aURL ,
											const css::uno::Sequence< css::beans::PropertyValue >&)
	throw(css::uno::RuntimeException)
{
	// silently drop the request if the new URL was marked to be ignored next time.
	sal_Int32 nAllowedToIgnore = SvtHelpOptions().getAgentIgnoreURLCounter(aURL.Complete);
	if (nAllowedToIgnore < 1)
		return;

	// stop the expiration timer for the old URL
	// The timer will add the old URL to the list of ignorable URLs.
	// So m_sCurrentURL must be set AFTER the timer was stopped !!!
	implts_stopTimer();
	
	// SAFE ->
	WriteGuard aWriteLock(m_aLock);
	m_sCurrentURL = aURL.Complete;
	aWriteLock.unlock();
	// <- SAFE
	
	// start the expiration timer for the new URL
	implts_startTimer();

	// make sure the agent window is shown
	implts_showAgentWindow();
}

//--------------------------------------------------------------------
void SAL_CALL HelpAgentDispatcher::addStatusListener(const css::uno::Reference< css::frame::XStatusListener >&,
													 const css::util::URL&)
	throw(css::uno::RuntimeException)
{
	// no status available
}

//--------------------------------------------------------------------
void SAL_CALL HelpAgentDispatcher::removeStatusListener(const css::uno::Reference< css::frame::XStatusListener >&,
														const css::util::URL&)
	throw(css::uno::RuntimeException)
{
	// no status available
}

//--------------------------------------------------------------------
void SAL_CALL HelpAgentDispatcher::windowResized(const css::awt::WindowEvent&)
	throw(css::uno::RuntimeException)
{
	implts_positionAgentWindow();
}

//--------------------------------------------------------------------
void SAL_CALL HelpAgentDispatcher::windowMoved(const css::awt::WindowEvent&)
	throw(css::uno::RuntimeException)
{
	implts_positionAgentWindow();
}

//--------------------------------------------------------------------
void SAL_CALL HelpAgentDispatcher::windowShown(const css::lang::EventObject&)
	throw(css::uno::RuntimeException)
{
	implts_showAgentWindow();
}

//--------------------------------------------------------------------
void SAL_CALL HelpAgentDispatcher::windowHidden(const css::lang::EventObject&)
	throw(css::uno::RuntimeException)
{
	implts_hideAgentWindow();
}

//--------------------------------------------------------------------
void SAL_CALL HelpAgentDispatcher::disposing(const css::lang::EventObject& aEvent)
	throw(css::uno::RuntimeException)
{
	// SAFE ->
	WriteGuard aWriteLock(m_aLock);
	
	// Already disposed ?!
	if (! m_xContainerWindow.is())
		return;
	// Wrong broadcaster ?!
	if (aEvent.Source != m_xContainerWindow)
		return;
	
    css::uno::Reference< css::uno::XInterface > xSelfHoldUntilMethodEnds(static_cast< css::frame::XDispatch* >(this), css::uno::UNO_QUERY_THROW);
	m_xSelfHold.clear();
	
	aWriteLock.unlock();
	// <- SAFE
	
	implts_stopTimer();
	implts_hideAgentWindow();
	implts_ignoreCurrentURL();
	
	// SAFE ->
	aWriteLock.lock();
	m_xContainerWindow.clear();
	css::uno::Reference< css::lang::XComponent > xAgentWindow(m_xAgentWindow, css::uno::UNO_QUERY);
	m_xAgentWindow.clear();
	aWriteLock.unlock();
	// <- SAFE
	
	// Needed ... because it was create as "new VCLWindow()" ! Such windows must be disposed explicitly.
	if (xAgentWindow.is())
		xAgentWindow->dispose();
}

//--------------------------------------------------------------------
void HelpAgentDispatcher::helpRequested()
{
	implts_stopTimer();
	implts_hideAgentWindow();
	implts_acceptCurrentURL();
}

//-----------------------------------------------
void HelpAgentDispatcher::closeAgent()
{
	implts_stopTimer();
	implts_hideAgentWindow();
	implts_ignoreCurrentURL();
}

//--------------------------------------------------------------------
void HelpAgentDispatcher::implts_acceptCurrentURL()
{
	// SAFE ->
	WriteGuard aWriteLock(m_aLock);
	
	::rtl::OUString sAcceptedURL  = m_sCurrentURL;
					m_sCurrentURL = ::rtl::OUString();
	
	aWriteLock.unlock();
	// <- SAFE
	
	// We must make sure that this URL isnt marked as ignored by the user.
	// Otherwhise the user wont see the corresponding help content in the future.
	SvtHelpOptions().resetAgentIgnoreURLCounter(sAcceptedURL);

	// show the right help content
	// SOLAR SAFE ->
	{
		::vos::OGuard aSolarLock(Application::GetSolarMutex());
		Help* pHelp = Application::GetHelp();
		if (pHelp)
			pHelp->Start(sAcceptedURL, NULL);
	}
	// <- SOLAR SAFE
}

//--------------------------------------------------------------------
void HelpAgentDispatcher::implts_ignoreCurrentURL()
{
	// SAFE ->
	WriteGuard aWriteLock(m_aLock);
	
	::rtl::OUString sIgnoredURL   = m_sCurrentURL;
					m_sCurrentURL = ::rtl::OUString();
	
	aWriteLock.unlock();
	// <- SAFE
	
	if (sIgnoredURL.getLength())
		SvtHelpOptions().decAgentIgnoreURLCounter(sIgnoredURL);
}

//--------------------------------------------------------------------
void HelpAgentDispatcher::implts_stopTimer()
{
	// SAFE ->
	WriteGuard aWriteLock(m_aLock);
	m_xSelfHold.clear();
	aWriteLock.unlock();
	// <- SAFE
	
	// SOLAR SAFE ->
	// Timer access needs no "own lock" ! It lives if we live ...
	// But it requires locking of the solar mutex ... because it's a vcl based timer.
	{
		::vos::OGuard aSolarLock(Application::GetSolarMutex());
		if (! m_aTimer.IsActive())
			return;
		m_aTimer.Stop();
	}
	// <- SOLAR SAFE
}

//--------------------------------------------------------------------
void HelpAgentDispatcher::implts_startTimer()
{
	// SOLAR SAFE ->
	// Timer access needs no "own lock" ! It lives if we live ...
	// But it requires locking of the solar mutex ... because it's a vcl based timer.
	{
		::vos::OGuard aSolarLock(Application::GetSolarMutex());
		if (m_aTimer.IsActive())
			return;
	}
	// <- SOLAR SAFE
	
	// SAFE ->
	// Timer uses pointer to this help agent dispatcher ...
	// But normaly we are ref counted. So we must make sure that this
	// dispatcher isnt killed during the timer runs .-)
	WriteGuard aWriteLock(m_aLock);
	m_xSelfHold = css::uno::Reference< css::uno::XInterface >(static_cast< css::frame::XDispatch* >(this), css::uno::UNO_QUERY_THROW);
	aWriteLock.unlock();
	// <- SAFE
	
	sal_Int32 nTime = SvtHelpOptions().GetHelpAgentTimeoutPeriod();
	
	// SOLAR SAFE ->
	// Timer access needs no "own lock" ! It lives if we live ...
	// But it requires locking of the solar mutex ... because it's a vcl based timer.
	{
		::vos::OGuard aSolarLock(Application::GetSolarMutex());
		m_aTimer.SetTimeout(nTime*1000); // sec => ms !
		m_aTimer.Start();
	}
}

//-----------------------------------------------
IMPL_LINK(HelpAgentDispatcher, implts_timerExpired, void*,)
{
    // This method is called by using a pointer to us.
    // But we must be aware that we can be destroyed hardly
    // if our uno reference will be gone!
    // => Hold this object alive till this method finish its work.
	// SAFE ->
	WriteGuard aWriteLock(m_aLock);
    css::uno::Reference< css::uno::XInterface > xSelfHoldUntilMethodEnds(static_cast< css::frame::XDispatch* >(this), css::uno::UNO_QUERY_THROW);
	m_xSelfHold.clear();
	aWriteLock.unlock();
	// <- SAFE

	implts_hideAgentWindow();
	implts_ignoreCurrentURL();
	
	return 0;
}

//--------------------------------------------------------------------
void HelpAgentDispatcher::implts_showAgentWindow()
{
	// SAFE ->
	ReadGuard aReadLock(m_aLock);
	css::uno::Reference< css::awt::XWindow2 > xContainerWindow(m_xContainerWindow, css::uno::UNO_QUERY_THROW);
	aReadLock.unlock();
	// <- SAFE
	
	css::uno::Reference< css::awt::XWindow > xAgentWindow = implts_ensureAgentWindow();
	
	if (
		(xContainerWindow.is() 		  ) &&
		(xAgentWindow.is() 		  	  ) &&
		(xContainerWindow->isVisible())
	   )
	{
		// make sure that agent window resists at the right place .-)
		implts_positionAgentWindow();
		xAgentWindow->setVisible(sal_True);
	}
}

//--------------------------------------------------------------------
void HelpAgentDispatcher::implts_hideAgentWindow()
{
	css::uno::Reference< css::awt::XWindow > xAgentWindow = implts_ensureAgentWindow();
	if (xAgentWindow.is())
		xAgentWindow->setVisible(sal_False);
}

//--------------------------------------------------------------------
void HelpAgentDispatcher::implts_positionAgentWindow()
{
	// SAFE ->
	ReadGuard aReadLock(m_aLock);
	css::uno::Reference< css::awt::XWindow > xContainerWindow = m_xContainerWindow;
	aReadLock.unlock();
	// <- SAFE
	
	css::uno::Reference< css::awt::XWindow > xAgentWindow = implts_ensureAgentWindow();
	if (
		(! xContainerWindow.is())  ||
		(! xAgentWindow.is()    )
	   )
		return;
	
		  ::svt::HelpAgentWindow* pAgentWindow   = (::svt::HelpAgentWindow*)VCLUnoHelper::GetWindow(xAgentWindow);
	const css::awt::Rectangle 	  aContainerSize = xContainerWindow->getPosSize();
	const Size                	  aAgentSize     = pAgentWindow->getPreferredSizePixel();

    sal_Int32 nW = aAgentSize.Width() ;
    sal_Int32 nH = aAgentSize.Height();

    if (nW < 1)
        nW = 100;
    if (nH < 1)
        nH = 100;

	sal_Int32 nX = aContainerSize.Width  - nW;
	sal_Int32 nY = aContainerSize.Height - nH;

	// TODO: use a surrogate if the container window is too small to contain the full-sized agent window
	xAgentWindow->setPosSize(nX, nY, nW, nH, css::awt::PosSize::POSSIZE);
}

//--------------------------------------------------------------------
css::uno::Reference< css::awt::XWindow > HelpAgentDispatcher::implts_ensureAgentWindow()
{
	// SAFE ->
	ReadGuard aReadLock(m_aLock);
	if (m_xAgentWindow.is())
		return m_xAgentWindow;
	css::uno::Reference< css::awt::XWindow > xContainerWindow = m_xContainerWindow;
	aReadLock.unlock();
	// <- SAFE
	
	if (!xContainerWindow.is())
		return css::uno::Reference< css::awt::XWindow >();
	
	::svt::HelpAgentWindow* pAgentWindow = 0;
	// SOLAR SAFE ->
	{
		::vos::OGuard aSolarLock(Application::GetSolarMutex());
		// create the agent window
		Window*	pContainerWindow = VCLUnoHelper::GetWindow(xContainerWindow);
				pAgentWindow 	 = new ::svt::HelpAgentWindow(pContainerWindow);
		pAgentWindow->setCallback(this);
	}
	// <- SOLAR SAFE
	
	// SAFE ->
	WriteGuard aWriteLock(m_aLock);
	m_xAgentWindow = VCLUnoHelper::GetInterface(pAgentWindow);
	css::uno::Reference< css::awt::XWindow > xAgentWindow = m_xAgentWindow;
	aWriteLock.unlock();
	// <- SAFE
	
	// add as window listener to the container window so we can maintain the property position of the agent window
	xContainerWindow->addWindowListener(this);

	// SOLAR SAFE ->
	{
		::vos::OGuard aSolarLock(Application::GetSolarMutex());
		// establish callback for our internal used timer.
		// Note: Its only active, if the timer will be started ...
		m_aTimer.SetTimeoutHdl(LINK(this, HelpAgentDispatcher, implts_timerExpired));
	}
	// <- SOLAR SAFE
	
	return xAgentWindow;
}

} // namespace framework

