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

#ifdef SOLARIS
// HACK: prevent conflict between STLPORT and Workshop headers on Solaris 8
#include <ctime>
#endif

#include <string> // HACK: prevent conflict between STLPORT and Workshop headers
#include <com/sun/star/util/XURLTransformer.hpp>
#include <com/sun/star/frame/XController.hpp>
#include <com/sun/star/frame/XFrameActionListener.hpp>
#include <com/sun/star/frame/XComponentLoader.hpp>
#include <com/sun/star/frame/XFrame.hpp>
#include <com/sun/star/frame/FrameActionEvent.hpp>
#include <com/sun/star/frame/FrameAction.hpp>
#include <com/sun/star/beans/PropertyValue.hpp>
#include <cppuhelper/weak.hxx>
#include <svl/eitem.hxx>
#include <svl/intitem.hxx>
#include <svl/stritem.hxx>
#include <svl/visitem.hxx>
#include <comphelper/processfactory.hxx>

#ifndef GCC
#endif

#include <sfx2/app.hxx>
#include <sfx2/appuno.hxx>
#include "statcach.hxx"
#include <sfx2/msg.hxx>
#include <sfx2/ctrlitem.hxx>
#include <sfx2/dispatch.hxx>
#include "sfxtypes.hxx"
#include <sfx2/sfxuno.hxx>
#include <sfx2/unoctitm.hxx>
#include <sfx2/msgpool.hxx>
#include <sfx2/viewfrm.hxx>

using namespace ::com::sun::star;
using namespace ::com::sun::star::uno;
using namespace ::com::sun::star::util;

//====================================================================

DBG_NAME(SfxStateCache)
DBG_NAME(SfxStateCacheSetState)

SFX_IMPL_XINTERFACE_2( BindDispatch_Impl, OWeakObject, ::com::sun::star::frame::XStatusListener, ::com::sun::star::lang::XEventListener )
SFX_IMPL_XTYPEPROVIDER_2( BindDispatch_Impl, ::com::sun::star::frame::XStatusListener, ::com::sun::star::lang::XEventListener )

//-----------------------------------------------------------------------------
BindDispatch_Impl::BindDispatch_Impl( const ::com::sun::star::uno::Reference< ::com::sun::star::frame::XDispatch > & rDisp, const ::com::sun::star::util::URL& rURL, SfxStateCache *pStateCache, const SfxSlot* pS )
	: xDisp( rDisp )
	, aURL( rURL )
	, pCache( pStateCache )
    , pSlot( pS )
{
    DBG_ASSERT( pCache && pSlot, "Invalid BindDispatch!");
    aStatus.IsEnabled = sal_True;
}

void SAL_CALL BindDispatch_Impl::disposing( const ::com::sun::star::lang::EventObject& ) throw( ::com::sun::star::uno::RuntimeException )
{
	if ( xDisp.is() )
	{
        xDisp->removeStatusListener( (::com::sun::star::frame::XStatusListener*) this, aURL );
		xDisp = ::com::sun::star::uno::Reference< ::com::sun::star::frame::XDispatch > ();
	}
}

void SAL_CALL  BindDispatch_Impl::statusChanged( const ::com::sun::star::frame::FeatureStateEvent& rEvent ) throw( ::com::sun::star::uno::RuntimeException )
{
	aStatus = rEvent;
    if ( !pCache )
		return;

	::com::sun::star::uno::Reference< ::com::sun::star::frame::XStatusListener >  xRef( (::cppu::OWeakObject*)this, ::com::sun::star::uno::UNO_QUERY );
	if ( aStatus.Requery )
		pCache->Invalidate( sal_True );
	else
	{
        SfxPoolItem *pItem=NULL;
        sal_uInt16 nId = pCache->GetId();
        SfxItemState eState = SFX_ITEM_DISABLED;
        // pCache->Invalidate( sal_False );
        if ( !aStatus.IsEnabled )
        {
            // default
        }
        else if (aStatus.State.hasValue())
		{
            eState = SFX_ITEM_AVAILABLE;
			::com::sun::star::uno::Any aAny = aStatus.State;

			::com::sun::star::uno::Type pType =	aAny.getValueType();
			if ( pType == ::getBooleanCppuType() )
			{
				sal_Bool bTemp = false;
				aAny >>= bTemp ;
				pItem = new SfxBoolItem( nId, bTemp );
			}
			else if ( pType == ::getCppuType((const sal_uInt16*)0) )
			{
				sal_uInt16 nTemp = 0;
				aAny >>= nTemp ;
				pItem = new SfxUInt16Item( nId, nTemp );
			}
			else if ( pType == ::getCppuType((const sal_uInt32*)0) )
			{
				sal_uInt32 nTemp = 0;
				aAny >>= nTemp ;
				pItem = new SfxUInt32Item( nId, nTemp );
			}
			else if ( pType == ::getCppuType((const ::rtl::OUString*)0) )
			{
				::rtl::OUString sTemp ;
				aAny >>= sTemp ;
				pItem = new SfxStringItem( nId, sTemp );
			}
			else
            {
                if ( pSlot )
                    pItem = pSlot->GetType()->CreateItem();
                if ( pItem )
                {
                    pItem->SetWhich( nId );
                    pItem->PutValue( aAny );
                }
                else
                    pItem = new SfxVoidItem( nId );
            }
		}
		else
		{
			// DONTCARE status
            pItem = new SfxVoidItem(0);
            eState = SFX_ITEM_UNKNOWN;
		}

        for ( SfxControllerItem *pCtrl = pCache->GetItemLink();
            pCtrl;
            pCtrl = pCtrl->GetItemLink() )
            pCtrl->StateChanged( nId, eState, pItem );

       delete pItem;
	}
}

void BindDispatch_Impl::Release()
{
	if ( xDisp.is() )
	{
        xDisp->removeStatusListener( (::com::sun::star::frame::XStatusListener*) this, aURL );
		xDisp = ::com::sun::star::uno::Reference< ::com::sun::star::frame::XDispatch > ();
	}

	pCache = NULL;
	release();
}

const ::com::sun::star::frame::FeatureStateEvent& BindDispatch_Impl::GetStatus() const
{
	return aStatus;
}

void BindDispatch_Impl::Dispatch( uno::Sequence < beans::PropertyValue > aProps, sal_Bool bForceSynchron )
{
    if ( xDisp.is() && aStatus.IsEnabled )
    {
		sal_Int32 nLength = aProps.getLength();
        aProps.realloc(nLength+1);
        aProps[nLength].Name = DEFINE_CONST_UNICODE("SynchronMode");
        aProps[nLength].Value <<= bForceSynchron ;
        xDisp->dispatch( aURL, aProps );
    }
}

//--------------------------------------------------------------------

/*	Dieser Konstruktor fuer einen ungueltigen Cache, der sich also
	bei der ersten Anfrage zun"achst updated.
 */

SfxStateCache::SfxStateCache( sal_uInt16 nFuncId ):
    pDispatch( 0 ),
    nId(nFuncId),
    pInternalController(0),
    pController(0),
    pLastItem( 0 ),
    eLastState( 0 ),
    bItemVisible( sal_True )
{
	DBG_MEMTEST();
	DBG_CTOR(SfxStateCache,	0);
	bCtrlDirty = sal_True;
	bSlotDirty = sal_True;
	bItemDirty = sal_True;
}

//--------------------------------------------------------------------

/*	Der Destruktor pr"uft per Assertion, ob noch Controller angemeldet
	sind.
 */

SfxStateCache::~SfxStateCache()
{
	DBG_MEMTEST();
	DBG_DTOR(SfxStateCache,	0);
    DBG_ASSERT( pController == 0 && pInternalController == 0, "es sind noch Controller angemeldet" );
	if ( !IsInvalidItem(pLastItem) )
		delete pLastItem;
	if ( pDispatch )
	{
		pDispatch->Release();
		pDispatch = NULL;
	}
}

//--------------------------------------------------------------------
// invalidates the cache (next request will force update)
void SfxStateCache::Invalidate( sal_Bool bWithMsg )
{
	bCtrlDirty = sal_True;
	if ( bWithMsg )
	{
		bSlotDirty = sal_True;
		aSlotServ.SetSlot( 0 );
		if ( pDispatch )
		{
			pDispatch->Release();
			pDispatch = NULL;
		}
	}
}

//--------------------------------------------------------------------

// gets the corresponding function from the dispatcher or the cache

const SfxSlotServer* SfxStateCache::GetSlotServer( SfxDispatcher &rDispat , const ::com::sun::star::uno::Reference< ::com::sun::star::frame::XDispatchProvider > & xProv )
{
	DBG_MEMTEST();
	DBG_CHKTHIS(SfxStateCache, 0);

	if ( bSlotDirty )
	{
        // get the SlotServer; we need it for internal controllers anyway, but also in most cases
        rDispat._FindServer( nId, aSlotServ, sal_False );

        DBG_ASSERT( !pDispatch, "Old Dispatch not removed!" );

        // we don't need to check the dispatch provider if we only have an internal controller
        if ( xProv.is() )
        {
            const SfxSlot* pSlot = aSlotServ.GetSlot();
            if ( !pSlot )
                // get the slot - even if it is disabled on the dispatcher
                pSlot = SfxSlotPool::GetSlotPool( rDispat.GetFrame() ).GetSlot( nId );

            if ( !pSlot || !pSlot->pUnoName )
            {
                bSlotDirty = sal_False;
                bCtrlDirty = sal_True;
                return aSlotServ.GetSlot()? &aSlotServ: 0;
            }

            // create the dispatch URL from the slot data
            ::com::sun::star::util::URL aURL;
            ::rtl::OUString aCmd = DEFINE_CONST_UNICODE(".uno:");
            aURL.Protocol = aCmd;
            aURL.Path = ::rtl::OUString::createFromAscii( pSlot->GetUnoName() );
            aCmd += aURL.Path;
            aURL.Complete = aCmd;
            aURL.Main = aCmd;

            // try to get a dispatch object for this command
            ::com::sun::star::uno::Reference< ::com::sun::star::frame::XDispatch >  xDisp = xProv->queryDispatch( aURL, ::rtl::OUString(), 0 );
            if ( xDisp.is() )
            {
                // test the dispatch object if it is just a wrapper for a SfxDispatcher
				::com::sun::star::uno::Reference< ::com::sun::star::lang::XUnoTunnel > xTunnel( xDisp, ::com::sun::star::uno::UNO_QUERY );
                SfxOfficeDispatch* pDisp = NULL;
                if ( xTunnel.is() )
                {
                    sal_Int64 nImplementation = xTunnel->getSomething(SfxOfficeDispatch::impl_getStaticIdentifier());
                    pDisp = reinterpret_cast< SfxOfficeDispatch* >(sal::static_int_cast< sal_IntPtr >( nImplementation ));
                }

                if ( pDisp )
                {
                    // The intercepting object is an SFX component
                    // If this dispatch object does not use the wanted dispatcher or the AppDispatcher, it's treated like any other UNO component
                    // (intercepting by internal dispatches)
                    SfxDispatcher *pDispatcher = pDisp->GetDispatcher_Impl();
					if ( pDispatcher == &rDispat || pDispatcher == SFX_APP()->GetAppDispatcher_Impl() )
					{
                        // so we can use it directly
                        bSlotDirty = sal_False;
                        bCtrlDirty = sal_True;
                        return aSlotServ.GetSlot()? &aSlotServ: 0;
                    }
                }

                // so the dispatch object isn't a SfxDispatcher wrapper or it is one, but it uses another dispatcher, but not rDispat
                pDispatch = new BindDispatch_Impl( xDisp, aURL, this, pSlot );
                pDispatch->acquire();

                // flags must be set before adding StatusListener because the dispatch object will set the state
                bSlotDirty = sal_False;
                bCtrlDirty = sal_True;
                xDisp->addStatusListener( pDispatch, aURL );
            }
            else if ( rDispat.GetFrame() )
            {
                ::com::sun::star::uno::Reference < ::com::sun::star::frame::XDispatchProvider > xFrameProv(
                        rDispat.GetFrame()->GetFrame().GetFrameInterface(), ::com::sun::star::uno::UNO_QUERY );
                if ( xFrameProv != xProv )
                    return GetSlotServer( rDispat, xFrameProv );
            }
        }

		bSlotDirty = sal_False;
		bCtrlDirty = sal_True;
	}

    // we *always* return a SlotServer (if there is one); but in case of an external dispatch we might not use it
    // for the "real" (non internal) controllers
    return aSlotServ.GetSlot()? &aSlotServ: 0;
}


//--------------------------------------------------------------------

// Status setzen in allen Controllern

void SfxStateCache::SetState
(
	SfxItemState		eState, 	// <SfxItemState> von 'pState'
	const SfxPoolItem*	pState,		// Status des Slots, ggf. 0 oder -1
	sal_Bool bMaybeDirty
)

/*	[Beschreibung]

	Diese Methode verteilt die Status auf alle an dieser SID gebundenen
	<SfxControllerItem>s. Ist der Wert derselbe wie zuvor und wurde in-
	zwischen weder ein Controller angemeldet, noch ein Controller invalidiert,
	dann wird kein Wert weitergeleitet. Dadurch wird z.B. Flackern in
	ListBoxen vermieden.
*/

{
//    if ( pDispatch )
//        return;
	SetState_Impl( eState, pState, bMaybeDirty );
}

//--------------------------------------------------------------------

void SfxStateCache::SetVisibleState( sal_Bool bShow )
{
	SfxItemState		eState( SFX_ITEM_AVAILABLE );
	const SfxPoolItem*	pState( NULL );
	sal_Bool            bNotify( sal_False );
    sal_Bool            bDeleteItem( sal_False );

    if ( bShow != bItemVisible )
    {
        bItemVisible = bShow;
        if ( bShow )
        {
            if ( IsInvalidItem(pLastItem) || ( pLastItem == NULL ))
            {
                pState = new SfxVoidItem( nId );
                bDeleteItem = sal_True;
            }
            else
                pState = pLastItem;

            eState = eLastState;
            bNotify = ( pState != 0 );
        }
        else
        {
            pState = new SfxVisibilityItem( nId, sal_False );
            bDeleteItem = sal_True;
        }

        // Controller updaten
        if ( !pDispatch && pController )
        {
            for ( SfxControllerItem *pCtrl = pController;
                    pCtrl;
                    pCtrl = pCtrl->GetItemLink() )
                pCtrl->StateChanged( nId, eState, pState );
        }

        if ( pInternalController )
            pInternalController->StateChanged( nId, eState, pState );

        if ( !bDeleteItem )
            delete pState;
    }
}

//--------------------------------------------------------------------

void SfxStateCache::SetState_Impl
(
	SfxItemState		eState, 	// <SfxItemState> von 'pState'
	const SfxPoolItem*	pState,		// Status des Slots, ggf. 0 oder -1
	sal_Bool bMaybeDirty
)
{
    (void)bMaybeDirty; //unused
	DBG_MEMTEST();
	DBG_CHKTHIS(SfxStateCache, 0);

	// wenn zwischen Enter- und LeaveRegistrations ein hartes Update kommt
	// k"onnen zwischenzeitlich auch Cached ohne Controller exisitieren
    if ( !pController && !pInternalController )
		return;

	DBG_ASSERT( bMaybeDirty || !bSlotDirty, "setting state of dirty message" );
//	DBG_ASSERT( bCtrlDirty || ( aSlotServ.GetSlot() && aSlotServ.GetSlot()->IsMode(SFX_SLOT_VOLATILE) ), ! Discussed with MBA
//				"setting state of non dirty controller" );
    DBG_ASSERT( SfxControllerItem::GetItemState(pState) == eState, "invalid SfxItemState" );
	DBG_PROFSTART(SfxStateCacheSetState);

	// m"ussen die Controller "uberhaupt benachrichtigt werden?
	bool bNotify = bItemDirty;
	if ( !bItemDirty )
	{
		bool bBothAvailable = pLastItem && pState &&
					!IsInvalidItem(pState) && !IsInvalidItem(pLastItem);
		DBG_ASSERT( !bBothAvailable || pState != pLastItem, "setting state with own item" );
		if ( bBothAvailable )
			bNotify = pState->Type() != pLastItem->Type() ||
					  *pState != *pLastItem;
		else
			bNotify = ( pState != pLastItem ) || ( eState != eLastState );
	}

	if ( bNotify )
	{
		// Controller updaten
        if ( !pDispatch && pController )
        {
            for ( SfxControllerItem *pCtrl = pController;
                pCtrl;
                pCtrl = pCtrl->GetItemLink() )
                pCtrl->StateChanged( nId, eState, pState );
        }

        if ( pInternalController )
            ((SfxDispatchController_Impl *)pInternalController)->StateChanged( nId, eState, pState, &aSlotServ );

		// neuen Wert merken
		if ( !IsInvalidItem(pLastItem) )
			DELETEZ(pLastItem);
		if ( pState && !IsInvalidItem(pState) )
			pLastItem = pState->Clone();
		else
			pLastItem = 0;
		eLastState = eState;
		bItemDirty = sal_False;
	}

	bCtrlDirty = sal_False;
	DBG_PROFSTOP(SfxStateCacheSetState);
}


//--------------------------------------------------------------------

// alten Status in allen Controllern nochmal setzen

void SfxStateCache::SetCachedState( sal_Bool bAlways )
{
	DBG_MEMTEST();
	DBG_CHKTHIS(SfxStateCache, 0);
	DBG_ASSERT(pController==NULL||pController->GetId()==nId, "Cache mit falschem ControllerItem" );
	DBG_PROFSTART(SfxStateCacheSetState);

	// nur updaten wenn cached item vorhanden und auch verarbeitbar
	// (Wenn der State gesendet wird, mu\s sichergestellt sein, da\s ein
	// Slotserver vorhanden ist, s. SfxControllerItem::GetCoreMetric() )
	if ( bAlways || ( !bItemDirty && !bSlotDirty ) )
	{
		// Controller updaten
        if ( !pDispatch && pController )
        {
            for ( SfxControllerItem *pCtrl = pController;
                pCtrl;
                pCtrl = pCtrl->GetItemLink() )
                pCtrl->StateChanged( nId, eLastState, pLastItem );
        }

        if ( pInternalController )
            ((SfxDispatchController_Impl *)pInternalController)->StateChanged( nId, eLastState, pLastItem, &aSlotServ );

		// Controller sind jetzt ok
		bCtrlDirty = sal_True;
	}

	DBG_PROFSTOP(SfxStateCacheSetState);
}


//--------------------------------------------------------------------

// FloatingWindows in allen Controls mit dieser Id zerstoeren

void SfxStateCache::DeleteFloatingWindows()
{
	DBG_MEMTEST();
	DBG_CHKTHIS(SfxStateCache, 0);

	SfxControllerItem *pNextCtrl=0;
	for ( SfxControllerItem *pCtrl=pController; pCtrl; pCtrl=pNextCtrl )
	{
		DBG_TRACE((ByteString("pCtrl: ").Append(ByteString::CreateFromInt64((sal_uIntPtr)pCtrl))).GetBuffer());
		pNextCtrl = pCtrl->GetItemLink();
		pCtrl->DeleteFloatingWindow();
	}
}

::com::sun::star::uno::Reference< ::com::sun::star::frame::XDispatch >  SfxStateCache::GetDispatch() const
{
	if ( pDispatch )
		return pDispatch->xDisp;
	return ::com::sun::star::uno::Reference< ::com::sun::star::frame::XDispatch > ();
}

void SfxStateCache::Dispatch( const SfxItemSet* pSet, sal_Bool bForceSynchron )
{
	// protect pDispatch against destruction in the call
	::com::sun::star::uno::Reference < ::com::sun::star::frame::XStatusListener > xKeepAlive( pDispatch );
	if ( pDispatch )
	{
		uno::Sequence < beans::PropertyValue > aArgs;
		if (pSet)
			TransformItems( nId, *pSet, aArgs );
        pDispatch->Dispatch( aArgs, bForceSynchron );
	}
}