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


#include <cppu/helper/purpenv/Environment.hxx>

#include <osl/diagnose.h>
#include <sal/log.hxx>
#include <uno/lbnames.h>
#include <cppu/Enterable.hxx>

#include <typelib/typedescription.h>
#include <osl/interlck.h>
#include <memory>

extern "C" {
typedef void EnvFun_P   (uno_Environment *);
typedef void EnvFun_PP_P(uno_Environment ** ppHardEnv, uno_Environment *);
typedef void ExtEnv_registerProxyInterface (uno_ExtEnvironment                 *,
                                                     void                              ** ppProxy,
                                                     uno_freeProxyFunc                    freeProxy,
                                                     rtl_uString * pOId,
                                                     typelib_InterfaceTypeDescription   * pTypeDescr);
typedef void ExtEnv_revokeInterface        (uno_ExtEnvironment                 *,
                                                     void                               * pInterface);
typedef void ExtEnv_getObjectIdentifier    (uno_ExtEnvironment                 *,
                                                     rtl_uString                       **,
                                                     void                               *);
typedef void ExtEnv_getRegisteredInterface (uno_ExtEnvironment                 *,
                                                     void                              **,
                                                     rtl_uString                        *,
                                                     typelib_InterfaceTypeDescription   *);
typedef void ExtEnv_getRegisteredInterfaces(uno_ExtEnvironment                 *,
                                                     void                             *** pppInterfaces,
                                                     sal_Int32                          * pnLen,
                                                     uno_memAlloc                         memAlloc);
typedef void ExtEnv_computeObjectIdentifier(uno_ExtEnvironment                 *,
                                                     rtl_uString                       ** ppOId,
                                                     void                               * pInterface);
typedef void ExtEnv_acquireInterface       (uno_ExtEnvironment                 *,
                                                     void                               * pInterface);
typedef void ExtEnv_releaseInterface       (uno_ExtEnvironment                 *,
                                                     void                               * pInterface);
}

namespace {

class Base : public cppu::Enterable
{
public:
    explicit Base(uno_Environment * pEnv, cppu::Enterable * pEnterable);

    void acquireWeak();
    void releaseWeak();
    void harden     (uno_Environment ** ppHardEnv);
    void acquire();
    void release();

    void registerProxyInterface (void                                  ** ppProxy,
                                 uno_freeProxyFunc                        freeProxy,
                                 OUString                         const & oid,
                                 typelib_InterfaceTypeDescription       * pTypeDescr);
    void revokeInterface        (void                                   * pInterface);
    void getObjectIdentifier    (void                                   * pInterface,
                                 OUString                               * pOid);
    void getRegisteredInterface (void                                  **,
                                 OUString                    const & oid,
                                 typelib_InterfaceTypeDescription       *);
    void getRegisteredInterfaces(void                                 ***,
                                 sal_Int32                              * pnLen,
                                 uno_memAlloc                             memAlloc);
    void computeObjectIdentifier(void                                   * pInterface,
                                 OUString                               * pOid);
    void acquireInterface       (void                                   * pInterface);
    void releaseInterface       (void                                   * pInterface);

    virtual void v_enter() override;
    virtual void v_leave() override;
    virtual void v_callInto_v(uno_EnvCallee * pCallee, va_list * pParam) override;
    virtual void v_callOut_v (uno_EnvCallee * pCallee, va_list * pParam) override;
    virtual bool v_isValid   (OUString * pReason) override;

protected:
    oslInterlockedCount    m_nRef;
    uno_Environment      * m_pEnv;
    std::unique_ptr<cppu::Enterable> m_pEnterable;

    EnvFun_P    * m_env_acquire;
    EnvFun_P    * m_env_release;
    EnvFun_PP_P * m_env_harden;
    EnvFun_P    * m_env_acquireWeak;
    EnvFun_P    * m_env_releaseWeak;

    ExtEnv_registerProxyInterface  * m_env_registerProxyInterface;
    ExtEnv_revokeInterface         * m_env_revokeInterface;
    ExtEnv_getObjectIdentifier     * m_env_getObjectIdentifier;
    ExtEnv_getRegisteredInterface  * m_env_getRegisteredInterface;
    ExtEnv_getRegisteredInterfaces * m_env_getRegisteredInterfaces;
    ExtEnv_computeObjectIdentifier * m_env_computeObjectIdentifier;
    ExtEnv_acquireInterface        * m_env_acquireInterface;
    ExtEnv_releaseInterface        * m_env_releaseInterface;

    virtual  ~Base() override;
};

}

extern "C" {
static void s_acquire(uno_Environment * pEnv) //SAL_THROW_EXTERN_C()
{
    Base * pBase = static_cast<Base *>(pEnv->pReserved);
    pBase->acquire();
}

static void s_release(uno_Environment * pEnv) noexcept
{
    Base * pBase = static_cast<Base *>(pEnv->pReserved);
    pBase->release();
}

static void s_harden(uno_Environment ** ppHardEnv, uno_Environment * pEnv) noexcept
{
    Base * pBase = static_cast<Base *>(pEnv->pReserved);
    pBase->harden(ppHardEnv);
}

static void s_acquireWeak(uno_Environment * pEnv) noexcept
{
    Base * pBase = static_cast<Base *>(pEnv->pReserved);
    pBase->acquireWeak();
}

static void s_releaseWeak(uno_Environment * pEnv) noexcept
{
    Base * pBase = static_cast<Base *>(pEnv->pReserved);
    pBase->releaseWeak();
}


static void s_registerProxyInterface(uno_ExtEnvironment                * pExtEnv,
                                                         void                             ** ppProxy,
                                                         uno_freeProxyFunc                   freeProxy,
                                                         rtl_uString                       * pOId,
                                                         typelib_InterfaceTypeDescription  * pTypeDescr)
{
    Base * pBase = static_cast<Base *>(pExtEnv->aBase.pReserved);
    pBase->registerProxyInterface(ppProxy, freeProxy, pOId, pTypeDescr);
}

static void s_revokeInterface(uno_ExtEnvironment * pExtEnv, void * pInterface)
{
    Base * pBase = static_cast<Base *>(pExtEnv->aBase.pReserved);
    pBase->revokeInterface(pInterface);
}

static void s_getObjectIdentifier(uno_ExtEnvironment *  pExtEnv,
                                                      rtl_uString        ** ppOId,
                                                      void               *  pInterface)
{
    Base * pBase = static_cast<Base *>(pExtEnv->aBase.pReserved);
    pBase->getObjectIdentifier(pInterface, reinterpret_cast<OUString *>(ppOId));
}

static void s_getRegisteredInterface(uno_ExtEnvironment *  pExtEnv,
                                                         void               ** ppInterface,
                                                         rtl_uString        *  pOId,
                                                         typelib_InterfaceTypeDescription * pTypeDescr)
{
    Base * pBase = static_cast<Base *>(pExtEnv->aBase.pReserved);
    pBase->getRegisteredInterface(ppInterface, pOId, pTypeDescr);
}

static void s_getRegisteredInterfaces(uno_ExtEnvironment   * pExtEnv,
                                                          void               *** pppInterface,
                                                          sal_Int32            * pnLen,
                                                          uno_memAlloc           memAlloc)
{
    Base * pBase = static_cast<Base *>(pExtEnv->aBase.pReserved);
    pBase->getRegisteredInterfaces(pppInterface, pnLen, memAlloc);
}

static void s_computeObjectIdentifier(uno_ExtEnvironment *  pExtEnv,
                                                          rtl_uString        ** ppOId,
                                                          void               *  pInterface)
{
    Base * pBase = static_cast<Base *>(pExtEnv->aBase.pReserved);
    pBase->computeObjectIdentifier(pInterface, reinterpret_cast<OUString *>(ppOId));
}

static void s_acquireInterface(uno_ExtEnvironment * pExtEnv, void * pInterface) {
    Base * pBase = static_cast<Base *>(pExtEnv->aBase.pReserved);
    pBase->acquireInterface(pInterface);
}

static void s_releaseInterface(uno_ExtEnvironment * pExtEnv, void * pInterface) {
    Base * pBase = static_cast<Base *>(pExtEnv->aBase.pReserved);
    pBase->releaseInterface(pInterface);
}

}

Base::Base(uno_Environment * pEnv, cppu::Enterable * pEnterable)
    :m_nRef(1),
     m_pEnv(pEnv),
     m_pEnterable     (pEnterable),
     m_env_acquire    (pEnv->acquire),
     m_env_release    (pEnv->release),
     m_env_harden     (pEnv->harden),
     m_env_acquireWeak(pEnv->acquireWeak),
     m_env_releaseWeak(pEnv->releaseWeak),
     m_env_registerProxyInterface (pEnv->pExtEnv->registerProxyInterface),
     m_env_revokeInterface        (pEnv->pExtEnv->revokeInterface),
     m_env_getObjectIdentifier    (pEnv->pExtEnv->getObjectIdentifier),
     m_env_getRegisteredInterface (pEnv->pExtEnv->getRegisteredInterface),
     m_env_getRegisteredInterfaces(pEnv->pExtEnv->getRegisteredInterfaces),
     m_env_computeObjectIdentifier(pEnv->pExtEnv->computeObjectIdentifier),
     m_env_acquireInterface       (pEnv->pExtEnv->acquireInterface),
     m_env_releaseInterface       (pEnv->pExtEnv->releaseInterface)
{
    SAL_INFO("cppu.purpenv", "LIFE: cppu::helper::purpenv::Base::Base(uno_Environment * pEnv) -> " << this);
    OSL_ENSURE(
        rtl_ustr_ascii_compare_WithLength(pEnv->pTypeName->buffer, rtl_str_getLength(UNO_LB_UNO), UNO_LB_UNO)
            == 0,
            "### wrong environment type!");

    pEnv->acquire     = s_acquire;
    pEnv->release     = s_release;
    pEnv->harden      = s_harden;
    pEnv->acquireWeak = s_acquireWeak;
    pEnv->releaseWeak = s_releaseWeak;

    pEnv->pExtEnv->registerProxyInterface  = s_registerProxyInterface;
    pEnv->pExtEnv->revokeInterface         = s_revokeInterface;
    pEnv->pExtEnv->getObjectIdentifier     = s_getObjectIdentifier;
    pEnv->pExtEnv->getRegisteredInterface  = s_getRegisteredInterface;
    pEnv->pExtEnv->getRegisteredInterfaces = s_getRegisteredInterfaces;
    pEnv->pExtEnv->computeObjectIdentifier = s_computeObjectIdentifier;
    pEnv->pExtEnv->acquireInterface        = s_acquireInterface;
    pEnv->pExtEnv->releaseInterface        = s_releaseInterface;

    pEnv->pReserved = this;
}

Base::~Base()
{
    SAL_INFO("cppu.purpenv", "LIFE: cppu::helper::purpenv::Base::~Base() -> " << this);

    m_pEnv->acquire     = m_env_acquire;
    m_pEnv->release     = m_env_release;
    m_pEnv->harden      = m_env_harden;
    m_pEnv->acquireWeak = m_env_acquireWeak;
    m_pEnv->releaseWeak = m_env_releaseWeak;

    m_pEnv->pReserved = nullptr;

    m_pEnterable.reset();
    m_pEnv->release(m_pEnv);
}

void Base::acquire()
{
    m_env_acquire(m_pEnv);

    osl_atomic_increment(&m_nRef);
}

void Base::release()
{
    if (osl_atomic_decrement(&m_nRef) == 0)
        delete this;

    else
        m_env_release(m_pEnv);
}

void Base::harden(uno_Environment ** ppHardEnv)
{
    m_env_harden(ppHardEnv, m_pEnv);
    osl_atomic_increment(&m_nRef);
}

void Base::acquireWeak()
{
    m_env_acquireWeak(m_pEnv);
}

void Base::releaseWeak()
{
    m_env_releaseWeak(m_pEnv);
}


extern "C" { static void s_registerProxyInterface_v(va_list * pParam)
{
    uno_ExtEnvironment                   * pExtEnv    = va_arg(*pParam, uno_ExtEnvironment *);
    void                                ** ppProxy    = va_arg(*pParam, void **);
    uno_freeProxyFunc                      freeProxy  = va_arg(*pParam, uno_freeProxyFunc);
    rtl_uString                          * pOId       = va_arg(*pParam, rtl_uString *);
    typelib_InterfaceTypeDescription     * pTypeDescr = va_arg(*pParam, typelib_InterfaceTypeDescription *);
    ExtEnv_registerProxyInterface  * pRegisterProxyInterface
        = va_arg(*pParam, ExtEnv_registerProxyInterface *);

    pRegisterProxyInterface(pExtEnv, ppProxy, freeProxy, pOId, pTypeDescr);
}}

void Base::registerProxyInterface(void                                  ** ppProxy,
                                  uno_freeProxyFunc                        freeProxy,
                                  OUString                         const & oid,
                                  typelib_InterfaceTypeDescription       * pTypeDescr)
{
     uno_Environment_invoke(m_pEnv,
                           s_registerProxyInterface_v,
                           m_pEnv->pExtEnv,
                           ppProxy,
                           freeProxy,
                           oid.pData,
                           pTypeDescr,
                           m_env_registerProxyInterface);
}


extern "C" { static void s_revokeInterface_v(va_list * pParam)
{
    uno_ExtEnvironment * pExtEnv    = va_arg(*pParam, uno_ExtEnvironment *);
    void               * pInterface = va_arg(*pParam, void *);
    ExtEnv_revokeInterface * pRevokeInterface = va_arg(*pParam, ExtEnv_revokeInterface *);

    pRevokeInterface(pExtEnv, pInterface);
}}

void Base::revokeInterface(void * pInterface)
{
     uno_Environment_invoke(m_pEnv,
                           s_revokeInterface_v,
                           m_pEnv->pExtEnv,
                           pInterface,
                           m_env_revokeInterface);
}


extern "C" { static void s_getObjectIdentifier_v(va_list * pParam)
{
    uno_ExtEnvironment * pExtEnv    = va_arg(*pParam, uno_ExtEnvironment *);
    void               * pInterface = va_arg(*pParam, void *);
    OUString      * pOId       = va_arg(*pParam, OUString *);
    ExtEnv_getObjectIdentifier * pGetObjectIdentifier
        = va_arg(*pParam, ExtEnv_getObjectIdentifier *);

    pGetObjectIdentifier(pExtEnv, reinterpret_cast<rtl_uString **>(pOId), pInterface);
}}

void Base::getObjectIdentifier(void * pInterface, OUString * pOid)
{
     uno_Environment_invoke(m_pEnv,
                           s_getObjectIdentifier_v,
                           m_pEnv->pExtEnv,
                           pInterface,
                           pOid,
                           m_env_getObjectIdentifier);
}


extern "C" { static void s_getRegisteredInterface_v(va_list * pParam)
{
    uno_ExtEnvironment                   * pExtEnv     = va_arg(*pParam, uno_ExtEnvironment *);
    void                                ** ppInterface = va_arg(*pParam, void **);
    rtl_uString                          * pOId        = va_arg(*pParam, rtl_uString *);
    typelib_InterfaceTypeDescription     * pTypeDescr  = va_arg(*pParam, typelib_InterfaceTypeDescription *);
    ExtEnv_getRegisteredInterface  * pGetRegisteredInterface
        = va_arg(*pParam, ExtEnv_getRegisteredInterface *);

    pGetRegisteredInterface(pExtEnv, ppInterface, pOId, pTypeDescr);
}}

void Base::getRegisteredInterface(void ** ppInterface,
                                  OUString const & oid,
                                  typelib_InterfaceTypeDescription * pTypeDescr)
{
     uno_Environment_invoke(m_pEnv,
                           s_getRegisteredInterface_v,
                           m_pEnv->pExtEnv,
                           ppInterface,
                           oid.pData,
                           pTypeDescr,
                           m_env_getRegisteredInterface);
}


extern "C" { static void s_getRegisteredInterfaces_v(va_list * pParam)
{
    uno_ExtEnvironment   * pExtEnv      = va_arg(*pParam, uno_ExtEnvironment *);
    void               *** pppInterface = va_arg(*pParam, void ***);
    sal_Int32            * pnLen        = va_arg(*pParam, sal_Int32 *);
    uno_memAlloc           memAlloc     = va_arg(*pParam, uno_memAlloc);
    ExtEnv_getRegisteredInterfaces * pGetRegisteredInterfaces
        = va_arg(*pParam, ExtEnv_getRegisteredInterfaces *);

    pGetRegisteredInterfaces(pExtEnv, pppInterface, pnLen, memAlloc);
}}

void Base::getRegisteredInterfaces(void         *** pppInterface,
                                   sal_Int32      * pnLen,
                                   uno_memAlloc     memAlloc)
{
     uno_Environment_invoke(m_pEnv,
                           s_getRegisteredInterfaces_v,
                           m_pEnv->pExtEnv,
                           pppInterface,
                           pnLen,
                           memAlloc,
                           m_env_getRegisteredInterfaces);
}


extern "C" { static void s_computeObjectIdentifier_v(va_list * pParam)
{
    uno_ExtEnvironment * pExtEnv    = va_arg(*pParam, uno_ExtEnvironment *);
    void               * pInterface = va_arg(*pParam, void *);
    OUString      * pOId       = va_arg(*pParam, OUString *);
    ExtEnv_computeObjectIdentifier * pComputeObjectIdentifier
        = va_arg(*pParam, ExtEnv_computeObjectIdentifier *);

    pComputeObjectIdentifier(pExtEnv, reinterpret_cast<rtl_uString **>(pOId), pInterface);
}}

void Base::computeObjectIdentifier(void * pInterface, OUString * pOid)
{
     uno_Environment_invoke(m_pEnv,
                           s_computeObjectIdentifier_v,
                           m_pEnv->pExtEnv,
                           pInterface,
                           pOid,
                           m_env_computeObjectIdentifier);
}


extern "C" { static void s_acquireInterface_v(va_list * pParam)
{
    uno_ExtEnvironment * pExtEnv    = va_arg(*pParam, uno_ExtEnvironment *);
    void               * pInterface = va_arg(*pParam, void *);
    ExtEnv_acquireInterface * pAcquireInterface
        = va_arg(*pParam, ExtEnv_acquireInterface *);

    pAcquireInterface(pExtEnv, pInterface);
}}

void Base::acquireInterface(void * pInterface)
{
     uno_Environment_invoke(m_pEnv, s_acquireInterface_v, m_pEnv->pExtEnv, pInterface, m_env_acquireInterface);
}


extern "C" { static void s_releaseInterface_v(va_list * pParam)
{
    uno_ExtEnvironment * pExtEnv    = va_arg(*pParam, uno_ExtEnvironment *);
    void               * pInterface = va_arg(*pParam, void *);
    ExtEnv_releaseInterface * pReleaseInterface
        = va_arg(*pParam, ExtEnv_releaseInterface *);

    pReleaseInterface(pExtEnv, pInterface);
}}

void Base::releaseInterface(void * pInterface)
{
     uno_Environment_invoke(m_pEnv,
                           s_releaseInterface_v,
                           m_pEnv->pExtEnv,
                           pInterface,
                           m_env_releaseInterface);
}

void Base::v_enter()
{
    m_pEnterable->enter();
}

void Base::v_leave()
{
    m_pEnterable->leave();
}

void Base::v_callInto_v(uno_EnvCallee * pCallee, va_list * pParam)
{
    m_pEnterable->callInto_v(pCallee, pParam);
}

void Base::v_callOut_v(uno_EnvCallee * pCallee, va_list * pParam)
{
    m_pEnterable->callOut_v(pCallee, pParam);
}

bool Base::v_isValid(OUString * pReason)
{
    return m_pEnterable->isValid(pReason);
}

namespace cppu::helper::purpenv {

void Environment_initWithEnterable(uno_Environment * pEnvironment, cppu::Enterable * pEnterable)
{
    new Base(pEnvironment, pEnterable);
}

}

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