
/* -*- mode: C++; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: t -*- */

// openpluginlib - A plugin interface to openlibraries.

// Copyright (C) 2005-2007 Editopia Inc.
// Released under the LGPL.
// For more information, see http://www.openlibraries.org.

// std
#include <algorithm>
#include <functional>

#ifdef WIN32
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#else
#include <dlfcn.h>
#endif // WIN32

// boost
#include <boost/filesystem/path.hpp>

// opl
#include <openpluginlib/pl/openplugin.hpp>
#include <openpluginlib/pl/utf8_utils.hpp>
#include <openpluginlib/pl/log.hpp>

namespace fs = boost::filesystem;

namespace olib { namespace openpluginlib { 

namespace detail {

namespace
{
#ifdef WIN32

    HMODULE dlopen_( const boost::filesystem::path& path )
    {
	return LoadLibrary( to_wstring( path.native_file_string( ).c_str( ) ).c_str( ) );
    }
	
    void* dlsym_( HMODULE shared, const char* entry_point )
    {
	return GetProcAddress( shared, entry_point );
    }

    bool dlclose_( HMODULE shared )
    {
	return FreeLibrary( shared ) == TRUE;
    }

#else

    void* dlopen_( const boost::filesystem::path& path )
    {
	// CY: It is essential that RTLD_GLOBAL is used here for dynamic_cast to be
	// functional. All g++ apps should also link with -Wl,-E.
	return dlopen( path.native_file_string( ).c_str( ), RTLD_GLOBAL | RTLD_NOW );
    }
	
    void* dlsym_( void* shared, const char* entry_point )
    {
	return dlsym( shared, entry_point );
    }

    bool dlclose_( void* shared )
    {
	return dlclose( shared ) == 0;
    }

#endif

    bool resolve_plugin_symbols( plugin_resolver& resolver )
    {
	resolver.init	        = ( openplugin_init )		dlsym_( resolver.dl_handle, "openplugin_init" );
	resolver.uninit		= ( openplugin_uninit )		dlsym_( resolver.dl_handle, "openplugin_uninit" );
	resolver.create_plugin	= ( openplugin_create_plugin )	dlsym_( resolver.dl_handle, "openplugin_create_plugin" );
	resolver.destroy_plugin = ( openplugin_destroy_plugin ) dlsym_( resolver.dl_handle, "openplugin_destroy_plugin" );
	
	if( !resolver.init || !resolver.uninit || !resolver.create_plugin || !resolver.destroy_plugin )
	{
	    WARNING_LOG << "failed to resolve all required methods in plugin";
	    return false;
	}
	
	return true;
    }
}

plugin_resolver::plugin_resolver( )
    : init( 0 )
    , uninit( 0 )
    , create_plugin( 0 )
    , destroy_plugin( 0 )
    , dl_handle( NULL )
    , dlopened( false )
{ }

plugin_resolver::~plugin_resolver( )
{
}

bool acquire_shared_symbols( plugin_item& item )
{
    typedef std::vector<wstring>::const_iterator const_iterator;
    
    for( const_iterator I = item.filename.begin( ); I != item.filename.end( ); ++I )
    {
	item.resolver.dl_handle = dlopen_( fs::path( to_string( *I ).c_str( ), fs::native ) );
	if( item.resolver.dl_handle )
	{
	    if( !resolve_plugin_symbols( item.resolver ) ) continue;
	    
	    item.resolver.dlopened = true;
	    break;
	}
    }

    if ( !item.resolver.dlopened )
    {
	WARNING_LOG << "failed to open: " << to_string( item.name );
    }
    
    return item.resolver.dlopened;
}

void release_shared_symbols( plugin_resolver& resolver )
{
    if( resolver.dlopened )
    {
	if( dlclose_( resolver.dl_handle ) )
	    resolver.dlopened = false;
    }
}
    
}

openplugin::~openplugin( )
{ }

} }
