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

#include <osl/mutex.hxx>
#include <osl/process.h>
#include <cppuhelper/implementationentry.hxx>
#include <cppuhelper/factory.hxx>
#include <cppuhelper/implbase3.hxx>
#include <vcl/configsettings.hxx>

#include <com/sun/star/uno/XComponentContext.hpp>
#include <com/sun/star/lang/XServiceInfo.hpp>
#include <com/sun/star/lang/XSingleComponentFactory.hpp>
#include <com/sun/star/container/XContentEnumerationAccess.hpp>
#include <com/sun/star/container/XNameAccess.hpp>
#include <com/sun/star/container/XHierarchicalNameAccess.hpp>
#include <com/sun/star/beans/PropertyValue.hpp>

#include <boost/bind.hpp>
#include <vector>
#include <utility>
#include <functional>
#include <algorithm>

#define OUSTR(x) ::rtl::OUString( RTL_CONSTASCII_USTRINGPARAM(x) )
#define ARLEN(x) (sizeof (x) / sizeof *(x))


using namespace ::com::sun::star;
using namespace ::com::sun::star::uno;
using ::rtl::OUString;

namespace 
{

OUString SAL_CALL getImplName()
{
    return OUSTR("com.sun.star.comp.rendering.CanvasFactory");
}

Sequence<OUString> SAL_CALL getSuppServices()
{
    OUString name = OUSTR("com.sun.star.rendering.CanvasFactory");
    return Sequence<OUString>(&name, 1);
}

//==============================================================================
class CanvasFactory
    : public ::cppu::WeakImplHelper3< lang::XServiceInfo,
                                      lang::XMultiComponentFactory,
                                      lang::XMultiServiceFactory >
{
    typedef std::pair<OUString,Sequence<OUString> > AvailPair;
    typedef std::pair<OUString,OUString>            CachePair;
    typedef std::vector< AvailPair >                AvailVector;
    typedef std::vector< CachePair >                CacheVector;


    mutable ::osl::Mutex              m_mutex;
    Reference<XComponentContext>      m_xContext;
    Reference<container::XNameAccess> m_xCanvasConfigNameAccess;
    AvailVector                       m_aAvailableImplementations;
    AvailVector                       m_aAcceleratedImplementations;
    AvailVector                       m_aAAImplementations;
    mutable CacheVector               m_aCachedImplementations;
    mutable bool                      m_bCacheHasForcedLastImpl;
    mutable bool                      m_bCacheHasUseAcceleratedEntry;
    mutable bool                      m_bCacheHasUseAAEntry;

    void checkConfigFlag( bool& r_bFlag, 
                          bool& r_CacheFlag,
                          const OUString& nodeName ) const;
    Reference<XInterface> use(
        OUString const & serviceName,
        Sequence<Any> const & args,
        Reference<XComponentContext> const & xContext ) const;
    Reference<XInterface> lookupAndUse(
        OUString const & serviceName, Sequence<Any> const & args,
        Reference<XComponentContext> const & xContext ) const;

public:
    virtual ~CanvasFactory();
    CanvasFactory( Reference<XComponentContext> const & xContext );

    // XServiceInfo
    virtual OUString SAL_CALL getImplementationName() throw (RuntimeException);
    virtual sal_Bool SAL_CALL supportsService( OUString const & serviceName )
        throw (RuntimeException);
    virtual Sequence<OUString> SAL_CALL getSupportedServiceNames()
        throw (RuntimeException);

    // XMultiComponentFactory
    virtual Sequence<OUString> SAL_CALL getAvailableServiceNames()
        throw (RuntimeException);
    virtual Reference<XInterface> SAL_CALL createInstanceWithContext(
        OUString const & name,
        Reference<XComponentContext> const & xContext ) throw (Exception);
    virtual Reference<XInterface> SAL_CALL
    createInstanceWithArgumentsAndContext(
        OUString const & name,
        Sequence<Any> const & args,
        Reference<XComponentContext> const & xContext ) throw (Exception);

    // XMultiServiceFactory
    virtual Reference<XInterface> SAL_CALL createInstance(
        OUString const & name )
        throw (Exception);
    virtual Reference<XInterface> SAL_CALL createInstanceWithArguments(
        OUString const & name, Sequence<Any> const & args )
        throw (Exception);
};

CanvasFactory::CanvasFactory( Reference<XComponentContext> const & xContext ) :
    m_mutex(),
    m_xContext(xContext),
    m_xCanvasConfigNameAccess(),
    m_aAvailableImplementations(),
    m_aAcceleratedImplementations(),
    m_aAAImplementations(),
    m_aCachedImplementations(),
    m_bCacheHasForcedLastImpl(),
    m_bCacheHasUseAcceleratedEntry(),
    m_bCacheHasUseAAEntry()
{
    try 
    {
        // read out configuration for preferred services:
        Reference<lang::XMultiServiceFactory> xConfigProvider(
            m_xContext->getServiceManager()->createInstanceWithContext(
                OUSTR("com.sun.star.configuration.ConfigurationProvider"),
                m_xContext ), UNO_QUERY_THROW );

        Any propValue(
            makeAny( beans::PropertyValue(
                         OUSTR("nodepath"), -1,
                         makeAny( OUSTR("/org.openoffice.Office.Canvas") ),
                         beans::PropertyState_DIRECT_VALUE ) ) );

        m_xCanvasConfigNameAccess.set(
            xConfigProvider->createInstanceWithArguments(
                OUSTR("com.sun.star.configuration.ConfigurationAccess"),
                Sequence<Any>( &propValue, 1 ) ), 
            UNO_QUERY_THROW );

        propValue = makeAny( 
            beans::PropertyValue(
                OUSTR("nodepath"), -1,
                makeAny( OUSTR("/org.openoffice.Office.Canvas/CanvasServiceList") ),
                beans::PropertyState_DIRECT_VALUE ) );

        Reference<container::XNameAccess> xNameAccess(
            xConfigProvider->createInstanceWithArguments(
                OUSTR("com.sun.star.configuration.ConfigurationAccess"),
                Sequence<Any>( &propValue, 1 ) ), UNO_QUERY_THROW );
        Reference<container::XHierarchicalNameAccess> xHierarchicalNameAccess(
            xNameAccess, UNO_QUERY_THROW);

        Sequence<OUString> serviceNames = xNameAccess->getElementNames();
        const OUString* pCurr = serviceNames.getConstArray();
        const OUString* const pEnd = pCurr + serviceNames.getLength();
        while( pCurr != pEnd )
        {
            Reference<container::XNameAccess> xEntryNameAccess(
                xHierarchicalNameAccess->getByHierarchicalName(*pCurr), 
                UNO_QUERY );

            if( xEntryNameAccess.is() )
            {
                Sequence<OUString> implementationList;
                if( (xEntryNameAccess->getByName( OUSTR("PreferredImplementations") ) >>= implementationList) )
                    m_aAvailableImplementations.push_back( std::make_pair(*pCurr,implementationList) );
                if( (xEntryNameAccess->getByName( OUSTR("AcceleratedImplementations") ) >>= implementationList) )
                    m_aAcceleratedImplementations.push_back( std::make_pair(*pCurr,implementationList) );
                if( (xEntryNameAccess->getByName( OUSTR("AntialiasingImplementations") ) >>= implementationList) )
                    m_aAAImplementations.push_back( std::make_pair(*pCurr,implementationList) );
            }

            ++pCurr;
        }
    }
    catch (RuntimeException &) 
    {
        throw;
    }
    catch (Exception&) 
    {
    }

    if( m_aAvailableImplementations.empty() )
    {
        // Ugh. Looks like configuration is borked. Fake minimal
        // setup.
        Sequence<OUString> aServices(1);
        aServices[0] = OUSTR("com.sun.star.comp.rendering.Canvas.VCL");
        m_aAvailableImplementations.push_back( std::make_pair(OUSTR("com.sun.star.rendering.Canvas"),
                                                              aServices) );
        
        aServices[0] = OUSTR("com.sun.star.comp.rendering.SpriteCanvas.VCL");
        m_aAvailableImplementations.push_back( std::make_pair(OUSTR("com.sun.star.rendering.SpriteCanvas"),
                                                              aServices) );
    }
}

CanvasFactory::~CanvasFactory()
{
}

//------------------------------------------------------------------------------
Reference<XInterface> create( Reference<XComponentContext> const & xContext )
{
    return static_cast< ::cppu::OWeakObject * >(
        new CanvasFactory( xContext ) );
}

// XServiceInfo
//______________________________________________________________________________
OUString CanvasFactory::getImplementationName() throw (RuntimeException)
{
    return getImplName();
}

//______________________________________________________________________________
sal_Bool CanvasFactory::supportsService( OUString const & serviceName )
    throw (RuntimeException)
{
    return serviceName.equals(getSuppServices()[0]);
}

//______________________________________________________________________________
Sequence<OUString> CanvasFactory::getSupportedServiceNames()
    throw (RuntimeException)
{
    return getSuppServices();
}

// XMultiComponentFactory
//______________________________________________________________________________
Sequence<OUString> CanvasFactory::getAvailableServiceNames()
    throw (RuntimeException)
{
    Sequence<OUString> aServiceNames(m_aAvailableImplementations.size());
    std::transform(m_aAvailableImplementations.begin(),
                   m_aAvailableImplementations.end(),
                   aServiceNames.getArray(),
                   std::select1st<AvailPair>());
    return aServiceNames;
}

//______________________________________________________________________________
Reference<XInterface> CanvasFactory::createInstanceWithContext(
    OUString const & name, Reference<XComponentContext> const & xContext )
    throw (Exception)
{
    return createInstanceWithArgumentsAndContext(
        name, Sequence<Any>(), xContext );
}

//______________________________________________________________________________
Reference<XInterface> CanvasFactory::use(
    OUString const & serviceName,
    Sequence<Any> const & args, 
    Reference<XComponentContext> const & xContext ) const
{
    try {
        return m_xContext->getServiceManager()->createInstanceWithArgumentsAndContext(
            serviceName, args, xContext);
    }
    catch (RuntimeException &) 
    {
        throw;
    }
    catch (Exception &) 
    {
        return Reference<XInterface>();
    }
}

//______________________________________________________________________________
void CanvasFactory::checkConfigFlag( bool& r_bFlag, 
                                     bool& r_CacheFlag,
                                     const OUString& nodeName ) const
{
    if( m_xCanvasConfigNameAccess.is() )
    {
        m_xCanvasConfigNameAccess->getByName( nodeName ) >>= r_bFlag;

        if( r_CacheFlag != r_bFlag )
        {
            // cache is invalid, because of different order of
            // elements
            r_CacheFlag = r_bFlag;
            m_aCachedImplementations.clear();
        }
    }
}

//______________________________________________________________________________
Reference<XInterface> CanvasFactory::lookupAndUse(
    OUString const & serviceName, Sequence<Any> const & args,
    Reference<XComponentContext> const & xContext ) const
{
    ::osl::MutexGuard guard(m_mutex);

    // forcing last entry from impl list, if config flag set
    bool bForceLastEntry(false);
    checkConfigFlag( bForceLastEntry, 
                     m_bCacheHasForcedLastImpl,
                     OUSTR("ForceSafeServiceImpl") );
    
    // use anti-aliasing canvas, if config flag set (or not existing)
    bool bUseAAEntry(true);
    checkConfigFlag( bUseAAEntry,
                     m_bCacheHasUseAAEntry,
                     OUSTR("UseAntialiasingCanvas") );
    
    // use accelerated canvas, if config flag set (or not existing)
    bool bUseAcceleratedEntry(true);
    checkConfigFlag( bUseAcceleratedEntry,
                     m_bCacheHasUseAcceleratedEntry,
                     OUSTR("UseAcceleratedCanvas") );
    
    // try to reuse last working implementation for given service name
    const CacheVector::iterator aEnd(m_aCachedImplementations.end());
    CacheVector::iterator aMatch;
    if( (aMatch=std::find_if(m_aCachedImplementations.begin(), 
                             aEnd, 
                             boost::bind(&OUString::equals, 
                                         boost::cref(serviceName),
                                         boost::bind(
                                             std::select1st<CachePair>(),
                                             _1)))) != aEnd )
    {
        Reference<XInterface> xCanvas( use( aMatch->second, args, xContext ) );
        if(xCanvas.is())
            return xCanvas;
    }

    // lookup in available service list
    const AvailVector::const_iterator aAvailEnd(m_aAvailableImplementations.end());
    AvailVector::const_iterator aAvailImplsMatch;
    if( (aAvailImplsMatch=std::find_if(m_aAvailableImplementations.begin(), 
                                       aAvailEnd, 
                                       boost::bind(&OUString::equals, 
                                                   boost::cref(serviceName),
                                                   boost::bind(
                                                       std::select1st<AvailPair>(),
                                                       _1)))) == aAvailEnd )
    {
        return Reference<XInterface>();
    }

    const AvailVector::const_iterator aAAEnd(m_aAAImplementations.end());
    AvailVector::const_iterator aAAImplsMatch;
    if( (aAAImplsMatch=std::find_if(m_aAAImplementations.begin(), 
                                    aAAEnd, 
                                    boost::bind(&OUString::equals, 
                                                boost::cref(serviceName),
                                                boost::bind(
                                                    std::select1st<AvailPair>(),
                                                    _1)))) == aAAEnd )
    {
        return Reference<XInterface>();
    }

    const AvailVector::const_iterator aAccelEnd(m_aAcceleratedImplementations.end());
    AvailVector::const_iterator aAccelImplsMatch;
    if( (aAccelImplsMatch=std::find_if(m_aAcceleratedImplementations.begin(), 
                                       aAccelEnd, 
                                       boost::bind(&OUString::equals, 
                                                   boost::cref(serviceName),
                                                   boost::bind(
                                                       std::select1st<AvailPair>(),
                                                       _1)))) == aAccelEnd )
    {
        return Reference<XInterface>();
    }

    const Sequence<OUString> aPreferredImpls( aAvailImplsMatch->second );
    const OUString* pCurrImpl = aPreferredImpls.getConstArray();
    const OUString* const pEndImpl = pCurrImpl + aPreferredImpls.getLength();

    const Sequence<OUString> aAAImpls( aAAImplsMatch->second );
    const OUString* const pFirstAAImpl = aAAImpls.getConstArray();
    const OUString* const pEndAAImpl = pFirstAAImpl + aAAImpls.getLength();

    const Sequence<OUString> aAccelImpls( aAccelImplsMatch->second );
    const OUString* const pFirstAccelImpl = aAccelImpls.getConstArray();
    const OUString* const pEndAccelImpl = pFirstAccelImpl + aAccelImpls.getLength();

    // force last entry from impl list, if config flag set
    if( bForceLastEntry )
        pCurrImpl = pEndImpl-1;

    while( pCurrImpl != pEndImpl ) 
    {
        const OUString aCurrName(pCurrImpl->trim());

        // check whether given canvas service is listed in the
        // sequence of "accelerated canvas implementations"
        const bool bIsAcceleratedImpl(
            std::find_if(pFirstAccelImpl,
                         pEndAccelImpl,
                         boost::bind(&OUString::equals, 
                                     boost::cref(aCurrName),
                                     boost::bind(
                                         &OUString::trim,
                                         _1))) != pEndAccelImpl );

        // check whether given canvas service is listed in the
        // sequence of "antialiasing canvas implementations"
        const bool bIsAAImpl(
            std::find_if(pFirstAAImpl,
                         pEndAAImpl,
                         boost::bind(&OUString::equals, 
                                     boost::cref(aCurrName),
                                     boost::bind(
                                         &OUString::trim,
                                         _1))) != pEndAAImpl );

        // try to instantiate canvas *only* if either accel and AA
        // property match preference, *or*, if there's a mismatch, only
        // go for a less capable canvas (that effectively let those
        // pour canvas impls still work as fallbacks, should an
        // accelerated/AA one fail). Property implies configuration:
        // http://en.wikipedia.org/wiki/Truth_table#Logical_implication
        if( (!bIsAAImpl || bUseAAEntry) && (!bIsAcceleratedImpl || bUseAcceleratedEntry) )
        {
            Reference<XInterface> xCanvas(
                use( pCurrImpl->trim(), args, xContext ) );

            if(xCanvas.is()) 
            {
                if( aMatch != aEnd )
                {
                    // cache entry exists, replace dysfunctional
                    // implementation name
                    aMatch->second = pCurrImpl->trim();
                }
                else
                {
                    // new service name, add new cache entry
                    m_aCachedImplementations.push_back(std::make_pair(serviceName, 
                                                                      pCurrImpl->trim()));
                }

                return xCanvas;
            }
        }

        ++pCurrImpl;
    }

    return Reference<XInterface>();
}

//______________________________________________________________________________
Reference<XInterface> CanvasFactory::createInstanceWithArgumentsAndContext(
    OUString const & preferredOne, Sequence<Any> const & args,
    Reference<XComponentContext> const & xContext ) throw (Exception)
{
    Reference<XInterface> xCanvas(
        lookupAndUse( preferredOne, args, xContext ) );
    if(xCanvas.is())
        return xCanvas;

    // last resort: try service name directly
    return use( preferredOne, args, xContext );
}

// XMultiServiceFactory
//______________________________________________________________________________
Reference<XInterface> CanvasFactory::createInstance( OUString const & name )
    throw (Exception)
{
    return createInstanceWithArgumentsAndContext(
        name, Sequence<Any>(), m_xContext );
}

//______________________________________________________________________________
Reference<XInterface> CanvasFactory::createInstanceWithArguments(
    OUString const & name, Sequence<Any> const & args ) throw (Exception)
{
    return createInstanceWithArgumentsAndContext(
        name, args, m_xContext );
}

const ::cppu::ImplementationEntry s_entries [] = {
    {
        create,
        getImplName,
        getSuppServices,
        ::cppu::createSingleComponentFactory,
        0, 0
    },
    { 0, 0, 0, 0, 0, 0 }
};

} // anon namespace

extern "C" {

SAL_DLLPUBLIC_EXPORT void SAL_CALL component_getImplementationEnvironment(
    const sal_Char ** ppEnvTypeName, uno_Environment ** /*ppEnv*/ )
{
    *ppEnvTypeName = CPPU_CURRENT_LANGUAGE_BINDING_NAME;
}

SAL_DLLPUBLIC_EXPORT void * SAL_CALL component_getFactory(
    sal_Char const * pImplName,
    lang::XMultiServiceFactory * pServiceManager,
    registry::XRegistryKey * pRegistryKey )
{
    return ::cppu::component_getFactoryHelper(
        pImplName, pServiceManager, pRegistryKey, s_entries );
}

}

