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

#include <stdio.h>

#include <osl/interlck.h>
#include <osl/diagnose.h>
#include <osl/conditn.h>
#include <osl/mutex.hxx>
#include <osl/process.h>

#include <rtl/alloc.h>
#include <rtl/uuid.h>
#include <rtl/unload.h>

#include <uno/environment.h>
#include <uno/lbnames.h>
#include <uno/mapping.hxx>
#include <uno/threadpool.h>

#include <com/sun/star/uno/Sequence.hxx>

#include <bridges/remote/proxy.hxx>
#include <bridges/remote/stub.hxx>
#include <bridges/remote/context.h>
#include <bridges/remote/mapping.hxx>
#include <bridges/remote/counter.hxx>
#include <bridges/remote/bridgeimpl.hxx>
#include <bridges/remote/helper.hxx>

#include "urp_bridgeimpl.hxx"
#include "urp_writer.hxx"
#include "urp_reader.hxx"
#include "urp_dispatch.hxx"
#include "urp_log.hxx"
#include "urp_propertyobject.hxx"

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

namespace bridges_urp
{
rtl_StandardModuleCount g_moduleCount = MODULE_COUNT_INIT;

//  static void dumpProperties( struct Properties *p )
//  {
//      fprintf( stderr , "FlushBlockSize     : %d\n" , p->nFlushBlockSize );
//      fprintf( stderr , "OnewayTimeoutMUSEC : %d\n" , p->nOnewayTimeoutMUSEC );
//      fprintf( stderr , "OidCacheSize       : %d\n" , p->nOidCacheSize );
//      fprintf( stderr , "TypeCacheSize      : %d\n" , p->nTypeCacheSize );
//      fprintf( stderr , "TidCacheSize       : %d\n" , p->nTidCacheSize );
//      OString o = OUStringToOString( p->sSupportedVersions , RTL_TEXTENCODING_ASCII_US );
//      fprintf( stderr , "SupportedVersions  : %s\n" , o.pData->buffer );
//      o = OUStringToOString( p->sVersion , RTL_TEXTENCODING_ASCII_US );
//      fprintf( stderr , "Version : %s\n" , o.pData->buffer );
//      fprintf( stderr , "SupportsMultipleSynchronous : %d\n" , p->bSupportsMultipleSynchronous );
//      fprintf( stderr , "SupportsMustReply   : %d\n" , p->bSupportsMustReply );
//      fprintf( stderr , "SupportsSynchronous : %d\n" , p->bSupportsSynchronous );
//  }

// PropertySetterThread
//------------------------------------
class PropertySetterThread : public ::osl::Thread
{
    urp_BridgeImpl *m_pImpl;
    ::rtl::OUString m_sProps;
    uno_Environment *m_pEnvRemote;
public:
    PropertySetterThread( uno_Environment *pEnvRemote,
                          urp_BridgeImpl *pImpl,
                          const ::rtl::OUString  & props )
        : m_pImpl( pImpl )
        , m_sProps( props )
        , m_pEnvRemote( pEnvRemote )
        {
            if (m_sProps.getLength() > 0) {
                m_sProps += rtl::OUString(RTL_CONSTASCII_USTRINGPARAM(","));
            }
            m_sProps += rtl::OUString(
                RTL_CONSTASCII_USTRINGPARAM("CurrentContext="));
            // hold the environment in case all references are released before this
            // thread terminates
            m_pEnvRemote->acquire( pEnvRemote );
        }
    ~PropertySetterThread()
        {
            m_pEnvRemote->release( m_pEnvRemote );
        }

    virtual void SAL_CALL run()
        {
            for (;;)
            {
                switch ( m_pImpl->m_pPropertyObject->localRequestChange( ) )
                {
                case 1:
                    sal_Bool bExceptionThrown;
                    m_pImpl->m_pPropertyObject->localCommitChange( m_sProps , &bExceptionThrown );
                    OSL_ENSURE( !bExceptionThrown, "properties were not set\n" );
                    goto done;
                case 0:
                    OSL_TRACE( "urp-bridge : remote-counterpart won the changing-the-protocol-race\n" );
                    goto done;
                }
            }
        done:
            m_pImpl->m_initialized.set();
        }
    virtual void SAL_CALL onTerminated()
        {
            delete this;
        }
};
//------------------------------------


void test_cache()
{
    OUString a = OUString( RTL_CONSTASCII_USTRINGPARAM( "a" ) );
    OUString b = OUString( RTL_CONSTASCII_USTRINGPARAM( "b" ) );
    OUString c = OUString( RTL_CONSTASCII_USTRINGPARAM( "c" ) );
    Cache < OUString , equalOUString > cache ( 2 );

    sal_Int32 n = cache.put( a );
    if (cache.seek( a ) != n)
    {
        OSL_ASSERT( false );
    }
    OSL_ASSERT( 0 == n );

    n = cache.put( b );
    OSL_ASSERT( 1 == n );

    cache.put( c );

    OSL_ASSERT( 0xffff == cache.seek( a ) );
    OSL_ASSERT( 1 == cache.seek( b ) );
    OSL_ASSERT( 0 == cache.seek( c ) );

    OSL_ASSERT( 1 == cache.put( a ) );
    OSL_ASSERT( 0xffff == cache.seek( b) );
    OSL_ASSERT( 1 == cache.seek( a ) );
    OSL_ASSERT( 0 == cache.seek( c ) );
}

/*******************
 * Are we within thread, that calls destructors of static objects ?
 *******************/
sal_Bool g_bStaticDestructorsCalled = sal_False;
struct StaticSingleton
{
    ~StaticSingleton()
        {
            ::osl::MutexGuard guard( ::osl::Mutex::getGlobalMutex() );
            g_bStaticDestructorsCalled = sal_True;
        }
};
StaticSingleton singleton;

#if OSL_DEBUG_LEVEL > 1
static MyCounter thisCounter( "Remote Environment" );
#endif

void SAL_CALL allThreadsAreGone( uno_Environment * pEnvRemote )
{
    remote_Context *pContext = (remote_Context *) pEnvRemote->pContext;
    urp_BridgeImpl *pImpl = ( urp_BridgeImpl *) pContext->m_pBridgeImpl;

    // if the current thread is not the writer thread, the writer thread
    // object is not destroyed up to now, though it may already have run out.
    // In both cases, it must be safe to cal pImpl->m_pWriter->getIdentifier()
    OSL_ASSERT( pImpl->m_pWriter );
    if( pImpl->m_pWriter->getIdentifier() == osl_getThreadIdentifier(0) )
    {
        // This is the writer thread. It has done some release calls,
        // and is now the last one, that was active. Because the writer
        // thread holds the environment weakly, there may also be a thread within
        // the dispose of the bridge ( because the enviroment may have a refcount == 0 ).
        // However, this thread MUST wait for the writer thread, so it is perfectly ok,
        // not to set m_cndWaitForThreads. ( The check for m_nRemoteThreads is done
        // after the join of the writer thread ).
    }
    else
    {
        pImpl->m_cndWaitForThreads.set();
    }

}

void SAL_CALL releaseStubs( uno_Environment *pEnvRemote )
{

    ((remote_Context * ) pEnvRemote->pContext )->m_pBridgeImpl->m_bReleaseStubsCalled = sal_True;

    remote_Interface **ppInterfaces = 0;
    sal_Int32 nCount;
    pEnvRemote->pExtEnv->getRegisteredInterfaces( pEnvRemote->pExtEnv,
                                                  (void***)&ppInterfaces,
                                                  &nCount,
                                                  rtl_allocateMemory );

    sal_Int32 i;
    for( i  = 0 ; i < nCount ; i ++ )
    {
          if( ppInterfaces[i]->acquire == bridges_remote::acquireUno2RemoteStub )
          {
              // these are freed by the environment, so no release necessary
              pEnvRemote->pExtEnv->revokeInterface( pEnvRemote->pExtEnv, ppInterfaces[i] );
          }
          else
          {
            ppInterfaces[i]->release( ppInterfaces[i] );
          }
    }

    rtl_freeMemory( (void*) ppInterfaces );
}

} // end namespace bridges_urp

using namespace bridges_urp;

extern "C" {

static void SAL_CALL RemoteEnvironment_thisDispose( uno_Environment *pEnvRemote )
{
    remote_Context *pContext = (remote_Context *) pEnvRemote->pContext;
    urp_BridgeImpl *pImpl = ( urp_BridgeImpl *) pContext->m_pBridgeImpl;

    ::osl::ClearableMutexGuard guard( pImpl->m_disposingMutex  );
      if( pContext->m_pBridgeImpl->m_bDisposed &&
          ( ! pImpl->m_pReader ||
            osl_getThreadIdentifier(0) ==
            (oslThreadIdentifier) pImpl->m_pReader->getIdentifier() ) )
      {
          return;
      }
    // in case, that the static destructors already have been called, no
    // tiding up is done.
    bool tidyUp;
    {
        ::osl::MutexGuard guard2( ::osl::Mutex::getGlobalMutex() );
        tidyUp = ! g_bStaticDestructorsCalled &&
            ! pContext->m_pBridgeImpl->m_bDisposed;
    }
    if( tidyUp )
    {
        // TODO : not threadsafe
        // synchronization with dispatch methods needed !

        pImpl->m_bDisposed = sal_True;


        // close the connection
        uno_threadpool_dispose( pImpl->m_hThreadPool );
        pImpl->m_pWriter->abortThread();
          pContext->m_pConnection->close( pContext->m_pConnection );

        if( osl_getThreadIdentifier(0) ==
            (oslThreadIdentifier) pImpl->m_pReader->getIdentifier() )
        {
            // This is the reader thread. Let the thread destroy itself
            // the reader thread object must also release the connection at this stage !
            pImpl->m_pReader->destroyYourself();
            pImpl->m_pReader = 0;
        }
        else
        {
            // wait for the reader thread
            // the reader thread object must also release the connection,
            // when terminating !!!!
            pImpl->m_pReader->join();
        }

        // wait for the writer thread
        pImpl->m_pWriter->join();

        // now let the context go !
        pContext->dispose( pContext );

          if( 0 != pImpl->m_nRemoteThreads )
          {
              // Wait for all threads
            guard.clear();
              pImpl->m_cndWaitForThreads.wait();
              OSL_ASSERT( ! pImpl->m_nRemoteThreads );
          }
          else
          {
              guard.clear();
          }
#ifdef BRIDGES_URP_PROT
        if( pImpl->m_pLogFile )
        {
            fclose( pImpl->m_pLogFile );
            pImpl->m_pLogFile = 0;
        }
#endif
#if OSL_DEBUG_LEVEL > 1
        pImpl->dumpErrors( stderr );
#endif

        // destroy the threads
        delete pImpl->m_pWriter;
        pImpl->m_pWriter = 0;

        if( pImpl->m_pReader != 0 )
        {
            // This is not the reader thread, so the thread object is deleted
            delete pImpl->m_pReader;
            pImpl->m_pReader = 0;
        }

        bool bReleaseStubs = false;
        {
            ::osl::MutexGuard guard2( ::osl::Mutex::getGlobalMutex() );
            bReleaseStubs = !g_bStaticDestructorsCalled;
        }
        if( bReleaseStubs )
        {
            releaseStubs( pEnvRemote );
        }
    }
}

static void SAL_CALL RemoteEnvironment_thisDisposing(
    uno_Environment *pEnvRemote )
{
    remote_Context *pContext = (remote_Context * )pEnvRemote->pContext;
    urp_BridgeImpl *pImpl = ((urp_BridgeImpl*) pContext->m_pBridgeImpl);

    {
        ::osl::ClearableMutexGuard guard( pImpl->m_disposingMutex );
        if( ! pImpl->m_bDisposed )
        {
            guard.clear();
            urp_sendCloseConnection( pEnvRemote );
            RemoteEnvironment_thisDispose( pEnvRemote );
        }
    }
    pImpl->m_pPropertyObject->thisRelease();
    pImpl->m_pPropertyObject = 0;

     uno_threadpool_destroy( pImpl->m_hThreadPool );

    delete pImpl;
    pContext->aBase.release( (uno_Context * ) pContext );
    g_moduleCount.modCnt.release( &g_moduleCount.modCnt );
#if OSL_DEBUG_LEVEL > 1
      thisCounter.release();
#endif
}

static void SAL_CALL RemoteEnvironment_thisComputeObjectIdentifier(
    uno_ExtEnvironment *, rtl_uString **, void *)
{
    OSL_ENSURE( 0, "RemoteEnvironment_thisComputeObjectIdentifier should never be called" );
}

static void SAL_CALL RemoteEnvironment_thisAcquireInterface(
    uno_ExtEnvironment *, void *pInterface )
{
    ((remote_Interface *)pInterface)->acquire( ( remote_Interface *) pInterface );
}

static void SAL_CALL RemoteEnvironment_thisReleaseInterface(
    uno_ExtEnvironment *, void *pInterface )
{
    ((remote_Interface *)pInterface)->release( ( remote_Interface *) pInterface );
}

//##################################################################################################
void SAL_CALL uno_initEnvironment( uno_Environment * pEnvRemote )
{
    g_moduleCount.modCnt.acquire( &g_moduleCount.modCnt );
    // set C-virtual methods
    pEnvRemote->environmentDisposing = RemoteEnvironment_thisDisposing;
    pEnvRemote->pExtEnv->computeObjectIdentifier = RemoteEnvironment_thisComputeObjectIdentifier;
    pEnvRemote->pExtEnv->acquireInterface = RemoteEnvironment_thisAcquireInterface;
    pEnvRemote->pExtEnv->releaseInterface = RemoteEnvironment_thisReleaseInterface;
    pEnvRemote->dispose = RemoteEnvironment_thisDispose;

    remote_Context *pContext = ( remote_Context * ) pEnvRemote->pContext;
    pContext->aBase.acquire( ( uno_Context * )  pContext );
      pContext->getRemoteInstance = ::bridges_remote::remote_sendQueryInterface;

    // Initialize impl struct     urp_BridgeImpl
    urp_BridgeImpl *pImpl = new ::bridges_urp::urp_BridgeImpl( 256, 8192 );
    pContext->m_pBridgeImpl = pImpl;

    // Initialize threadpool
    pImpl->m_hThreadPool = uno_threadpool_create();

    // take the bridgepointer as id
    pImpl->m_properties.seqBridgeID = ByteSequence( (sal_Int8*)&pEnvRemote , sizeof( pEnvRemote ) );

    pImpl->m_allThreadsAreGone = allThreadsAreGone;
    pImpl->m_sendRequest = urp_sendRequest;
    pImpl->m_nRemoteThreads = 0;
    pImpl->m_bDisposed = sal_False;
    pImpl->m_bReleaseStubsCalled = sal_False;

    pImpl->m_pPropertyObject = new PropertyObject( &(pImpl->m_properties ),  pEnvRemote, pImpl );
    pImpl->m_pPropertyObject->thisAcquire();

    OUString sProtocolProperties;
    if( pContext->m_pProtocol->length > 3 )
    {
        sProtocolProperties = OUString( pContext->m_pProtocol ).copy( 4, pContext->m_pProtocol->length-4);
    }
    if( sProtocolProperties.getLength() )
    {
        struct Properties props = pImpl->m_properties;
        assignFromStringToStruct( sProtocolProperties , &props );
        if( ! props.bNegotiate )
        {
            // no negotiation takes place, the creator of the bridge knows the parameter
            // of the other side !
            pImpl->applyProtocolChanges( props );
            sProtocolProperties = OUString();
        }
    }

#ifdef BRIDGES_URP_PROT
      char *p = getenv( "PROT_REMOTE" );
    pImpl->m_pLogFile = 0;
      if( p )
      {
        MutexGuard guard( Mutex::getGlobalMutex() );
        static int counter;
        oslProcessInfo data;
        data.Size = sizeof( data );
        osl_getProcessInfo( 0 , osl_Process_HEAPUSAGE | osl_Process_IDENTIFIER , &data );
        OString s(p);
        s += "_pid";
        s += OString::valueOf( (sal_Int32) data.Ident );
        s += "_";
        s += OString::valueOf( (sal_Int32) counter );
        pImpl->m_sLogFileName = s;
        // clear the file
          FILE *f = fopen( s.getStr() , "w" );
        OSL_ASSERT( f );
        if( getenv( "PROT_REMOTE_FAST") )
        {
            pImpl->m_pLogFile = f;
        }
        else
        {
            fclose( f );
        }
        counter++;
      }
#endif

    // start reader and writer threads
    pImpl->m_pWriter = new ::bridges_urp::OWriterThread( pContext->m_pConnection , pImpl,
                                                         pEnvRemote);
    pImpl->m_pWriter->create();

    pImpl->m_pReader = new ::bridges_urp::OReaderThread( pContext->m_pConnection ,
                                                         pEnvRemote,
                                                         pImpl->m_pWriter );
    pImpl->m_pReader->create();

    PropertySetterThread *pPropsSetterThread =
        new PropertySetterThread( pEnvRemote, pImpl , sProtocolProperties );
    pPropsSetterThread->create();
#if OSL_DEBUG_LEVEL > 1
    thisCounter.acquire();
#endif
}


//##################################################################################################
void SAL_CALL uno_ext_getMapping(
    uno_Mapping ** ppMapping,
    uno_Environment * pFrom,
    uno_Environment * pTo )
{
    OSL_ASSERT( ppMapping && pFrom && pTo );
    if (ppMapping && pFrom && pTo)
    {
        if (*ppMapping)
            ((*ppMapping)->release)( *ppMapping );
        bridges_remote::RemoteMapping * pMapping = 0;

        ::rtl::OUString sFromName = pFrom->pTypeName;
        ::rtl::OUString sToName = pTo->pTypeName;
        ::rtl::OUString sUno(RTL_CONSTASCII_USTRINGPARAM(UNO_LB_UNO));
        ::rtl::OUString sRemote(RTL_CONSTASCII_USTRINGPARAM("urp"));
        if ( sFromName.equalsIgnoreAsciiCase( sRemote ) &&
             sToName.equalsIgnoreAsciiCase( sUno ) )
        {
            pMapping =  new bridges_remote::RemoteMapping( pTo, /* Uno */
                                     pFrom, /*remote*/
                                     bridges_remote::remoteToUno,
                                     OUString() );
        }
        else if ( sFromName.equalsIgnoreAsciiCase( sUno ) &&
                  sToName.equalsIgnoreAsciiCase( sRemote ) )
        {
            pMapping =  new bridges_remote::RemoteMapping( pFrom ,
                                           pTo ,
                                           bridges_remote::unoToRemote,
                                           OUString() );
        }

        *ppMapping = (uno_Mapping * )pMapping;
        OUString dummy;
        uno_registerMapping( ppMapping ,
                             bridges_remote::freeRemoteMapping,
                             pFrom ,
                             pTo ,
                             dummy.pData );
    }
}

sal_Bool SAL_CALL component_canUnload( TimeValue *pTime )
{
    return g_moduleCount.canUnload( &g_moduleCount , pTime );
}

}

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