/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*************************************************************************
 *
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 * 
 * Copyright 2000, 2010 Oracle and/or its affiliates.
 *
 * OpenOffice.org - a multi-platform office productivity suite
 *
 * This file is part of OpenOffice.org.
 *
 * OpenOffice.org is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License version 3
 * only, as published by the Free Software Foundation.
 *
 * OpenOffice.org is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License version 3 for more details
 * (a copy is included in the LICENSE file that accompanied this code).
 *
 * You should have received a copy of the GNU Lesser General Public License
 * version 3 along with OpenOffice.org.  If not, see
 * <http://www.openoffice.org/license.html>
 * for a copy of the LGPLv3 License.
 *
 ************************************************************************/

// MARKER(update_precomp.py): autogen include statement, do not remove
#include "precompiled_cppu.hxx"

#include "cppu/EnvDcp.hxx"

#include "sal/alloca.h"
#include "osl/diagnose.h"
#include "osl/interlck.h"
#include "osl/mutex.hxx"
#include "osl/module.h"
#include "osl/process.h"
#include "rtl/process.h"
#include "rtl/unload.h"
#include "rtl/string.hxx"
#include "rtl/ustring.hxx"
#include "rtl/ustrbuf.hxx"
#include "rtl/instance.hxx"
#include "typelib/typedescription.h"
#include "uno/dispatcher.h"
#include "uno/environment.h"
#include "uno/lbnames.h"
#include "prim.hxx"
#include "destr.hxx"
#include "loadmodule.hxx"

#include <boost/unordered_map.hpp>
#include <vector>
#include <stdio.h>


using ::rtl::OUString;

namespace
{

//------------------------------------------------------------------------------
inline static bool td_equals( typelib_InterfaceTypeDescription * pTD1,
                              typelib_InterfaceTypeDescription * pTD2 )
{
    return (pTD1 == pTD2 ||
            (((typelib_TypeDescription *)pTD1)->pTypeName->length ==
             ((typelib_TypeDescription *)pTD2)->pTypeName->length &&
             ::rtl_ustr_compare(
                 ((typelib_TypeDescription *) pTD1)->pTypeName->buffer,
                 ((typelib_TypeDescription *) pTD2)->pTypeName->buffer ) == 0));
}

struct ObjectEntry;
struct uno_DefaultEnvironment;

//------------------------------------------------------------------------------
struct InterfaceEntry
{
    sal_Int32 refCount;
    void * pInterface;
    uno_freeProxyFunc fpFreeProxy;
    typelib_InterfaceTypeDescription * pTypeDescr;
};

struct ObjectEntry
{
    OUString oid;
    sal_Int32 nRef;
    ::std::vector< InterfaceEntry > aInterfaces;
    bool mixedObject;
    
    inline ObjectEntry( const OUString & rOId_ );

    inline void append(
        uno_DefaultEnvironment * pEnv,
        void * pInterface, typelib_InterfaceTypeDescription * pTypeDescr,
        uno_freeProxyFunc fpFreeProxy );
    inline InterfaceEntry * find(
        typelib_InterfaceTypeDescription * pTypeDescr );
    inline sal_Int32 find( void * iface_ptr, ::std::size_t pos );
};

//------------------------------------------------------------------------------
struct FctPtrHash :
    public ::std::unary_function< const void *, ::std::size_t >
{
    ::std::size_t operator () ( const void * pKey ) const
        { return (::std::size_t) pKey; }
};

//------------------------------------------------------------------------------
struct FctOUStringHash :
    public ::std::unary_function< const OUString &, ::std::size_t >
{
    ::std::size_t operator () ( const OUString & rKey ) const
        { return rKey.hashCode(); }
};

// mapping from environment name to environment
typedef ::boost::unordered_map<
    OUString, uno_Environment *, FctOUStringHash,
    ::std::equal_to< OUString > > OUString2EnvironmentMap;

// mapping from ptr to object entry
typedef ::boost::unordered_map<
    void *, ObjectEntry *, FctPtrHash,
    ::std::equal_to< void * > > Ptr2ObjectMap;
// mapping from oid to object entry
typedef ::boost::unordered_map<
    OUString, ObjectEntry *, FctOUStringHash,
    ::std::equal_to< OUString > > OId2ObjectMap;


//==============================================================================
struct EnvironmentsData
{
    ::osl::Mutex mutex;
    OUString2EnvironmentMap aName2EnvMap;

    EnvironmentsData() : isDisposing(false) {}
    ~EnvironmentsData();

    inline void getEnvironment(
        uno_Environment ** ppEnv, const OUString & rEnvDcp, void * pContext );
    inline void registerEnvironment( uno_Environment ** ppEnv );
    inline void getRegisteredEnvironments(
        uno_Environment *** pppEnvs, sal_Int32 * pnLen,
        uno_memAlloc memAlloc, const OUString & rEnvDcp );

    bool isDisposing;
};

namespace
{
    struct theEnvironmentsData : public rtl::Static< EnvironmentsData, theEnvironmentsData > {};
}

//==============================================================================
struct uno_DefaultEnvironment : public uno_ExtEnvironment
{
    sal_Int32 nRef;
    sal_Int32 nWeakRef;
    
    ::osl::Mutex mutex;
    Ptr2ObjectMap aPtr2ObjectMap;
    OId2ObjectMap aOId2ObjectMap;
    
    uno_DefaultEnvironment(
        const OUString & rEnvDcp_, void * pContext_ );
    ~uno_DefaultEnvironment();
};

//______________________________________________________________________________
inline ObjectEntry::ObjectEntry( OUString const & rOId_ )
    : oid( rOId_ ),
      nRef( 0 ),
      mixedObject( false )
{
    aInterfaces.reserve( 2 );
}

//______________________________________________________________________________
inline void ObjectEntry::append(
    uno_DefaultEnvironment * pEnv,
    void * pInterface, typelib_InterfaceTypeDescription * pTypeDescr,
    uno_freeProxyFunc fpFreeProxy )
{
    InterfaceEntry aNewEntry;
    if (! fpFreeProxy)
        (*pEnv->acquireInterface)( pEnv, pInterface );
    aNewEntry.refCount = 1;
    aNewEntry.pInterface = pInterface;
    aNewEntry.fpFreeProxy = fpFreeProxy;
    typelib_typedescription_acquire( (typelib_TypeDescription *) pTypeDescr );
    aNewEntry.pTypeDescr = pTypeDescr;
    
    ::std::pair< Ptr2ObjectMap::iterator, bool > insertion(
        pEnv->aPtr2ObjectMap.insert( Ptr2ObjectMap::value_type(
                                         pInterface, this ) ) );
    OSL_ASSERT( insertion.second ||
                (find( pInterface, 0 ) >= 0 &&
                 // points to the same object entry:
                 insertion.first->second == this) );
    aInterfaces.push_back( aNewEntry );
}

//______________________________________________________________________________
inline InterfaceEntry * ObjectEntry::find(
    typelib_InterfaceTypeDescription * pTypeDescr_ )
{
    OSL_ASSERT( ! aInterfaces.empty() );
    if (aInterfaces.empty())
        return 0;
    
    // shortcut common case:
    OUString const & type_name =
        OUString::unacquired(
            &((typelib_TypeDescription *) pTypeDescr_)->pTypeName );
    if (type_name.equalsAsciiL(
            RTL_CONSTASCII_STRINGPARAM("com.sun.star.uno.XInterface") ))
    {
        return &aInterfaces[ 0 ];
    }
    
    ::std::size_t nSize = aInterfaces.size();
    for ( ::std::size_t nPos = 0; nPos < nSize; ++nPos )
    {
        typelib_InterfaceTypeDescription * pITD =
            aInterfaces[ nPos ].pTypeDescr;
        while (pITD)
        {
            if (td_equals( pITD, pTypeDescr_ ))
                return &aInterfaces[ nPos ];
            pITD = pITD->pBaseTypeDescription;
        }
    }
    return 0;
}

//______________________________________________________________________________
inline sal_Int32 ObjectEntry::find(
    void * iface_ptr, ::std::size_t pos )
{
    ::std::size_t size = aInterfaces.size();
    for ( ; pos < size; ++pos )
    {
        if (aInterfaces[ pos ].pInterface == iface_ptr)
            return pos;
    }
    return -1;
}

extern "C"
{

//------------------------------------------------------------------------------
static void SAL_CALL defenv_registerInterface(
    uno_ExtEnvironment * pEnv, void ** ppInterface,
    rtl_uString * pOId, typelib_InterfaceTypeDescription * pTypeDescr )
{
    OSL_ENSURE( pEnv && ppInterface && pOId && pTypeDescr, "### null ptr!" );
    OUString const & rOId = OUString::unacquired( &pOId );
    
    uno_DefaultEnvironment * that =
        static_cast< uno_DefaultEnvironment * >( pEnv );
    ::osl::ClearableMutexGuard guard( that->mutex );
    
    // try to insert dummy 0:
    std::pair<OId2ObjectMap::iterator, bool> const insertion(
        that->aOId2ObjectMap.insert( OId2ObjectMap::value_type( rOId, (ObjectEntry*)0 ) ) );
    if (insertion.second)
    {
        ObjectEntry * pOEntry = new ObjectEntry( rOId );
        insertion.first->second = pOEntry;
        ++pOEntry->nRef; // another register call on object
        pOEntry->append( that, *ppInterface, pTypeDescr, 0 );
    }
    else // object entry exists
    {
        ObjectEntry * pOEntry = insertion.first->second;
        ++pOEntry->nRef; // another register call on object
        InterfaceEntry * pIEntry = pOEntry->find( pTypeDescr );

        if (pIEntry) // type entry exists
        {
            ++pIEntry->refCount;
            if (pIEntry->pInterface != *ppInterface)
            {
                void * pInterface = pIEntry->pInterface;
                (*pEnv->acquireInterface)( pEnv, pInterface );
                guard.clear();
                (*pEnv->releaseInterface)( pEnv, *ppInterface );
                *ppInterface = pInterface;
            }
        }
        else
        {
            pOEntry->append( that, *ppInterface, pTypeDescr, 0 );
        }
    }
}

//------------------------------------------------------------------------------
static void SAL_CALL defenv_registerProxyInterface(
    uno_ExtEnvironment * pEnv, void ** ppInterface, uno_freeProxyFunc freeProxy,
    rtl_uString * pOId, typelib_InterfaceTypeDescription * pTypeDescr )
{
    OSL_ENSURE( pEnv && ppInterface && pOId && pTypeDescr && freeProxy,
                "### null ptr!" );
    OUString const & rOId = OUString::unacquired( &pOId );
    
    uno_DefaultEnvironment * that =
        static_cast< uno_DefaultEnvironment * >( pEnv );
    ::osl::ClearableMutexGuard guard( that->mutex );
    
    // try to insert dummy 0:
    std::pair<OId2ObjectMap::iterator, bool> const insertion(
        that->aOId2ObjectMap.insert( OId2ObjectMap::value_type( rOId, (ObjectEntry*)0 ) ) );
    if (insertion.second)
    {
        ObjectEntry * pOEntry = new ObjectEntry( rOId );
        insertion.first->second = pOEntry;
        ++pOEntry->nRef; // another register call on object
        pOEntry->append( that, *ppInterface, pTypeDescr, freeProxy );
    }
    else // object entry exists
    {
        ObjectEntry * pOEntry = insertion.first->second;
        
        // first registration was an original, then registerProxyInterface():
        pOEntry->mixedObject |=
            (!pOEntry->aInterfaces.empty() &&
             pOEntry->aInterfaces[ 0 ].fpFreeProxy == 0);
        
        ++pOEntry->nRef; // another register call on object
        InterfaceEntry * pIEntry = pOEntry->find( pTypeDescr );

        if (pIEntry) // type entry exists
        {
            if (pIEntry->pInterface == *ppInterface)
            {
                ++pIEntry->refCount;
            }
            else
            {
                void * pInterface = pIEntry->pInterface;
                (*pEnv->acquireInterface)( pEnv, pInterface );
                --pOEntry->nRef; // manual revoke of proxy to be freed
                guard.clear();
                (*freeProxy)( pEnv, *ppInterface );
                *ppInterface = pInterface;
            }
        }
        else
        {
            pOEntry->append( that, *ppInterface, pTypeDescr, freeProxy );
        }
    }
}

//------------------------------------------------------------------------------
static void SAL_CALL s_stub_defenv_revokeInterface(va_list * pParam)
{
    uno_ExtEnvironment * pEnv       = va_arg(*pParam, uno_ExtEnvironment *);
    void               * pInterface = va_arg(*pParam, void *);

    OSL_ENSURE( pEnv && pInterface, "### null ptr!" );
    uno_DefaultEnvironment * that =
        static_cast< uno_DefaultEnvironment * >( pEnv );
    ::osl::ClearableMutexGuard guard( that->mutex );

    Ptr2ObjectMap::const_iterator const iFind(
        that->aPtr2ObjectMap.find( pInterface ) );
    OSL_ASSERT( iFind != that->aPtr2ObjectMap.end() );
    ObjectEntry * pOEntry = iFind->second;
    if (! --pOEntry->nRef)
    {
        // cleanup maps
        that->aOId2ObjectMap.erase( pOEntry->oid );
        sal_Int32 nPos;
        for ( nPos = pOEntry->aInterfaces.size(); nPos--; )
        {
            that->aPtr2ObjectMap.erase( pOEntry->aInterfaces[nPos].pInterface );
        }

        // the last proxy interface of the environment might kill this
        // environment, because of releasing its language binding!!!
        guard.clear();

        // release interfaces
        for ( nPos = pOEntry->aInterfaces.size(); nPos--; )
        {
            InterfaceEntry const & rEntry = pOEntry->aInterfaces[nPos];
            typelib_typedescription_release(
                (typelib_TypeDescription *) rEntry.pTypeDescr );
            if (rEntry.fpFreeProxy) // is proxy or used interface?
            {
                (*rEntry.fpFreeProxy)( pEnv, rEntry.pInterface );
            }
            else
            {
                (*pEnv->releaseInterface)( pEnv, rEntry.pInterface );
            }
        }

        delete pOEntry;
    }
    else if (pOEntry->mixedObject)
    {
        OSL_ASSERT( !pOEntry->aInterfaces.empty() &&
                    pOEntry->aInterfaces[ 0 ].fpFreeProxy == 0 );

        sal_Int32 index = pOEntry->find( pInterface, 1 );
        OSL_ASSERT( index > 0 );
        if (index > 0)
        {
            InterfaceEntry & entry = pOEntry->aInterfaces[ index ];
            OSL_ASSERT( entry.pInterface == pInterface );
            if (entry.fpFreeProxy != 0)
            {
                --entry.refCount;
                if (entry.refCount == 0)
                {
                    uno_freeProxyFunc fpFreeProxy = entry.fpFreeProxy;
                    typelib_TypeDescription * pTypeDescr =
                        reinterpret_cast< typelib_TypeDescription * >(
                            entry.pTypeDescr );
                    
                    pOEntry->aInterfaces.erase(
                        pOEntry->aInterfaces.begin() + index );
                    if (pOEntry->find( pInterface, index ) < 0)
                    {
                        // proxy ptr not registered for another interface:
                        // remove from ptr map
#if OSL_DEBUG_LEVEL > 0
                        ::std::size_t erased =
#endif
                              that->aPtr2ObjectMap.erase( pInterface );
                        OSL_ASSERT( erased == 1 );
                    }
                    
                    guard.clear();
                    
                    typelib_typedescription_release( pTypeDescr );
                    (*fpFreeProxy)( pEnv, pInterface );
                }
            }
        }
    }
}

static void SAL_CALL defenv_revokeInterface(uno_ExtEnvironment * pEnv, void * pInterface)
{
    uno_Environment_invoke(&pEnv->aBase, s_stub_defenv_revokeInterface, pEnv, pInterface);
}

//------------------------------------------------------------------------------
static void SAL_CALL defenv_getObjectIdentifier(
    uno_ExtEnvironment * pEnv, rtl_uString ** ppOId, void * pInterface )
{
    OSL_ENSURE( pEnv && ppOId && pInterface, "### null ptr!" );
    if (*ppOId)
    {
        ::rtl_uString_release( *ppOId );
        *ppOId = 0;
    }

    uno_DefaultEnvironment * that =
        static_cast< uno_DefaultEnvironment * >( pEnv );
    ::osl::ClearableMutexGuard guard( that->mutex );

    Ptr2ObjectMap::const_iterator const iFind(
        that->aPtr2ObjectMap.find( pInterface ) );
    if (iFind == that->aPtr2ObjectMap.end())
    {
        guard.clear();
        (*pEnv->computeObjectIdentifier)( pEnv, ppOId, pInterface );
    }
    else
    {
        rtl_uString * hstr = iFind->second->oid.pData;
        rtl_uString_acquire( hstr );
        *ppOId = hstr;
    }
}

//------------------------------------------------------------------------------
static void SAL_CALL defenv_getRegisteredInterface(
    uno_ExtEnvironment * pEnv, void ** ppInterface,
    rtl_uString * pOId, typelib_InterfaceTypeDescription * pTypeDescr )
{
    OSL_ENSURE( pEnv && ppInterface && pOId && pTypeDescr, "### null ptr!" );
    if (*ppInterface)
    {
        (*pEnv->releaseInterface)( pEnv, *ppInterface );
        *ppInterface = 0;
    }

    OUString const & rOId = OUString::unacquired( &pOId );
    uno_DefaultEnvironment * that =
        static_cast< uno_DefaultEnvironment * >( pEnv );
    ::osl::MutexGuard guard( that->mutex );

    OId2ObjectMap::const_iterator const iFind
        ( that->aOId2ObjectMap.find( rOId ) );
    if (iFind != that->aOId2ObjectMap.end())
    {
        InterfaceEntry const * pIEntry = iFind->second->find( pTypeDescr );
        if (pIEntry)
        {
            (*pEnv->acquireInterface)( pEnv, pIEntry->pInterface );
            *ppInterface = pIEntry->pInterface;
        }
    }
}

//------------------------------------------------------------------------------
static void SAL_CALL defenv_getRegisteredInterfaces(
    uno_ExtEnvironment * pEnv, void *** pppInterfaces, sal_Int32 * pnLen,
    uno_memAlloc memAlloc )
{
    OSL_ENSURE( pEnv && pppInterfaces && pnLen && memAlloc, "### null ptr!" );
    uno_DefaultEnvironment * that =
        static_cast< uno_DefaultEnvironment * >( pEnv );
    ::osl::MutexGuard guard( that->mutex );

    sal_Int32 nLen = that->aPtr2ObjectMap.size();
    sal_Int32 nPos = 0;
    void ** ppInterfaces = (void **) (*memAlloc)( nLen * sizeof (void *) );

    Ptr2ObjectMap::const_iterator iPos( that->aPtr2ObjectMap.begin() );
    Ptr2ObjectMap::const_iterator const iEnd( that->aPtr2ObjectMap.end() );
    while (iPos != iEnd)
    {
        (*pEnv->acquireInterface)( pEnv, ppInterfaces[nPos++] = (*iPos).first );
        ++iPos;
    }

    *pppInterfaces = ppInterfaces;
    *pnLen = nLen;
}

//------------------------------------------------------------------------------
static void SAL_CALL defenv_acquire( uno_Environment * pEnv )
{
    uno_DefaultEnvironment * that = (uno_DefaultEnvironment *)pEnv;
    ::osl_incrementInterlockedCount( &that->nWeakRef );
    ::osl_incrementInterlockedCount( &that->nRef );
}

//------------------------------------------------------------------------------
static void SAL_CALL defenv_release( uno_Environment * pEnv )
{
    uno_DefaultEnvironment * that = (uno_DefaultEnvironment *)pEnv;
    if (! ::osl_decrementInterlockedCount( &that->nRef ))
    {
        // invoke dispose callback
        if (pEnv->environmentDisposing)
        {
            (*pEnv->environmentDisposing)( pEnv );
        }

        OSL_ENSURE( that->aOId2ObjectMap.empty(), "### object entries left!" );
    }
    // free memory if no weak refs left
    if (! ::osl_decrementInterlockedCount( &that->nWeakRef ))
    {
        delete that;
    }
}

//------------------------------------------------------------------------------
static void SAL_CALL defenv_acquireWeak( uno_Environment * pEnv )
{
    uno_DefaultEnvironment * that = (uno_DefaultEnvironment *)pEnv;
    ::osl_incrementInterlockedCount( &that->nWeakRef );
}

//------------------------------------------------------------------------------
static void SAL_CALL defenv_releaseWeak( uno_Environment * pEnv )
{
    uno_DefaultEnvironment * that = (uno_DefaultEnvironment *)pEnv;
    if (! ::osl_decrementInterlockedCount( &that->nWeakRef ))
    {
        delete that;
    }
}

//------------------------------------------------------------------------------
static void SAL_CALL defenv_harden(
    uno_Environment ** ppHardEnv, uno_Environment * pEnv )
{
    if (*ppHardEnv)
    {
        (*(*ppHardEnv)->release)( *ppHardEnv );
        *ppHardEnv = 0;
    }

    EnvironmentsData & rData = theEnvironmentsData::get();

    if (rData.isDisposing)
        return;

    uno_DefaultEnvironment * that = (uno_DefaultEnvironment *)pEnv;
    {
    ::osl::MutexGuard guard( rData.mutex );
    if (1 == ::osl_incrementInterlockedCount( &that->nRef )) // is dead
    {
        that->nRef = 0;
        return;
    }
    }
    ::osl_incrementInterlockedCount( &that->nWeakRef );
    *ppHardEnv = pEnv;
}

//------------------------------------------------------------------------------
static void SAL_CALL defenv_dispose( uno_Environment * )
{
}
}

//______________________________________________________________________________
uno_DefaultEnvironment::uno_DefaultEnvironment(
    const OUString & rEnvDcp_, void * pContext_ )
    : nRef( 0 ),
      nWeakRef( 0 )
{
    uno_Environment * that = reinterpret_cast< uno_Environment * >(this);
    that->pReserved = 0;
    // functions
    that->acquire = defenv_acquire;
    that->release = defenv_release;
    that->acquireWeak = defenv_acquireWeak;
    that->releaseWeak = defenv_releaseWeak;
    that->harden = defenv_harden;
    that->dispose = defenv_dispose;
    that->pExtEnv = this;
    // identifier
    ::rtl_uString_acquire( rEnvDcp_.pData );
    that->pTypeName = rEnvDcp_.pData;
    that->pContext = pContext_;

    // will be late initialized
    that->environmentDisposing = 0;

    uno_ExtEnvironment::registerInterface = defenv_registerInterface;
    uno_ExtEnvironment::registerProxyInterface = defenv_registerProxyInterface;
    uno_ExtEnvironment::revokeInterface = defenv_revokeInterface;
    uno_ExtEnvironment::getObjectIdentifier = defenv_getObjectIdentifier;
    uno_ExtEnvironment::getRegisteredInterface = defenv_getRegisteredInterface;
    uno_ExtEnvironment::getRegisteredInterfaces =
        defenv_getRegisteredInterfaces;

}

//______________________________________________________________________________
uno_DefaultEnvironment::~uno_DefaultEnvironment()
{
    ::rtl_uString_release( ((uno_Environment *) this)->pTypeName );
}

//==============================================================================
static void writeLine(
    void * stream, const sal_Char * pLine, const sal_Char * pFilter )
{
    if (pFilter && *pFilter)
    {
        // lookup pFilter in pLine
        while (*pLine)
        {
            if (*pLine == *pFilter)
            {
                sal_Int32 nPos = 1;
                while (pLine[nPos] && pFilter[nPos] == pLine[nPos])
                {
                    ++nPos;
                }
                if (! pFilter[nPos])
                {
                    if (stream)
                    {
                        fprintf( (FILE *) stream, "%s\n", pLine );
                    }
                    else
                    {
                        OSL_TRACE( "%s\n", pLine );
                    }
                }
            }
            ++pLine;
        }
    }
    else
    {
        if (stream)
        {
            fprintf( (FILE *) stream, "%s\n", pLine );
        }
        else
        {
            fprintf( stderr, "%s\n", pLine );
        }
    }
}

//==============================================================================
static void writeLine(
    void * stream, const OUString & rLine, const sal_Char * pFilter )
{
    ::rtl::OString aLine( ::rtl::OUStringToOString(
                              rLine, RTL_TEXTENCODING_ASCII_US ) );
    writeLine( stream, aLine.getStr(), pFilter );
}

//##############################################################################
extern "C" void SAL_CALL uno_dumpEnvironment(
    void * stream, uno_Environment * pEnv, const sal_Char * pFilter )
    SAL_THROW_EXTERN_C()
{
    OSL_ENSURE( pEnv, "### null ptr!" );
    ::rtl::OUStringBuffer buf;

    if (! pEnv->pExtEnv)
    {
        writeLine( stream, "###################################"
                   "###########################################", pFilter );
        buf.appendAscii( RTL_CONSTASCII_STRINGPARAM("environment: ") );
        buf.append( pEnv->pTypeName );
        writeLine( stream, buf.makeStringAndClear(), pFilter );
        writeLine( stream, "NO INTERFACE INFORMATION AVAILABLE!", pFilter );
        return;
    }

    writeLine( stream, "########################################"
               "######################################", pFilter );
    buf.appendAscii( RTL_CONSTASCII_STRINGPARAM("environment dump: ") );
    buf.append( pEnv->pTypeName );
    writeLine( stream, buf.makeStringAndClear(), pFilter );

    uno_DefaultEnvironment * that =
        reinterpret_cast< uno_DefaultEnvironment * >(pEnv);
    ::osl::MutexGuard guard( that->mutex );

    Ptr2ObjectMap ptr2obj( that->aPtr2ObjectMap );
    OId2ObjectMap::const_iterator iPos( that->aOId2ObjectMap.begin() );
    while (iPos != that->aOId2ObjectMap.end())
    {
        ObjectEntry * pOEntry = iPos->second;
        
        buf.appendAscii( RTL_CONSTASCII_STRINGPARAM("+ ") );
        if (pOEntry->mixedObject)
            buf.appendAscii( RTL_CONSTASCII_STRINGPARAM("mixed ") );
        buf.appendAscii( RTL_CONSTASCII_STRINGPARAM("object entry: nRef=") );
        buf.append( pOEntry->nRef, 10 );
        buf.appendAscii( RTL_CONSTASCII_STRINGPARAM("; oid=\"") );
        buf.append( pOEntry->oid );
        buf.append( (sal_Unicode) '\"' );
        writeLine( stream, buf.makeStringAndClear(), pFilter );

        for ( ::std::size_t nPos = 0;
              nPos < pOEntry->aInterfaces.size(); ++nPos )
        {
            const InterfaceEntry & rIEntry = pOEntry->aInterfaces[nPos];
            
            buf.appendAscii( RTL_CONSTASCII_STRINGPARAM("  - ") );
            buf.append(
                ((typelib_TypeDescription *) rIEntry.pTypeDescr)->pTypeName );
            if (rIEntry.fpFreeProxy)
            {
                buf.appendAscii(
                    RTL_CONSTASCII_STRINGPARAM("; proxy free=0x") );
                buf.append(
                    reinterpret_cast< sal_IntPtr >(rIEntry.fpFreeProxy), 16 );
            }
            else
            {
                buf.appendAscii( RTL_CONSTASCII_STRINGPARAM("; original") );
            }
            buf.appendAscii( RTL_CONSTASCII_STRINGPARAM("; ptr=0x") );
            buf.append(
                reinterpret_cast< sal_IntPtr >(rIEntry.pInterface), 16 );
            
            if (pOEntry->find( rIEntry.pInterface, nPos + 1 ) < 0)
            {
                ::std::size_t erased = ptr2obj.erase( rIEntry.pInterface );
                if (erased != 1)
                {
                    buf.appendAscii( RTL_CONSTASCII_STRINGPARAM(
                                         " (ptr not found in map!)") );
                }
            }
            writeLine( stream, buf.makeStringAndClear(), pFilter );
        }
        ++iPos;
    }
    if (! ptr2obj.empty())
        writeLine( stream, "ptr map inconsistency!!!", pFilter );
    writeLine( stream, "#####################################"
               "#########################################", pFilter );
}

//##############################################################################
extern "C" void SAL_CALL uno_dumpEnvironmentByName(
    void * stream, rtl_uString * pEnvDcp, const sal_Char * pFilter )
    SAL_THROW_EXTERN_C()
{
    uno_Environment * pEnv = 0;
    uno_getEnvironment( &pEnv, pEnvDcp, 0 );
    if (pEnv)
    {
        ::uno_dumpEnvironment( stream, pEnv, pFilter );
        (*pEnv->release)( pEnv );
    }
    else
    {
        ::rtl::OUStringBuffer buf( 32 );
        buf.appendAscii( RTL_CONSTASCII_STRINGPARAM("environment \"") );
        buf.append( pEnvDcp );
        buf.appendAscii( RTL_CONSTASCII_STRINGPARAM("\" does not exist!") );
        writeLine( stream, buf.makeStringAndClear(), pFilter );
    }
}

//------------------------------------------------------------------------------
inline static const OUString & unoenv_getStaticOIdPart()
{
    static OUString * s_pStaticOidPart = 0;
    if (! s_pStaticOidPart)
    {
        ::osl::MutexGuard guard( ::osl::Mutex::getGlobalMutex() );
        if (! s_pStaticOidPart)
        {
            ::rtl::OUStringBuffer aRet( 64 );
            aRet.appendAscii( RTL_CONSTASCII_STRINGPARAM("];") );
            // pid
            oslProcessInfo info;
            info.Size = sizeof(oslProcessInfo);
            if (::osl_getProcessInfo( 0, osl_Process_IDENTIFIER, &info ) ==
                osl_Process_E_None)
            {
                aRet.append( (sal_Int64)info.Ident, 16 );
            }
            else
            {
                aRet.appendAscii(
                    RTL_CONSTASCII_STRINGPARAM("unknown process id") );
            }
            // good guid
            sal_uInt8 ar[16];
            ::rtl_getGlobalProcessId( ar );
            aRet.append( (sal_Unicode)';' );
            for ( sal_Int32 i = 0; i < 16; ++i )
                aRet.append( (sal_Int32)ar[i], 16 );

            static OUString s_aStaticOidPart( aRet.makeStringAndClear() );
            s_pStaticOidPart = &s_aStaticOidPart;
        }
    }
    return *s_pStaticOidPart;
}

extern "C"
{

//------------------------------------------------------------------------------
static void SAL_CALL unoenv_computeObjectIdentifier(
    uno_ExtEnvironment * pEnv, rtl_uString ** ppOId, void * pInterface )
{
    OSL_ENSURE( pEnv && ppOId && pInterface, "### null ptr!" );
    if (*ppOId)
    {
        ::rtl_uString_release( *ppOId );
        *ppOId = 0;
    }

    uno_Interface * pUnoI = (uno_Interface *)
        ::cppu::binuno_queryInterface(
            pInterface, *typelib_static_type_getByTypeClass(
                typelib_TypeClass_INTERFACE ) );
    if (0 != pUnoI)
    {
        (*pUnoI->release)( pUnoI );
        // interface
        ::rtl::OUStringBuffer oid( 64 );
        oid.append( reinterpret_cast< sal_Int64 >(pUnoI), 16 );
        oid.append( static_cast< sal_Unicode >(';') );
        // environment[context]
        oid.append( ((uno_Environment *) pEnv)->pTypeName );
        oid.append( static_cast< sal_Unicode >('[') );
        oid.append( reinterpret_cast< sal_Int64 >(
                        reinterpret_cast<
                        uno_Environment * >(pEnv)->pContext ), 16 );
        // process;good guid
        oid.append( unoenv_getStaticOIdPart() );
        OUString aStr( oid.makeStringAndClear() );
        ::rtl_uString_acquire( *ppOId = aStr.pData );
    }
}

//==============================================================================
static void SAL_CALL unoenv_acquireInterface(
    uno_ExtEnvironment *, void * pUnoI_ )
{
    uno_Interface * pUnoI = reinterpret_cast< uno_Interface * >(pUnoI_);
    (*pUnoI->acquire)( pUnoI );
}

//==============================================================================
static void SAL_CALL unoenv_releaseInterface(
    uno_ExtEnvironment *, void * pUnoI_ )
{
    uno_Interface * pUnoI = reinterpret_cast< uno_Interface * >(pUnoI_);
    (*pUnoI->release)( pUnoI );
}
}

//______________________________________________________________________________
EnvironmentsData::~EnvironmentsData()
{
    ::osl::MutexGuard guard( mutex );
    isDisposing = true;

    for ( OUString2EnvironmentMap::const_iterator iPos( aName2EnvMap.begin() );
          iPos != aName2EnvMap.end(); ++iPos )
    {
        uno_Environment * pWeak = iPos->second;
        uno_Environment * pHard = 0;
        (*pWeak->harden)( &pHard, pWeak );
        (*pWeak->releaseWeak)( pWeak );

        if (pHard)
        {
#if OSL_DEBUG_LEVEL > 1
            ::uno_dumpEnvironment( 0, pHard, 0 );
#endif
            (*pHard->dispose)( pHard ); // send explicit dispose
            (*pHard->release)( pHard );
        }
    }
}

//______________________________________________________________________________
inline void EnvironmentsData::getEnvironment(
    uno_Environment ** ppEnv, const OUString & rEnvDcp, void * pContext )
{
    if (*ppEnv)
    {
        (*(*ppEnv)->release)( *ppEnv );
        *ppEnv = 0;
    }

    OUString aKey(
        OUString::valueOf( reinterpret_cast< sal_IntPtr >(pContext) ) );
    aKey += rEnvDcp;

    // try to find registered mapping
    OUString2EnvironmentMap::const_iterator const iFind(
        aName2EnvMap.find( aKey ) );
    if (iFind != aName2EnvMap.end())
    {
        uno_Environment * pWeak = iFind->second;
        (*pWeak->harden)( ppEnv, pWeak );
    }
}

//______________________________________________________________________________
inline void EnvironmentsData::registerEnvironment( uno_Environment ** ppEnv )
{
    OSL_ENSURE( ppEnv, "### null ptr!" );
    uno_Environment * pEnv =  *ppEnv;

    OUString aKey(
        OUString::valueOf( reinterpret_cast< sal_IntPtr >(pEnv->pContext) ) );
    aKey += pEnv->pTypeName;

    // try to find registered environment
    OUString2EnvironmentMap::const_iterator const iFind(
        aName2EnvMap.find( aKey ) );
    if (iFind == aName2EnvMap.end())
    {
        (*pEnv->acquireWeak)( pEnv );
        ::std::pair< OUString2EnvironmentMap::iterator, bool > insertion(
            aName2EnvMap.insert(
                OUString2EnvironmentMap::value_type( aKey, pEnv ) ) );
        OSL_ENSURE(
            insertion.second, "### insertion of env into map failed?!" );
    }
    else
    {
        uno_Environment * pHard = 0;
        uno_Environment * pWeak = iFind->second;
        (*pWeak->harden)( &pHard, pWeak );
        if (pHard)
        {
            if (pEnv)
                (*pEnv->release)( pEnv );
            *ppEnv = pHard;
        }
        else // registered one is dead
        {
            (*pWeak->releaseWeak)( pWeak );
            (*pEnv->acquireWeak)( pEnv );
            aName2EnvMap[ aKey ] = pEnv;
        }
    }
}

//______________________________________________________________________________
inline void EnvironmentsData::getRegisteredEnvironments(
    uno_Environment *** pppEnvs, sal_Int32 * pnLen, uno_memAlloc memAlloc,
    const OUString & rEnvDcp )
{
    OSL_ENSURE( pppEnvs && pnLen && memAlloc, "### null ptr!" );

    // max size
    uno_Environment ** ppFound = (uno_Environment **)alloca(
        sizeof(uno_Environment *) * aName2EnvMap.size() );
    sal_Int32 nSize = 0;

    // find matching environment
    for ( OUString2EnvironmentMap::const_iterator iPos( aName2EnvMap.begin() );
          iPos != aName2EnvMap.end(); ++iPos )
    {
        uno_Environment * pWeak = iPos->second;
        if (!rEnvDcp.getLength() ||
            rEnvDcp.equals( pWeak->pTypeName ))
        {
            ppFound[nSize] = 0;
            (*pWeak->harden)( &ppFound[nSize], pWeak );
            if (ppFound[nSize])
                ++nSize;
        }
    }

    *pnLen = nSize;
    if (nSize)
    {
        *pppEnvs = (uno_Environment **) (*memAlloc)(
            sizeof (uno_Environment *) * nSize );
        OSL_ASSERT( *pppEnvs );
        while (nSize--)
        {
            (*pppEnvs)[nSize] = ppFound[nSize];
        }
    }
    else
    {
        *pppEnvs = 0;
    }
}

static bool loadEnv(OUString const  & cLibStem, 
                    uno_Environment * pEnv,
                    void            * /*pContext*/)
{
    // late init with some code from matching uno language binding
    // will be unloaded by environment
    oslModule hMod = cppu::detail::loadModule( cLibStem );
    
    if (!hMod)
        return false;

    OUString aSymbolName(RTL_CONSTASCII_USTRINGPARAM(UNO_INIT_ENVIRONMENT));
    uno_initEnvironmentFunc fpInit = (uno_initEnvironmentFunc)
        ::osl_getFunctionSymbol( hMod, aSymbolName.pData );
    if (!fpInit)
    {
        ::osl_unloadModule( hMod );
        return false;
    }

    (*fpInit)( pEnv ); // init of environment
    ::rtl_registerModuleForUnloading( hMod );

    return true;
}


extern "C"
{

//------------------------------------------------------------------------------
static uno_Environment * initDefaultEnvironment(
    const OUString & rEnvDcp, void * pContext )
{
    uno_Environment * pEnv = &(new uno_DefaultEnvironment( rEnvDcp, pContext ))->aBase;
    (*pEnv->acquire)( pEnv );

    OUString envTypeName = cppu::EnvDcp::getTypeName(rEnvDcp);

    // create default environment
    if (envTypeName.equalsAsciiL( RTL_CONSTASCII_STRINGPARAM(UNO_LB_UNO) ))
    {
        uno_DefaultEnvironment * that = (uno_DefaultEnvironment *)pEnv;
        that->computeObjectIdentifier = unoenv_computeObjectIdentifier;
        that->acquireInterface = unoenv_acquireInterface;
        that->releaseInterface = unoenv_releaseInterface;

        OUString envPurpose = cppu::EnvDcp::getPurpose(rEnvDcp);
        if (envPurpose.getLength()) 
        {
            rtl::OUString libStem = envPurpose.copy(envPurpose.lastIndexOf(':') + 1);
            libStem += rtl::OUString( RTL_CONSTASCII_USTRINGPARAM("_uno_uno") );
            
            if(!loadEnv(libStem, pEnv, pContext))
            {
                pEnv->release(pEnv);
                return NULL;
            }
        }
    }
    else
    {
        // late init with some code from matching uno language binding
        ::rtl::OUStringBuffer aLibName( 16 );
        aLibName.append( envTypeName );
        aLibName.appendAscii( RTL_CONSTASCII_STRINGPARAM("_uno" ) );
        OUString aStr( aLibName.makeStringAndClear() );

        if (!loadEnv(aStr, pEnv, pContext))
        {
            pEnv->release(pEnv);
            return NULL;
        }
    }

    return pEnv;
}

//##############################################################################
void SAL_CALL uno_createEnvironment(
    uno_Environment ** ppEnv, rtl_uString * pEnvDcp, void * pContext )
    SAL_THROW_EXTERN_C()
{
    OSL_ENSURE( ppEnv, "### null ptr!" );
    if (*ppEnv)
        (*(*ppEnv)->release)( *ppEnv );
    
    OUString const & rEnvDcp = OUString::unacquired( &pEnvDcp );
    *ppEnv = initDefaultEnvironment( rEnvDcp, pContext );
}

//##############################################################################
void SAL_CALL uno_direct_getEnvironment(
    uno_Environment ** ppEnv, rtl_uString * pEnvDcp, void * pContext )
    SAL_THROW_EXTERN_C()
{
    OSL_ENSURE( ppEnv, "### null ptr!" );
    OUString const & rEnvDcp = OUString::unacquired( &pEnvDcp );

    EnvironmentsData & rData = theEnvironmentsData::get();

    ::osl::MutexGuard guard( rData.mutex );
    rData.getEnvironment( ppEnv, rEnvDcp, pContext );
    if (! *ppEnv)
    {
        *ppEnv = initDefaultEnvironment( rEnvDcp, pContext );
        if (*ppEnv)
        {
            // register new environment:
            rData.registerEnvironment( ppEnv );
        }
    }
}

//##############################################################################
void SAL_CALL uno_getRegisteredEnvironments(
    uno_Environment *** pppEnvs, sal_Int32 * pnLen, uno_memAlloc memAlloc,
    rtl_uString * pEnvDcp )
    SAL_THROW_EXTERN_C()
{
    EnvironmentsData & rData = theEnvironmentsData::get();

    ::osl::MutexGuard guard( rData.mutex );
    rData.getRegisteredEnvironments(
        pppEnvs, pnLen, memAlloc,
        (pEnvDcp ? OUString(pEnvDcp) : OUString()) );
}

} // extern "C"

}

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
