/* -*- 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 "MacabDriver.hxx"
#include "MacabConnection.hxx"

#include <com/sun/star/sdb/SQLContext.hpp>
#include <com/sun/star/lang/NullPointerException.hpp>
#include <com/sun/star/frame/Desktop.hpp>
#include <sal/log.hxx>
#include <comphelper/diagnose_ex.hxx>
#include <strings.hrc>
#include <cppuhelper/supportsservice.hxx>

using namespace com::sun::star::uno;
using namespace com::sun::star::lang;
using namespace com::sun::star::beans;
using namespace com::sun::star::sdbc;
using namespace com::sun::star::sdb;
using namespace com::sun::star::frame;
using namespace connectivity::macab;

namespace {

/** throws a generic SQL exception with SQLState S1000 and error code 0
 */
void throwGenericSQLException( const OUString& _rMessage )
{
    SQLException aError;
    aError.Message = _rMessage;
    aError.SQLState = "S1000";
    aError.ErrorCode = 0;
    throw aError;
}

/** throws an SQLException saying that no Mac OS installation was found
 */
void throwNoMacOSException()
{
    ::connectivity::SharedResources aResources;
    const OUString sError( aResources.getResourceString(
            STR_NO_MAC_OS_FOUND
         ) );
    throwGenericSQLException( sError );
}


}

// = MacabImplModule


MacabImplModule::MacabImplModule()
    :m_bAttemptedLoadModule(false)
    ,m_hConnectorModule(nullptr)
    ,m_pConnectionFactoryFunc(nullptr)
{
}


bool MacabImplModule::isMacOSPresent()
{
    return impl_loadModule();
}


namespace
{
    template< typename FUNCTION >
    void lcl_getFunctionFromModuleOrUnload( oslModule& _rModule, const char* _pAsciiSymbolName, FUNCTION& _rFunction )
    {
        _rFunction = nullptr;
        if ( _rModule )
        {

            const OUString sSymbolName = OUString::createFromAscii( _pAsciiSymbolName );
            _rFunction = reinterpret_cast<FUNCTION>( osl_getSymbol( _rModule, sSymbolName.pData ) );

            if ( !_rFunction )
            {   // did not find the symbol
                SAL_WARN( "connectivity.macab", "lcl_getFunctionFromModuleOrUnload: could not find the symbol " << _pAsciiSymbolName );
                osl_unloadModule( _rModule );
                _rModule = nullptr;
            }
        }
    }
}


extern "C" { static void thisModule() {} }

bool MacabImplModule::impl_loadModule()
{
    if ( m_bAttemptedLoadModule )
        return ( m_hConnectorModule != nullptr );
    m_bAttemptedLoadModule = true;

    OSL_ENSURE( !m_hConnectorModule && !m_pConnectionFactoryFunc,
        "MacabImplModule::impl_loadModule: inconsistence: inconsistency (never attempted load before, but some values already set)!");

    static constexpr OUString sModuleName( u"" SAL_MODULENAME( "macabdrv1" ) ""_ustr );
    m_hConnectorModule = osl_loadModuleRelative( &thisModule, sModuleName.pData, SAL_LOADMODULE_NOW );   // LAZY! #i61335#
    OSL_ENSURE( m_hConnectorModule, "MacabImplModule::impl_loadModule: could not load the implementation library!" );
    if ( !m_hConnectorModule )
        return false;

    lcl_getFunctionFromModuleOrUnload( m_hConnectorModule, "createMacabConnection",   m_pConnectionFactoryFunc );

    if ( !m_hConnectorModule )
        // one of the symbols did not exist
        throw RuntimeException();

    return true;
}


void MacabImplModule::impl_unloadModule()
{
    OSL_PRECOND( m_hConnectorModule != nullptr, "MacabImplModule::impl_unloadModule: no module!" );

    osl_unloadModule( m_hConnectorModule );
    m_hConnectorModule = nullptr;

    m_pConnectionFactoryFunc = nullptr;

    m_bAttemptedLoadModule = false;
}


void MacabImplModule::init()
{
    if ( !impl_loadModule() )
        throwNoMacOSException();

}


MacabConnection* MacabImplModule::createConnection( MacabDriver* _pDriver ) const
{
    OSL_PRECOND( m_hConnectorModule, "MacabImplModule::createConnection: not initialized!" );

    void* pUntypedConnection = (*m_pConnectionFactoryFunc)( _pDriver );
    if ( !pUntypedConnection )
        throw RuntimeException();

    return static_cast< MacabConnection* >( pUntypedConnection );
}


void MacabImplModule::shutdown()
{
    if ( !m_hConnectorModule )
        return;

    impl_unloadModule();
}


// = MacabDriver

MacabDriver::MacabDriver(
    const Reference< css::uno::XComponentContext >& _rxContext)
    : MacabDriver_BASE(m_aMutex),
      m_xContext(_rxContext),
      m_aImplModule()
{
    if ( !m_xContext.is() )
        throw NullPointerException();

    osl_atomic_increment( &m_refCount );
    try
    {
        Reference< XDesktop2 > xDesktop = Desktop::create( m_xContext );
        xDesktop->addTerminateListener( this );
    }
    catch( const Exception& )
    {
        DBG_UNHANDLED_EXCEPTION("connectivity.macab");
    }
    osl_atomic_decrement( &m_refCount );
}

void MacabDriver::disposing()
{
    ::osl::MutexGuard aGuard(m_aMutex);

    // when driver will be destroyed so all our connections have to be destroyed as well
    for (auto& rxConnection : m_xConnections)
    {
        Reference< XComponent > xComp(rxConnection.get(), UNO_QUERY);
        if (xComp.is())
            xComp->dispose();
    }
    m_xConnections.clear();

    WeakComponentImplHelperBase::disposing();
}

OUString SAL_CALL MacabDriver::getImplementationName(  )
{
    return "com.sun.star.comp.sdbc.macab.Driver";
}

sal_Bool SAL_CALL MacabDriver::supportsService( const OUString& _rServiceName )
{
    return cppu::supportsService(this, _rServiceName);
}

Sequence< OUString > SAL_CALL MacabDriver::getSupportedServiceNames(  )
{
    // which service is supported
    // for more information @see com.sun.star.sdbc.Driver
    return { "com.sun.star.sdbc.Driver" };
}

Reference< XConnection > SAL_CALL MacabDriver::connect( const OUString& url, const Sequence< PropertyValue >& info )
{
    ::osl::MutexGuard aGuard(m_aMutex);

    m_aImplModule.init();

    // create a new connection with the given properties and append it to our vector
    MacabConnection* pConnection = m_aImplModule.createConnection( this );
    SAL_WARN_IF( !pConnection, "connectivity.macab", "MacabDriver::connect: no connection has been created by the factory!" );

    // by definition, the factory function returned an object which was acquired once
    Reference< XConnection > xConnection = pConnection;
    pConnection->release();

    // late constructor call which can throw exception and allows a correct dtor call when so
    pConnection->construct( url, info );

    // remember it
    m_xConnections.push_back( WeakReferenceHelper( *pConnection ) );

    return xConnection;
}

sal_Bool SAL_CALL MacabDriver::acceptsURL( const OUString& url )
{
    ::osl::MutexGuard aGuard(m_aMutex);

    if ( !m_aImplModule.isMacOSPresent() )
        return false;

    // here we have to look whether we support this URL format
    return url == "sdbc:address:macab";
}

Sequence< DriverPropertyInfo > SAL_CALL MacabDriver::getPropertyInfo( const OUString&, const Sequence< PropertyValue >& )
{
    // if you have something special to say, return it here :-)
    return Sequence< DriverPropertyInfo >();
}

sal_Int32 SAL_CALL MacabDriver::getMajorVersion(  )
{
    return MACAB_DRIVER_VERSION_MAJOR;
}

sal_Int32 SAL_CALL MacabDriver::getMinorVersion(  )
{
    return MACAB_DRIVER_VERSION_MINOR;
}

void SAL_CALL MacabDriver::queryTermination( const EventObject& )
{
    // nothing to do, nothing to veto
}

void SAL_CALL MacabDriver::notifyTermination( const EventObject& )
{
    m_aImplModule.shutdown();
}

void SAL_CALL MacabDriver::disposing( const EventObject& )
{
    // not interested in (this is the disposing of the desktop, if any)
}

OUString MacabDriver::impl_getConfigurationSettingsPath()
{
    return "/org.openoffice.Office.DataAccess/DriverSettings/com.sun.star.comp.sdbc.macab.Driver";
}

extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface*
connectivity_MacabDriver_get_implementation(
    css::uno::XComponentContext* context, css::uno::Sequence<css::uno::Any> const&)
{
    return cppu::acquire(new MacabDriver(context));
}

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