/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of the LibreOffice project.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * This file incorporates work covered by the following license notice:
 *
 *   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 .
 */

#ifdef DISABLE_DYNLOADING
#include <config_java.h>
#endif

#include <cppu/EnvDcp.hxx>

#include <sal/log.hxx>
#include <osl/diagnose.h>
#include <osl/interlck.h>
#include <osl/mutex.hxx>
#include <osl/module.hxx>
#include <osl/process.h>
#include <rtl/process.h>
#include <rtl/string.hxx>
#include <rtl/ustring.hxx>
#include <rtl/ustrbuf.hxx>
#include <typelib/typedescription.h>
#include <uno/dispatcher.h>
#include <uno/environment.h>
#include <uno/lbnames.h>
#include "prim.hxx"
#include "loadmodule.hxx"

#include <string_view>
#include <unordered_map>
#include <utility>
#include <vector>
#include <stdio.h>


namespace
{


bool td_equals( typelib_InterfaceTypeDescription const * pTD1,
                       typelib_InterfaceTypeDescription const * pTD2 )
{
    return (pTD1 == pTD2 ||
            (pTD1->aBase.pTypeName->length == pTD2->aBase.pTypeName->length &&
             ::rtl_ustr_compare(
                 pTD1->aBase.pTypeName->buffer,
                 pTD2->aBase.pTypeName->buffer ) == 0));
}

struct uno_DefaultEnvironment;


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

struct ObjectEntry
{
    OUString oid;
    std::vector< InterfaceEntry > aInterfaces;
    sal_Int32 nRef;
    bool mixedObject;

    explicit ObjectEntry( OUString aOId_ );

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


struct FctPtrHash
{
    std::size_t operator () ( const void * pKey ) const
        { return reinterpret_cast< std::size_t>( pKey ); }
};


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

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

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

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

    void getEnvironment(
        uno_Environment ** ppEnv, std::u16string_view rEnvDcp, void * pContext );
    void registerEnvironment( uno_Environment ** ppEnv );
    void getRegisteredEnvironments(
        uno_Environment *** pppEnvs, sal_Int32 * pnLen,
        uno_memAlloc memAlloc, std::u16string_view rEnvDcp );

    bool isDisposing;
};

EnvironmentsData& theEnvironmentsData()
{
    static EnvironmentsData SINGLETON;
    return SINGLETON;
}

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();
};


ObjectEntry::ObjectEntry( OUString aOId_ )
    : oid(std::move( aOId_ )),
      nRef( 0 ),
      mixedObject( false )
{
    aInterfaces.reserve( 2 );
}


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( &pTypeDescr->aBase );
    aNewEntry.pTypeDescr = pTypeDescr;

    std::pair< Ptr2ObjectMap::iterator, bool > i(
        pEnv->aPtr2ObjectMap.emplace( pInterface, this ) );
    SAL_WARN_IF(
        !i.second && (find(pInterface, 0) == -1 || i.first->second != this),
        "cppu",
        "map already contains " << i.first->second << " != " << this << " for "
            << pInterface);
    aInterfaces.push_back( aNewEntry );
}


InterfaceEntry * ObjectEntry::find(
    typelib_InterfaceTypeDescription * pTypeDescr_ )
{
    OSL_ASSERT( ! aInterfaces.empty() );
    if (aInterfaces.empty())
        return nullptr;

    // shortcut common case:
    OUString const & type_name =
        OUString::unacquired( &pTypeDescr_->aBase.pTypeName );
    if ( type_name == "com.sun.star.uno.XInterface" )
    {
        return aInterfaces.data();
    }

    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 nullptr;
}


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

extern "C"
{


static void defenv_registerInterface(
    uno_ExtEnvironment * pEnv, void ** ppInterface,
    rtl_uString * pOId, typelib_InterfaceTypeDescription * pTypeDescr )
{
    assert(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.emplace(  rOId, nullptr ) );
    if (insertion.second)
    {
        ObjectEntry * pOEntry = new ObjectEntry( rOId );
        insertion.first->second = pOEntry;
        ++pOEntry->nRef; // another register call on object
        pOEntry->append( that, *ppInterface, pTypeDescr, nullptr );
    }
    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, nullptr );
        }
    }
}

static void defenv_registerProxyInterface(
    uno_ExtEnvironment * pEnv, void ** ppInterface, uno_freeProxyFunc freeProxy,
    rtl_uString * pOId, typelib_InterfaceTypeDescription * pTypeDescr )
{
    assert(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.emplace(  rOId, nullptr ) );
    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 == nullptr);

        ++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 s_stub_defenv_revokeInterface(va_list * pParam)
{
    uno_ExtEnvironment * pEnv       = va_arg(*pParam, uno_ExtEnvironment *);
    void               * pInterface = va_arg(*pParam, void *);

    assert(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 ) );
    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( &rEntry.pTypeDescr->aBase );
            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 == nullptr );

        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 != nullptr)
            {
                --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
                        std::size_t erased =
                              that->aPtr2ObjectMap.erase( pInterface );
                        OSL_ASSERT( erased == 1 );
                    }

                    guard.clear();

                    typelib_typedescription_release( pTypeDescr );
                    (*fpFreeProxy)( pEnv, pInterface );
                }
            }
        }
    }
}

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


static void defenv_getObjectIdentifier(
    uno_ExtEnvironment * pEnv, rtl_uString ** ppOId, void * pInterface )
{
    assert(pEnv && ppOId && pInterface && "### null ptr!");
    if (*ppOId)
    {
        ::rtl_uString_release( *ppOId );
        *ppOId = nullptr;
    }

    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 defenv_getRegisteredInterface(
    uno_ExtEnvironment * pEnv, void ** ppInterface,
    rtl_uString * pOId, typelib_InterfaceTypeDescription * pTypeDescr )
{
    assert(pEnv && ppInterface && pOId && pTypeDescr && "### null ptr!");
    if (*ppInterface)
    {
        (*pEnv->releaseInterface)( pEnv, *ppInterface );
        *ppInterface = nullptr;
    }

    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 defenv_getRegisteredInterfaces(
    uno_ExtEnvironment * pEnv, void *** pppInterfaces, sal_Int32 * pnLen,
    uno_memAlloc memAlloc )
{
    assert(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 = static_cast<void **>((*memAlloc)( nLen * sizeof (void *) ));

    for (const auto& rEntry : that->aPtr2ObjectMap)
    {
        ppInterfaces[nPos] = rEntry.first;
        (*pEnv->acquireInterface)( pEnv, ppInterfaces[nPos] );
        nPos++;
    }

    *pppInterfaces = ppInterfaces;
    *pnLen = nLen;
}


static void defenv_acquire( uno_Environment * pEnv )
{
    uno_DefaultEnvironment * that = reinterpret_cast<uno_DefaultEnvironment *>(pEnv);
    osl_atomic_increment( &that->nWeakRef );
    osl_atomic_increment( &that->nRef );
}


static void defenv_release( uno_Environment * pEnv )
{
    uno_DefaultEnvironment * that = reinterpret_cast<uno_DefaultEnvironment *>(pEnv);
    if (! osl_atomic_decrement( &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_atomic_decrement( &that->nWeakRef ))
    {
        delete that;
    }
}


static void defenv_acquireWeak( uno_Environment * pEnv )
{
    uno_DefaultEnvironment * that = reinterpret_cast<uno_DefaultEnvironment *>(pEnv);
    osl_atomic_increment( &that->nWeakRef );
}


static void defenv_releaseWeak( uno_Environment * pEnv )
{
    uno_DefaultEnvironment * that = reinterpret_cast<uno_DefaultEnvironment *>(pEnv);
    if (! osl_atomic_decrement( &that->nWeakRef ))
    {
        delete that;
    }
}


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

    EnvironmentsData & rData = theEnvironmentsData();

    if (rData.isDisposing)
        return;

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


static void defenv_dispose( SAL_UNUSED_PARAMETER 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 = nullptr;
    // 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 = nullptr;

    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( aBase.pTypeName );
}


void writeLine(
    void * stream, const char * pLine, const 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( static_cast<FILE *>(stream), "%s\n", pLine );
                    }
                    else
                    {
                        SAL_WARN("cppu", pLine );
                    }
                }
            }
            ++pLine;
        }
    }
    else
    {
        if (stream)
        {
            fprintf( static_cast<FILE *>(stream), "%s\n", pLine );
        }
        else
        {
            fprintf( stderr, "%s\n", pLine );
        }
    }
}


void writeLine(
    void * stream, std::u16string_view rLine, const char * pFilter )
{
    OString aLine( OUStringToOString(
                              rLine, RTL_TEXTENCODING_ASCII_US ) );
    writeLine( stream, aLine.getStr(), pFilter );
}

}

extern "C" void SAL_CALL uno_dumpEnvironment(
    void * stream, uno_Environment * pEnv, const char * pFilter ) noexcept
{
    assert(pEnv && "### null ptr!");
    OUStringBuffer buf;

    if (! pEnv->pExtEnv)
    {
        writeLine( stream, "###################################"
                   "###########################################", pFilter );
        buf.append( OUString::Concat("environment: ") + OUString::unacquired(&pEnv->pTypeName) );
        writeLine( stream, buf, pFilter );
        buf.setLength(0);
        writeLine( stream, "NO INTERFACE INFORMATION AVAILABLE!", pFilter );
        return;
    }

    writeLine( stream, "########################################"
               "######################################", pFilter );
    buf.append( OUString::Concat("environment dump: ") + OUString::unacquired(&pEnv->pTypeName) );
    writeLine( stream, buf, pFilter );
    buf.setLength(0);

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

    Ptr2ObjectMap ptr2obj( that->aPtr2ObjectMap );
    for (const auto& rEntry : that->aOId2ObjectMap)
    {
        ObjectEntry * pOEntry = rEntry.second;

        buf.append( "+ " );
        if (pOEntry->mixedObject)
            buf.append( "mixed " );
        buf.append( "object entry: nRef="
                + OUString::number(pOEntry->nRef)
                + "; oid=\""
                + pOEntry->oid
                + "\"" );
        writeLine( stream, buf, pFilter );
        buf.setLength(0);

        for ( std::size_t nPos = 0;
              nPos < pOEntry->aInterfaces.size(); ++nPos )
        {
            const InterfaceEntry & rIEntry = pOEntry->aInterfaces[nPos];

            buf.append( OUString::Concat("  - ")
                + OUString::unacquired(&rIEntry.pTypeDescr->aBase.pTypeName) );
            if (rIEntry.fpFreeProxy)
            {
                buf.append( "; proxy free=0x"
                    + OUString::number( reinterpret_cast< sal_IntPtr >(rIEntry.fpFreeProxy), 16 ) );
            }
            else
            {
                buf.append( "; original" );
            }
            buf.append( "; ptr=0x"
                + OUString::number(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.append( " (ptr not found in map!)" );
                }
            }
            writeLine( stream, buf, pFilter );
            buf.setLength(0);
        }
    }
    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 char * pFilter ) noexcept
{
    uno_Environment * pEnv = nullptr;
    uno_getEnvironment( &pEnv, pEnvDcp, nullptr );
    if (pEnv)
    {
        ::uno_dumpEnvironment( stream, pEnv, pFilter );
        (*pEnv->release)( pEnv );
    }
    else
    {
        writeLine(
            stream,
            Concat2View("environment \"" + OUString::unacquired(&pEnvDcp) + "\" does not exist!"),
            pFilter );
    }
}

namespace
{

const OUString & unoenv_getStaticOIdPart()
{
    static auto const theStaticOIdPart = [] {
            OUStringBuffer aRet( 64 );
            aRet.append( "];" );
            // pid
            oslProcessInfo info;
            info.Size = sizeof(oslProcessInfo);
            if (::osl_getProcessInfo( nullptr, osl_Process_IDENTIFIER, &info ) ==
                osl_Process_E_None)
            {
                aRet.append( static_cast<sal_Int64>(info.Ident), 16 );
            }
            else
            {
                aRet.append( "unknown process id" );
            }
            // good guid
            sal_uInt8 ar[16];
            ::rtl_getGlobalProcessId( ar );
            aRet.append( ';' );
            for (unsigned char i : ar)
                aRet.append( static_cast<sal_Int32>(i), 16 );

            return aRet.makeStringAndClear();
        }();
    return theStaticOIdPart;
}

}

extern "C"
{


static void unoenv_computeObjectIdentifier(
    uno_ExtEnvironment * pEnv, rtl_uString ** ppOId, void * pInterface )
{
    assert(pEnv && ppOId && pInterface && "### null ptr!");
    if (*ppOId)
    {
        ::rtl_uString_release( *ppOId );
        *ppOId = nullptr;
    }

    uno_Interface * pUnoI = static_cast<uno_Interface *>(
        ::cppu::binuno_queryInterface(
            pInterface, *typelib_static_type_getByTypeClass(
                typelib_TypeClass_INTERFACE ) ));
    if (nullptr == pUnoI)
        return;

    (*pUnoI->release)( pUnoI );
    OUString aStr(
        // interface
        OUString::number( reinterpret_cast< sal_IntPtr >(pUnoI), 16 ) + ";"
        // environment[context]
        + OUString::unacquired(&pEnv->aBase.pTypeName) + "["
        + OUString::number( reinterpret_cast< sal_IntPtr >(
                    reinterpret_cast<
                    uno_Environment * >(pEnv)->pContext ), 16 )
        // process;good guid
        + unoenv_getStaticOIdPart() );
    *ppOId = aStr.pData;
    ::rtl_uString_acquire( *ppOId );
}


static void unoenv_acquireInterface(
    SAL_UNUSED_PARAMETER uno_ExtEnvironment *, void * pUnoI_ )
{
    uno_Interface * pUnoI = static_cast< uno_Interface * >(pUnoI_);
    (*pUnoI->acquire)( pUnoI );
}


static void unoenv_releaseInterface(
    SAL_UNUSED_PARAMETER uno_ExtEnvironment *, void * pUnoI_ )
{
    uno_Interface * pUnoI = static_cast< uno_Interface * >(pUnoI_);
    (*pUnoI->release)( pUnoI );
}
}

namespace {

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

    for ( const auto& rEntry : aName2EnvMap )
    {
        uno_Environment * pWeak = rEntry.second;
        uno_Environment * pHard = nullptr;
        (*pWeak->harden)( &pHard, pWeak );
        (*pWeak->releaseWeak)( pWeak );

        if (pHard)
        {
            (*pHard->dispose)( pHard ); // send explicit dispose
            (*pHard->release)( pHard );
        }
    }
}


void EnvironmentsData::getEnvironment(
    uno_Environment ** ppEnv, std::u16string_view rEnvDcp, void * pContext )
{
    if (*ppEnv)
    {
        (*(*ppEnv)->release)( *ppEnv );
        *ppEnv = nullptr;
    }

    OUString aKey = OUString::number( reinterpret_cast< sal_IntPtr >(pContext) ) + 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 );
    }
}

void EnvironmentsData::registerEnvironment( uno_Environment ** ppEnv )
{
    assert(ppEnv && "### null ptr!");
    uno_Environment * pEnv =  *ppEnv;

    OUString aKey =
        OUString::number( reinterpret_cast< sal_IntPtr >(pEnv->pContext) ) +
        OUString::unacquired(&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.emplace( aKey, pEnv ) );
        SAL_WARN_IF( !insertion.second, "cppu", "key " << aKey << " already in env map" );
    }
    else
    {
        uno_Environment * pHard = nullptr;
        uno_Environment * pWeak = iFind->second;
        (*pWeak->harden)( &pHard, pWeak );
        if (pHard)
        {
            (*pEnv->release)( pEnv );
            *ppEnv = pHard;
        }
        else // registered one is dead
        {
            (*pWeak->releaseWeak)( pWeak );
            (*pEnv->acquireWeak)( pEnv );
            aName2EnvMap[ aKey ] = pEnv;
        }
    }
}

void EnvironmentsData::getRegisteredEnvironments(
    uno_Environment *** pppEnvs, sal_Int32 * pnLen, uno_memAlloc memAlloc,
    std::u16string_view rEnvDcp )
{
    assert(pppEnvs && pnLen && memAlloc && "### null ptr!");

    // max size
    std::vector<uno_Environment*> aFounds(aName2EnvMap.size());
    sal_Int32 nSize = 0;

    // find matching environment
    for ( const auto& rEntry : aName2EnvMap )
    {
        uno_Environment * pWeak = rEntry.second;
        if (rEnvDcp.empty() ||
            rEnvDcp == OUString::unacquired(&pWeak->pTypeName) )
        {
            aFounds[nSize] = nullptr;
            (*pWeak->harden)( &aFounds[nSize], pWeak );
            if (aFounds[nSize])
                ++nSize;
        }
    }

    *pnLen = nSize;
    if (nSize)
    {
        *pppEnvs = static_cast<uno_Environment **>((*memAlloc)(
            sizeof (uno_Environment *) * nSize ));
        OSL_ASSERT( *pppEnvs );
        while (nSize--)
        {
            (*pppEnvs)[nSize] = aFounds[nSize];
        }
    }
    else
    {
        *pppEnvs = nullptr;
    }
}

bool loadEnv(OUString const  & cLibStem,
                    uno_Environment * pEnv)
{
#ifdef DISABLE_DYNLOADING
    uno_initEnvironmentFunc fpInit;

    if ( cLibStem == CPPU_CURRENT_LANGUAGE_BINDING_NAME "_uno" )
        fpInit = CPPU_ENV_uno_initEnvironment;
#if HAVE_FEATURE_JAVA
    else if ( cLibStem == "java_uno" )
        fpInit = java_uno_initEnvironment;
#endif
    else
    {
        SAL_INFO("cppu", ": Unhandled env: " << cLibStem);
        return false;
    }
#else
    // late init with some code from matching uno language binding
    // will be unloaded by environment
    osl::Module aMod;
    try {
        bool bMod = cppu::detail::loadModule(aMod, cLibStem);
        if (!bMod)
            return false;
    }
    catch(...) {
        // Catch everything and convert to return false
        return false;
    }


    uno_initEnvironmentFunc fpInit = reinterpret_cast<uno_initEnvironmentFunc>(aMod.getSymbol(u"" UNO_INIT_ENVIRONMENT ""_ustr));

    if (!fpInit)
        return false;

    aMod.release();
#endif

    (*fpInit)( pEnv ); // init of environment
    return true;
}

}

extern "C"
{


static uno_Environment * initDefaultEnvironment(
    const OUString & rEnvDcp, void * pContext )
{
    // coverity[leaked_storage : FALSE] - lifetime is controlled by acquire()/release() calls
    uno_Environment * pEnv = &(new uno_DefaultEnvironment( rEnvDcp, pContext ))->aBase;
    (*pEnv->acquire)( pEnv );

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

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

        OUString envPurpose = cppu::EnvDcp::getPurpose(rEnvDcp);
        if (!envPurpose.isEmpty())
        {
            OUString libStem(
                OUString::Concat(envPurpose.subView(envPurpose.lastIndexOf(':') + 1)) + "_uno_uno");
            if(!loadEnv(libStem, pEnv))
            {
                pEnv->release(pEnv);
                return nullptr;
            }
        }
    }
    else
    {
        // late init with some code from matching uno language binding
        OUString aStr( envTypeName + "_uno" );

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

    return pEnv;
}


void SAL_CALL uno_createEnvironment(
    uno_Environment ** ppEnv, rtl_uString * pEnvDcp, void * pContext ) noexcept
{
    assert(ppEnv && "### null ptr!");
    if (*ppEnv)
        (*(*ppEnv)->release)( *ppEnv );

    OUString const & rEnvDcp = OUString::unacquired( &pEnvDcp );
    *ppEnv = initDefaultEnvironment( rEnvDcp, pContext );
}

void SAL_CALL uno_getEnvironment(
    uno_Environment ** ppEnv, rtl_uString * pEnvDcp, void * pContext ) noexcept
{
    assert(ppEnv && "### null ptr!");
    OUString const & rEnvDcp = OUString::unacquired( &pEnvDcp );

    EnvironmentsData & rData = theEnvironmentsData();

    ::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 ) noexcept
{
    EnvironmentsData & rData = theEnvironmentsData();

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

} // extern "C"

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