/**************************************************************
 * 
 * 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.
 * 
 *************************************************************/



#include "precompiled_sd.hxx"

#include "framework/ConfigurationController.hxx"

#include "framework/Configuration.hxx"
#include "framework/FrameworkHelper.hxx"
#include "ConfigurationUpdater.hxx"
#include "ConfigurationControllerBroadcaster.hxx"
#include "ConfigurationTracer.hxx"
#include "GenericConfigurationChangeRequest.hxx"
#include "ResourceFactoryManager.hxx"
#include "UpdateRequest.hxx"
#include "ChangeRequestQueueProcessor.hxx"
#include "ConfigurationClassifier.hxx"
#include "ViewShellBase.hxx"
#include "UpdateLockManager.hxx"
#include "DrawController.hxx"
#include <com/sun/star/drawing/framework/XControllerManager.hpp>
#include <com/sun/star/util/XURLTransformer.hpp>

#include <comphelper/stl_types.hxx>
#include <vos/mutex.hxx>
#include <vcl/svapp.hxx>

using namespace ::com::sun::star;
using namespace ::com::sun::star::uno;
using namespace ::com::sun::star::drawing::framework;
using rtl::OUString;
using ::sd::framework::FrameworkHelper;

#undef VERBOSE
//#define VERBOSE 3


namespace sd { namespace framework {

Reference<XInterface> SAL_CALL ConfigurationController_createInstance (
    const Reference<XComponentContext>& rxContext)
{
    (void)rxContext;
    return static_cast<XWeak*>(new ConfigurationController());
}




OUString ConfigurationController_getImplementationName (void) throw(RuntimeException)
{
    return OUString(RTL_CONSTASCII_USTRINGPARAM(
        "com.sun.star.comp.Draw.framework.configuration.ConfigurationController"));
}




Sequence<rtl::OUString> SAL_CALL ConfigurationController_getSupportedServiceNames (void)
    throw (RuntimeException)
{
	static const OUString sServiceName(OUString::createFromAscii(
        "com.sun.star.drawing.framework.ConfigurationController"));
	return Sequence<rtl::OUString>(&sServiceName, 1);
}




//----- ConfigurationController::Implementation -------------------------------

class ConfigurationController::Implementation
{
public:
    Implementation (
        ConfigurationController& rController,
        const Reference<frame::XController>& rxController);
    ~Implementation (void);

    Reference<XControllerManager> mxControllerManager;

    /** The Broadcaster class implements storing and calling of listeners.
    */
    ::boost::shared_ptr<ConfigurationControllerBroadcaster> mpBroadcaster;

    /** The requested configuration which is modifed (asynchronously) by
        calls to requestResourceActivation() and
        requestResourceDeactivation().  The mpConfigurationUpdater makes the
        current configuration reflect the content of this one.
    */
    ::com::sun::star::uno::Reference<
        ::com::sun::star::drawing::framework::XConfiguration> mxRequestedConfiguration;

    ViewShellBase* mpBase;
    
    ::boost::shared_ptr<ResourceFactoryManager> mpResourceFactoryContainer;

    ::boost::shared_ptr<ConfigurationControllerResourceManager> mpResourceManager;

    ::boost::shared_ptr<ConfigurationUpdater> mpConfigurationUpdater;

    /** The queue processor ownes the queue of configuration change request
        objects and processes the objects.
    */
    ::boost::scoped_ptr<ChangeRequestQueueProcessor> mpQueueProcessor;

    ::boost::shared_ptr<ConfigurationUpdaterLock> mpConfigurationUpdaterLock;

    sal_Int32 mnLockCount;
};




//===== ConfigurationController::Lock =========================================

ConfigurationController::Lock::Lock (const Reference<XConfigurationController>& rxController)
    : mxController(rxController)
{
    OSL_ASSERT(mxController.is());
    
    if (mxController.is())
        mxController->lock();
}

 
 
  
ConfigurationController::Lock::~Lock (void)
{
    if (mxController.is())
        mxController->unlock();
}




//===== ConfigurationController ===============================================

ConfigurationController::ConfigurationController (void) throw()
    : ConfigurationControllerInterfaceBase(MutexOwner::maMutex),
      mpImplementation(),
      mbIsDisposed(false)
{
}




ConfigurationController::~ConfigurationController (void) throw()
{
}




void SAL_CALL ConfigurationController::disposing (void)
{
    if (mpImplementation.get() == NULL)
        return;

#if defined VERBOSE && VERBOSE>=1
    OSL_TRACE("ConfigurationController::disposing\n");
    OSL_TRACE("    requesting empty configuration\n");
#endif
    // To destroy all resources an empty configuration is requested and then,
    // synchronously, all resulting requests are processed.
    mpImplementation->mpQueueProcessor->Clear();
    restoreConfiguration(new Configuration(this,false));
    mpImplementation->mpQueueProcessor->ProcessUntilEmpty();
#if defined VERBOSE && VERBOSE>=1
    OSL_TRACE("    all requests processed\n");
#endif

    // Now that all resources have been deactivated, mark the controller as
    // disposed.
    mbIsDisposed = true;

    // Release the listeners.
	lang::EventObject aEvent;
	aEvent.Source = uno::Reference<uno::XInterface>((cppu::OWeakObject*)this);

    {
        const ::vos::OGuard aSolarGuard (Application::GetSolarMutex());
        mpImplementation->mpBroadcaster->DisposeAndClear();
    }

    mpImplementation->mpQueueProcessor.reset();
    mpImplementation->mxRequestedConfiguration = NULL;
    mpImplementation.reset();
}




void ConfigurationController::ProcessEvent (void)
{
    if (mpImplementation.get() != NULL)
    {
        OSL_ASSERT(mpImplementation->mpQueueProcessor.get()!=NULL);

        mpImplementation->mpQueueProcessor->ProcessOneEvent();
    }
}




void ConfigurationController::RequestSynchronousUpdate (void)
{
    if (mpImplementation.get() == NULL)
        return;
    if (mpImplementation->mpQueueProcessor.get() == 0)
        return;
    mpImplementation->mpQueueProcessor->ProcessUntilEmpty();
}




//----- XConfigurationControllerBroadcaster -----------------------------------

void SAL_CALL ConfigurationController::addConfigurationChangeListener (
    const Reference<XConfigurationChangeListener>& rxListener,
    const ::rtl::OUString& rsEventType,
    const Any& rUserData)
    throw (RuntimeException)
{
    ::osl::MutexGuard aGuard (maMutex);

	ThrowIfDisposed();
    OSL_ASSERT(mpImplementation.get()!=NULL);
    mpImplementation->mpBroadcaster->AddListener(rxListener, rsEventType, rUserData);
}




void SAL_CALL ConfigurationController::removeConfigurationChangeListener (
    const Reference<XConfigurationChangeListener>& rxListener)
    throw (RuntimeException)
{
    ::osl::MutexGuard aGuard (maMutex);

	ThrowIfDisposed();
    mpImplementation->mpBroadcaster->RemoveListener(rxListener);
}




void SAL_CALL ConfigurationController::notifyEvent (
    const ConfigurationChangeEvent& rEvent)
    throw (RuntimeException)
{
	ThrowIfDisposed();
    mpImplementation->mpBroadcaster->NotifyListeners(rEvent);
}





//----- XConfigurationController ----------------------------------------------
    
void SAL_CALL ConfigurationController::lock (void)
    throw (RuntimeException)
{
    OSL_ASSERT(mpImplementation.get()!=NULL);
    OSL_ASSERT(mpImplementation->mpConfigurationUpdater.get()!=NULL);

    ::osl::MutexGuard aGuard (maMutex);
    ThrowIfDisposed();

    
    ++mpImplementation->mnLockCount;
    if (mpImplementation->mpConfigurationUpdaterLock.get()==NULL)
        mpImplementation->mpConfigurationUpdaterLock
            = mpImplementation->mpConfigurationUpdater->GetLock();
}




void SAL_CALL ConfigurationController::unlock (void)
    throw (RuntimeException)
{
    ::osl::MutexGuard aGuard (maMutex);

    // Allow unlocking while the ConfigurationController is being disposed
    // (but not when that is done and the controller is disposed.)
    if (rBHelper.bDisposed)
        ThrowIfDisposed();

    OSL_ASSERT(mpImplementation->mnLockCount>0);
    --mpImplementation->mnLockCount;
    if (mpImplementation->mnLockCount == 0)
        mpImplementation->mpConfigurationUpdaterLock.reset();
}




void SAL_CALL ConfigurationController::requestResourceActivation (
    const Reference<XResourceId>& rxResourceId,
    ResourceActivationMode eMode)
    throw (RuntimeException)
{
    ::osl::MutexGuard aGuard (maMutex);
   	ThrowIfDisposed();

    // Check whether we are being disposed.  This is handled differently
    // then being completely disposed because the first thing disposing()
    // does is to deactivate all remaining resources.  This is done via
    // regular methods which must not throw DisposedExceptions.  Therefore
    // we just return silently during that stage.
    if (rBHelper.bInDispose)
    {
#if defined VERBOSE && VERBOSE>=1
        OSL_TRACE("ConfigurationController::requestResourceActivation(): ignoring %s\n",
            OUStringToOString(
                FrameworkHelper::ResourceIdToString(rxResourceId), RTL_TEXTENCODING_UTF8).getStr());
#endif
        return;
    }

#if defined VERBOSE && VERBOSE>=2
    OSL_TRACE("ConfigurationController::requestResourceActivation() %s\n",
        OUStringToOString(
            FrameworkHelper::ResourceIdToString(rxResourceId), RTL_TEXTENCODING_UTF8).getStr());
#endif

    if (rxResourceId.is())
    {
        if (eMode == ResourceActivationMode_REPLACE)
        {
            // Get a list of the matching resources and create deactivation
            // requests for them.
            Sequence<Reference<XResourceId> > aResourceList (
                mpImplementation->mxRequestedConfiguration->getResources(
                    rxResourceId->getAnchor(),
                    rxResourceId->getResourceTypePrefix(),
                    AnchorBindingMode_DIRECT));

            for (sal_Int32 nIndex=0; nIndex<aResourceList.getLength(); ++nIndex)
            {
                // Do not request the deactivation of the resource for which
                // this method was called.  Doing it would not change the
                // outcome but would result in unnecessary work.
                if (rxResourceId->compareTo(aResourceList[nIndex]) == 0)
                    continue;

                // Request the deactivation of a resource and all resources
                // linked to it.
                requestResourceDeactivation(aResourceList[nIndex]);
            }
        }
    
        Reference<XConfigurationChangeRequest> xRequest(
            new GenericConfigurationChangeRequest(
                rxResourceId,
                GenericConfigurationChangeRequest::Activation));
        postChangeRequest(xRequest);
    }
}




void SAL_CALL ConfigurationController::requestResourceDeactivation (
    const Reference<XResourceId>& rxResourceId)
    throw (RuntimeException)
{
    ::osl::MutexGuard aGuard (maMutex);
    ThrowIfDisposed();

#if defined VERBOSE && VERBOSE>=2
    OSL_TRACE("ConfigurationController::requestResourceDeactivation() %s\n",
            OUStringToOString(
                FrameworkHelper::ResourceIdToString(rxResourceId), RTL_TEXTENCODING_UTF8).getStr());
#endif

    if (rxResourceId.is())
    {
        // Request deactivation of all resources linked to the specified one
        // as well.
        const Sequence<Reference<XResourceId> > aLinkedResources (
            mpImplementation->mxRequestedConfiguration->getResources(
                rxResourceId,
                OUString(),
                AnchorBindingMode_DIRECT));
        const sal_Int32 nCount (aLinkedResources.getLength());
        for (sal_Int32 nIndex=0; nIndex<nCount; ++nIndex)
        {
            // We do not add deactivation requests directly but call this
            // method recursively, so that when one time there are resources
            // linked to linked resources, these are handled correctly, too.
            requestResourceDeactivation(aLinkedResources[nIndex]);
        }

        // Add a deactivation request for the specified resource.
        Reference<XConfigurationChangeRequest> xRequest(
            new GenericConfigurationChangeRequest(
                rxResourceId,
                GenericConfigurationChangeRequest::Deactivation));
        postChangeRequest(xRequest);
    }
}




Reference<XResource> SAL_CALL ConfigurationController::getResource (
    const Reference<XResourceId>& rxResourceId)
    throw (RuntimeException)
{
    ::osl::MutexGuard aGuard (maMutex);
    ThrowIfDisposed();

    ConfigurationControllerResourceManager::ResourceDescriptor aDescriptor (
        mpImplementation->mpResourceManager->GetResource(rxResourceId));
    return aDescriptor.mxResource;
}




void SAL_CALL ConfigurationController::update (void)
    throw (RuntimeException)
{
    ::osl::MutexGuard aGuard (maMutex);
    ThrowIfDisposed();

    if (mpImplementation->mpQueueProcessor->IsEmpty())
    {
        // The queue is empty.  Add another request that does nothing but
        // asynchronously trigger a request for an update.
        mpImplementation->mpQueueProcessor->AddRequest(new UpdateRequest());
    }
    else
    {
        // The queue is not empty, so we rely on the queue processor to
        // request an update automatically when the queue becomes empty.
    }
}




sal_Bool SAL_CALL ConfigurationController::hasPendingRequests (void)
    throw (RuntimeException)
{
    ::osl::MutexGuard aGuard (maMutex);
    ThrowIfDisposed();
    
    return ! mpImplementation->mpQueueProcessor->IsEmpty();
}





void SAL_CALL ConfigurationController::postChangeRequest (
    const Reference<XConfigurationChangeRequest>& rxRequest)
    throw (RuntimeException)
{
    ::osl::MutexGuard aGuard (maMutex);
    ThrowIfDisposed();

    mpImplementation->mpQueueProcessor->AddRequest(rxRequest);
}




Reference<XConfiguration> SAL_CALL ConfigurationController::getRequestedConfiguration (void)
    throw (RuntimeException)
{
    ::osl::MutexGuard aGuard (maMutex);
    ThrowIfDisposed();

    if (mpImplementation->mxRequestedConfiguration.is())
        return Reference<XConfiguration>(
            mpImplementation->mxRequestedConfiguration->createClone(), UNO_QUERY);
    else
        return Reference<XConfiguration>();
}




Reference<XConfiguration> SAL_CALL ConfigurationController::getCurrentConfiguration (void)
    throw (RuntimeException)
{
    ::osl::MutexGuard aGuard (maMutex);
    ThrowIfDisposed();

    Reference<XConfiguration> xCurrentConfiguration(
        mpImplementation->mpConfigurationUpdater->GetCurrentConfiguration());
    if (xCurrentConfiguration.is())
        return Reference<XConfiguration>(xCurrentConfiguration->createClone(), UNO_QUERY);
    else
        return Reference<XConfiguration>();
}




/** The given configuration is restored by generating the appropriate set of
    activation and deactivation requests.
*/
void SAL_CALL ConfigurationController::restoreConfiguration (
    const Reference<XConfiguration>& rxNewConfiguration)
    throw (RuntimeException)
{
    ::osl::MutexGuard aGuard (maMutex);
    ThrowIfDisposed();

    // We will probably be making a couple of activation and deactivation
    // requests so lock the configuration controller and let it later update
    // all changes at once.
    ::boost::shared_ptr<ConfigurationUpdaterLock> pLock (
        mpImplementation->mpConfigurationUpdater->GetLock());

    // Get lists of resources that are to be activated or deactivated.
    Reference<XConfiguration> xCurrentConfiguration (mpImplementation->mxRequestedConfiguration);
#if defined VERBOSE && VERBOSE>=1
    OSL_TRACE("ConfigurationController::restoreConfiguration(\n");
    ConfigurationTracer::TraceConfiguration(rxNewConfiguration, "requested configuration");
    ConfigurationTracer::TraceConfiguration(xCurrentConfiguration, "current configuration");
#endif
    ConfigurationClassifier aClassifier (rxNewConfiguration, xCurrentConfiguration);
    aClassifier.Partition();
#if defined VERBOSE && VERBOSE>=3
    aClassifier.TraceResourceIdVector(
        "requested but not current resources:\n", aClassifier.GetC1minusC2());
    aClassifier.TraceResourceIdVector(
        "current but not requested resources:\n", aClassifier.GetC2minusC1());
    aClassifier.TraceResourceIdVector(
        "requested and current resources:\n", aClassifier.GetC1andC2());
#endif

    ConfigurationClassifier::ResourceIdVector::const_iterator iResource;

    // Request the deactivation of resources that are not requested in the
    // new configuration.
    const ConfigurationClassifier::ResourceIdVector& rResourcesToDeactivate (
        aClassifier.GetC2minusC1());
    for (iResource=rResourcesToDeactivate.begin();
         iResource!=rResourcesToDeactivate.end();
         ++iResource)
    {
        requestResourceDeactivation(*iResource);
    }

    // Request the activation of resources that are requested in the
    // new configuration but are not part of the current configuration.
    const ConfigurationClassifier::ResourceIdVector& rResourcesToActivate (
        aClassifier.GetC1minusC2());
    for (iResource=rResourcesToActivate.begin();
         iResource!=rResourcesToActivate.end();
         ++iResource)
    {
        requestResourceActivation(*iResource, ResourceActivationMode_ADD);
    }

    pLock.reset();
}




//----- XResourceFactoryManager -----------------------------------------------
    
void SAL_CALL ConfigurationController::addResourceFactory(
    const OUString& sResourceURL,
    const Reference<XResourceFactory>& rxResourceFactory)
    throw (RuntimeException)
{
    ::osl::MutexGuard aGuard (maMutex);
    ThrowIfDisposed();
    mpImplementation->mpResourceFactoryContainer->AddFactory(sResourceURL, rxResourceFactory);
}



    
void SAL_CALL ConfigurationController::removeResourceFactoryForURL(
    const OUString& sResourceURL)
    throw (RuntimeException)
{
    ::osl::MutexGuard aGuard (maMutex);
    ThrowIfDisposed();
    mpImplementation->mpResourceFactoryContainer->RemoveFactoryForURL(sResourceURL);
}




void SAL_CALL ConfigurationController::removeResourceFactoryForReference(
    const Reference<XResourceFactory>& rxResourceFactory)
    throw (RuntimeException)
{
    ::osl::MutexGuard aGuard (maMutex);
    ThrowIfDisposed();
    mpImplementation->mpResourceFactoryContainer->RemoveFactoryForReference(rxResourceFactory);
}




Reference<XResourceFactory> SAL_CALL ConfigurationController::getResourceFactory (
    const OUString& sResourceURL)
    throw (RuntimeException)
{
    ::osl::MutexGuard aGuard (maMutex);
    ThrowIfDisposed();

    return mpImplementation->mpResourceFactoryContainer->GetFactory(sResourceURL);
}




//----- XInitialization -------------------------------------------------------

void SAL_CALL ConfigurationController::initialize (const Sequence<Any>& aArguments)
    throw (Exception, RuntimeException)
{
    ::osl::MutexGuard aGuard (maMutex);

    if (aArguments.getLength() == 1)
    {
        const ::vos::OGuard aSolarGuard (Application::GetSolarMutex());

        mpImplementation.reset(new Implementation(
            *this,
            Reference<frame::XController>(aArguments[0], UNO_QUERY_THROW)));
    }
}




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

void ConfigurationController::ThrowIfDisposed (void) const
    throw (::com::sun::star::lang::DisposedException)
{
	if (mbIsDisposed)
	{
        throw lang::DisposedException (
            OUString(RTL_CONSTASCII_USTRINGPARAM(
                "ConfigurationController object has already been disposed")),
            const_cast<uno::XWeak*>(static_cast<const uno::XWeak*>(this)));
    }

    if (mpImplementation.get() == NULL)
    {
        OSL_ASSERT(mpImplementation.get() != NULL);
        throw RuntimeException(
            OUString(RTL_CONSTASCII_USTRINGPARAM(
                "ConfigurationController not initialized")),
            const_cast<uno::XWeak*>(static_cast<const uno::XWeak*>(this)));
    }
}




//===== ConfigurationController::Implementation ===============================

ConfigurationController::Implementation::Implementation (
    ConfigurationController& rController,
    const Reference<frame::XController>& rxController)
    : mxControllerManager(rxController, UNO_QUERY_THROW),
      mpBroadcaster(new ConfigurationControllerBroadcaster(&rController)),
      mxRequestedConfiguration(new Configuration(&rController, true)),
      mpBase(NULL),
      mpResourceFactoryContainer(new ResourceFactoryManager(mxControllerManager)),
      mpResourceManager(
          new ConfigurationControllerResourceManager(mpResourceFactoryContainer,mpBroadcaster)),
      mpConfigurationUpdater(
          new ConfigurationUpdater(mpBroadcaster, mpResourceManager,mxControllerManager)),
      mpQueueProcessor(new ChangeRequestQueueProcessor(&rController,mpConfigurationUpdater)),
      mpConfigurationUpdaterLock(),
      mnLockCount(0)
{
    mpQueueProcessor->SetConfiguration(mxRequestedConfiguration);
}




ConfigurationController::Implementation::~Implementation (void)
{
}




} } // end of namespace sd::framework
