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

#include "ChildWindowPane.hxx"
#include "FrameWindowPane.hxx"
#include "FullScreenPane.hxx"

#include "framework/FrameworkHelper.hxx"
#include "ViewShellBase.hxx"
#include "PaneChildWindows.hxx"
#include "DrawController.hxx"
#include "DrawDocShell.hxx"
#include <com/sun/star/drawing/framework/XControllerManager.hpp>
#include <boost/bind.hpp>


using namespace ::com::sun::star;
using namespace ::com::sun::star::uno;
using namespace ::com::sun::star::lang;
using namespace ::com::sun::star::drawing::framework;

using ::rtl::OUString;
using ::sd::framework::FrameworkHelper;

namespace {
    enum PaneId {
        CenterPaneId,
        FullScreenPaneId,
        LeftImpressPaneId,
        LeftDrawPaneId,
        RightPaneId
    };

    static const sal_Int32 gnConfigurationUpdateStartEvent(0);
    static const sal_Int32 gnConfigurationUpdateEndEvent(1);
}

namespace sd { namespace framework {


/** Store URL, XPane reference and (local) PaneId for every pane factory
    that is registered at the PaneController.
*/
class BasicPaneFactory::PaneDescriptor
{
public:
    OUString msPaneURL;
    Reference<XResource> mxPane;
    PaneId mePaneId;
    /** The mbReleased flag is set when the pane has been released.  Some
        panes are just hidden and destroyed.  When the pane is reused this
        flag is reset.
    */
    bool mbIsReleased;
    bool mbIsChildWindow;

    bool CompareURL (const OUString& rsPaneURL) { return msPaneURL.equals(rsPaneURL); }
    bool ComparePane (const Reference<XResource>& rxPane) { return mxPane==rxPane; }
};


class BasicPaneFactory::PaneContainer
    : public ::std::vector<PaneDescriptor>
{
public:
    PaneContainer (void) {}
};



Reference<XInterface> SAL_CALL BasicPaneFactory_createInstance (
    const Reference<XComponentContext>& rxContext)
{
    return Reference<XInterface>(static_cast<XWeak*>(new BasicPaneFactory(rxContext)));
}




::rtl::OUString BasicPaneFactory_getImplementationName (void) throw(RuntimeException)
{
    return ::rtl::OUString(
        RTL_CONSTASCII_USTRINGPARAM("com.sun.star.comp.Draw.framework.BasicPaneFactory"));
}




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




//===== PaneFactory ===========================================================

BasicPaneFactory::BasicPaneFactory (
    const Reference<XComponentContext>& rxContext)
    : BasicPaneFactoryInterfaceBase(m_aMutex),
      mxComponentContext(rxContext),
      mxConfigurationControllerWeak(),
      mpViewShellBase(NULL),
      mpPaneContainer(new PaneContainer),
      mbFirstUpdateSeen(false),
      mpUpdateLockManager()
{
}





BasicPaneFactory::~BasicPaneFactory (void)
{
}
    



void SAL_CALL BasicPaneFactory::disposing (void)
{
    Reference<XConfigurationController> xCC (mxConfigurationControllerWeak);
    if (xCC.is())
    {
        xCC->removeResourceFactoryForReference(this);
        xCC->removeConfigurationChangeListener(this);
        mxConfigurationControllerWeak = Reference<XConfigurationController>();
    }

    for (PaneContainer::const_iterator iDescriptor = mpPaneContainer->begin();
         iDescriptor != mpPaneContainer->end();
         ++iDescriptor)
    {
        if (iDescriptor->mbIsReleased)
        {
            Reference<XComponent> xComponent (iDescriptor->mxPane, UNO_QUERY);
            if (xComponent.is())
            {
                xComponent->removeEventListener(this);
                xComponent->dispose();
            }
        }
    }
}




void SAL_CALL BasicPaneFactory::initialize (const Sequence<Any>& aArguments)
    throw (Exception, RuntimeException)
{
    if (aArguments.getLength() > 0)
    {
        try
        {
            // Get the XController from the first argument.
            Reference<frame::XController> xController (aArguments[0], UNO_QUERY_THROW);
            mxControllerWeak = xController;

            // Tunnel through the controller to obtain access to the ViewShellBase.
            try
            {
                Reference<lang::XUnoTunnel> xTunnel (xController, UNO_QUERY_THROW);
                DrawController* pController
                    = reinterpret_cast<DrawController*>(
                        (sal::static_int_cast<sal_uIntPtr>(
                            xTunnel->getSomething(DrawController::getUnoTunnelId()))));
                mpViewShellBase = pController->GetViewShellBase();
                mpUpdateLockManager = mpViewShellBase->GetUpdateLockManager();
            }
            catch(RuntimeException&)
            {}

            Reference<XControllerManager> xCM (xController, UNO_QUERY_THROW);
            Reference<XConfigurationController> xCC (xCM->getConfigurationController());
            mxConfigurationControllerWeak = xCC;

            // Add pane factories for the two left panes (one for Impress and one for
            // Draw), the center pane, and the right pane.
            if (xController.is() && xCC.is())
            {
                PaneDescriptor aDescriptor;
                aDescriptor.msPaneURL = FrameworkHelper::msCenterPaneURL;
                aDescriptor.mePaneId = CenterPaneId;
                aDescriptor.mbIsReleased = false;
                aDescriptor.mbIsChildWindow = false;
                mpPaneContainer->push_back(aDescriptor);
                xCC->addResourceFactory(aDescriptor.msPaneURL, this);

                aDescriptor.msPaneURL = FrameworkHelper::msFullScreenPaneURL;
                aDescriptor.mePaneId = FullScreenPaneId;
                mpPaneContainer->push_back(aDescriptor);
                xCC->addResourceFactory(aDescriptor.msPaneURL, this);

                aDescriptor.msPaneURL = FrameworkHelper::msLeftImpressPaneURL;
                aDescriptor.mePaneId = LeftImpressPaneId;
                aDescriptor.mbIsChildWindow = true;
                mpPaneContainer->push_back(aDescriptor);
                xCC->addResourceFactory(aDescriptor.msPaneURL, this);

                aDescriptor.msPaneURL = FrameworkHelper::msLeftDrawPaneURL;
                aDescriptor.mePaneId = LeftDrawPaneId;
                mpPaneContainer->push_back(aDescriptor);
                xCC->addResourceFactory(aDescriptor.msPaneURL, this);

                aDescriptor.msPaneURL = FrameworkHelper::msRightPaneURL;
                aDescriptor.mePaneId = RightPaneId;
                mpPaneContainer->push_back(aDescriptor);
                xCC->addResourceFactory(aDescriptor.msPaneURL, this);
            }

            // Register as configuration change listener.
            if (xCC.is())
            {
                xCC->addConfigurationChangeListener(
                    this,
                    FrameworkHelper::msConfigurationUpdateStartEvent,
                    makeAny(gnConfigurationUpdateStartEvent));
                xCC->addConfigurationChangeListener(
                    this,
                    FrameworkHelper::msConfigurationUpdateEndEvent,
                    makeAny(gnConfigurationUpdateEndEvent));
            }
        }
        catch (RuntimeException&)
        {
            Reference<XConfigurationController> xCC (mxConfigurationControllerWeak);
            if (xCC.is())
                xCC->removeResourceFactoryForReference(this);
        }
    }
}




//===== XPaneFactory ==========================================================

Reference<XResource> SAL_CALL BasicPaneFactory::createResource (
    const Reference<XResourceId>& rxPaneId)
    throw (RuntimeException, IllegalArgumentException, WrappedTargetException)
{
    ThrowIfDisposed();

    Reference<XResource> xPane;

    // Based on the ResourceURL of the given ResourceId look up the
    // corresponding factory descriptor.
    PaneContainer::iterator iDescriptor (
        ::std::find_if (
            mpPaneContainer->begin(),
            mpPaneContainer->end(),
            ::boost::bind(&PaneDescriptor::CompareURL, _1, rxPaneId->getResourceURL())));

    if (iDescriptor != mpPaneContainer->end())
    {
        if (iDescriptor->mxPane.is())
        {
            // The pane has already been created and is still active (has
            // not yet been released).  This should not happen.
            xPane = iDescriptor->mxPane;
        }
        else
        {
            // Create a new pane.
            switch (iDescriptor->mePaneId)
            {
                case CenterPaneId:
                    xPane = CreateFrameWindowPane(rxPaneId);
                    break;

                case FullScreenPaneId:
                    xPane = CreateFullScreenPane(mxComponentContext, rxPaneId);
                    break;

                case LeftImpressPaneId:
                case LeftDrawPaneId:
                case RightPaneId:
                    xPane = CreateChildWindowPane(
                        rxPaneId,
                        *iDescriptor);
                    break;
            }
            iDescriptor->mxPane = xPane;

            // Listen for the pane being disposed.
            Reference<lang::XComponent> xComponent (xPane, UNO_QUERY);
            if (xComponent.is())
                xComponent->addEventListener(this);
        }
        iDescriptor->mbIsReleased = false;
    }
    else
    {
        // The requested pane can not be created by any of the factories
        // managed by the called BasicPaneFactory object.
        throw lang::IllegalArgumentException(
            ::rtl::OUString::createFromAscii(
                "BasicPaneFactory::createPane() called for unknown resource id"),
            NULL,
            0);
    }

    return xPane;
}





void SAL_CALL BasicPaneFactory::releaseResource (
    const Reference<XResource>& rxPane)
    throw (RuntimeException)
{
    ThrowIfDisposed();

    // Based on the given XPane reference look up the corresponding factory
    // descriptor.
    PaneContainer::iterator iDescriptor (
        ::std::find_if(
            mpPaneContainer->begin(),
            mpPaneContainer->end(),
            ::boost::bind(&PaneDescriptor::ComparePane, _1, rxPane)));

    if (iDescriptor != mpPaneContainer->end())
    {
        // The given pane was created by one of the factories.  Child
        // windows are just hidden and will be reused when requested later.
        // Other windows are disposed and their reference is reset so that
        // on the next createPane() call for the same pane type the pane is
        // created anew.
        ChildWindowPane* pChildWindowPane = dynamic_cast<ChildWindowPane*>(rxPane.get());
        if (pChildWindowPane != NULL)
        {
            iDescriptor->mbIsReleased = true;
            pChildWindowPane->Hide();
        }
        else
        {
            iDescriptor->mxPane = NULL;
            Reference<XComponent> xComponent (rxPane, UNO_QUERY);
            if (xComponent.is())
            {
                // We are disposing the pane and do not have to be informed of
                // that.
                xComponent->removeEventListener(this);
                xComponent->dispose();
            }
        }
    }
    else
    {
        // The given XPane reference is either empty or the pane was not
        // created by any of the factories managed by the called
        // BasicPaneFactory object.
        throw lang::IllegalArgumentException(
            ::rtl::OUString::createFromAscii(
                "BasicPaneFactory::releasePane() called for pane that that was not created by same factory."),
            NULL,
            0);
    }
}




//===== XConfigurationChangeListener ==========================================

void SAL_CALL BasicPaneFactory::notifyConfigurationChange (
    const ConfigurationChangeEvent& rEvent)
    throw (RuntimeException)
{
    sal_Int32 nEventType = 0;
    rEvent.UserData >>= nEventType;
    switch (nEventType)
    {
        case gnConfigurationUpdateStartEvent:
            // Lock UI updates while we are switching the views except for
            // the first time after creation.  Outherwise this leads to
            // problems after reload (missing resizes for the side panes).
            if (mbFirstUpdateSeen)
            {
                if (mpUpdateLockManager.get()!=NULL)
                {
                    //                    ::osl::Guard< ::osl::Mutex > aGuard (::osl::Mutex::getGlobalMutex());
                    //                    mpUpdateLockManager->Lock();
                }
            }
            else
                mbFirstUpdateSeen = true;
            break;

        case gnConfigurationUpdateEndEvent:
            // Unlock the update lock here when only the visibility of
            // windows but not the view shells displayed in them have
            // changed.  Otherwise the UpdateLockManager takes care of
            // unlocking at the right time.
            if (mpUpdateLockManager.get() != NULL)
            {
                ::osl::Guard< ::osl::Mutex > aGuard (::osl::Mutex::getGlobalMutex());
                //                if (mpUpdateLockManager->IsLocked())
                //                    mpUpdateLockManager->Unlock();
            }
            break;
    }
}



                
//===== lang::XEventListener ==================================================

void SAL_CALL BasicPaneFactory::disposing (
    const lang::EventObject& rEventObject)
    throw (RuntimeException)
{
    if (mxConfigurationControllerWeak == rEventObject.Source)
    {
        mxConfigurationControllerWeak = Reference<XConfigurationController>();
    }
    else
    {
        // Has one of the panes been disposed?  If so, then release the
        // reference to that pane, but not the pane descriptor.
        Reference<XResource> xPane (rEventObject.Source, UNO_QUERY);
        PaneContainer::iterator iDescriptor (
            ::std::find_if (
                mpPaneContainer->begin(),
                mpPaneContainer->end(),
                ::boost::bind(&PaneDescriptor::ComparePane, _1, xPane)));
        if (iDescriptor != mpPaneContainer->end())
        {
            iDescriptor->mxPane = NULL;
        }
    }
}




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

Reference<XResource> BasicPaneFactory::CreateFrameWindowPane (
    const Reference<XResourceId>& rxPaneId)
{
    Reference<XResource> xPane;
    
    if (mpViewShellBase != NULL)
    {
        xPane = new FrameWindowPane(rxPaneId, mpViewShellBase->GetViewWindow());
    }

    return xPane;
}




Reference<XResource> BasicPaneFactory::CreateFullScreenPane (
    const Reference<XComponentContext>& rxComponentContext,
    const Reference<XResourceId>& rxPaneId)
{
    Reference<XResource> xPane (
        new FullScreenPane(
            rxComponentContext,
            rxPaneId,
            mpViewShellBase->GetViewWindow()));

    return xPane;
}




Reference<XResource> BasicPaneFactory::CreateChildWindowPane (
    const Reference<XResourceId>& rxPaneId,
    const PaneDescriptor& rDescriptor)
{
    Reference<XResource> xPane;

    if (mpViewShellBase != NULL)
    {
        // Create the corresponding shell and determine the id of the child window.
        sal_uInt16 nChildWindowId = 0;
        ::std::auto_ptr<SfxShell> pShell;
        switch (rDescriptor.mePaneId)
        {
            case LeftImpressPaneId:
                pShell.reset(new LeftImpressPaneShell());
                nChildWindowId = ::sd::LeftPaneImpressChildWindow::GetChildWindowId();
                break;
                
            case LeftDrawPaneId:
                pShell.reset(new LeftDrawPaneShell());
                nChildWindowId = ::sd::LeftPaneDrawChildWindow::GetChildWindowId();
                break;
            
            case RightPaneId:
                pShell.reset(new ToolPanelPaneShell());
                nChildWindowId = ::sd::ToolPanelChildWindow::GetChildWindowId();
                break;

            default:
                break;
        }

        // With shell and child window id create the ChildWindowPane
        // wrapper.
        if (pShell.get() != NULL)
        {
            xPane = new ChildWindowPane(
                rxPaneId,
                nChildWindowId,
                *mpViewShellBase,
                pShell);
        }
    }

    return xPane;
}

void BasicPaneFactory::ThrowIfDisposed (void) const
    throw (lang::DisposedException)
{
	if (rBHelper.bDisposed || rBHelper.bInDispose)
	{
        throw lang::DisposedException (
            ::rtl::OUString(RTL_CONSTASCII_USTRINGPARAM(
                "BasicPaneFactory object has already been disposed")),
            const_cast<uno::XWeak*>(static_cast<const uno::XWeak*>(this)));
    }
}


} } // end of namespace sd::framework
