/** \file
 *
 *  Contains the Module class implementation.
 *
 *  Copyright (c) 2007,2008,2009 MBARI
 *  MBARI Proprietary Information.  All Rights Reserved
 *
 */

#include "ModuleLoader.h"

#include <cassert>
#include <cstddef>
#include <sys/types.h>
#include <dirent.h>
#include <dlfcn.h>

#include "component/Component.h"
#include "data/UniversalURI.h"
#include "io/FileInStream.h"
#include "module/Module.h"
#include "missionScript/ScriptComponent.h"

/// Constructor
ModuleLoader::ModuleLoader( ListOfLoadedModulePtrs& loadedModules )
    : logger_( "Module Loader" ),
      loadedModules_( loadedModules )
{}

/// Destructor
ModuleLoader::~ModuleLoader()
{}

/// Returns true if the indicated file exists
bool ModuleLoader::PathExists( const Str& filePath )
{
    bool exists = false;
    FileInStream fin( filePath.cStr() );
    if( fin.isReadable() )
    {
        exists = true;
    }
    return exists;
}

void ModuleLoader::loadModules( const Str& path )
{
    DIR* dir( opendir( path.cStr() ) );
    if( NULL == dir )
    {
        logger_.syslog( "Unable to open directory: " + path, Syslog::ERROR );
    }
    else
    {
        union // To use thread-safe readdir_r make sure dirent struct is big enough
        {
            struct dirent rent_;
            char b_[offsetof( struct dirent, d_name ) + NAME_MAX + 1];
        } di;
        struct dirent* entry;
        while( 0 == readdir_r( dir, &di.rent_, &entry ) && entry != NULL )
        {
            const char* name = entry->d_name;
            if( entry != NULL
                    && ( strstr( name, ".so" ) != NULL ) )
            {
                loadModule( path + name );
            }
        }
        closedir( dir );
    }

}

/// Loads the indicated module by file name root,
/// adds the components to the list of components and component map
/// registers its contained components' dataElements
LoadedModule* ModuleLoader::loadModule( Str path )
{
    LoadedModule * loadedModule = NULL;

    if( PathExists( path ) )
    {
        if( path.find( ".so" ) == path.size() - 3 && path.size() > 3 )
        {
            loadedModule = loadLibraryModule( path );
        }
    }
    else if( PathExists( path + ".so" ) )
    {
        loadedModule = loadLibraryModule( path + ".so" );
    }
    else if( PathExists( "Modules/" + path + ".so" ) )
    {
        loadedModule = loadLibraryModule( "Modules/" + path + ".so" );
    }

    if( NULL == loadedModule )
    {
        logger_.syslog( "Could not load the module at " + path, Syslog::ERROR );
        return NULL;
    }

    Module* module = loadedModule->getModule();
    module->loadComponents( logger_ );

    // Print something to the screen
    Str name = module->getName();
    logger_.syslog( "Loaded Module: " + module->getName() + " (" + module->getDescription() + ")" );

    if( loadedModule )
    {
        loadedModules_.push( loadedModule );
    }

    return loadedModule;
}

/// Loads the indicated compiled library module
LoadedModule* ModuleLoader::loadLibraryModule( Str path )
{

    // Load the library
    logger_.syslog( "Loading Module at " + path );
    void* lib = dlopen( path.cStr(), RTLD_LAZY );
    //printf( "Opened library at 0X%08x\n", (int)lib );
    if( !lib )
    {
        logger_.syslog( Str( "Cannot load library: " ) + dlerror(), Syslog::ERROR );
        return NULL;
    }

    create_module_t* create_module = ( create_module_t* ) dlsym( lib, "create_module" );
    if( !create_module )
    {
        logger_.syslog( Str( "Cannot load create_module: " ) + dlerror(), Syslog::ERROR );
        dlclose( lib );
        return NULL;
    }

    destroy_module_t* destroy_module = ( destroy_module_t* ) dlsym( lib, "destroy_module" );
    if( !destroy_module )
    {
        logger_.syslog( Str( "Cannot load destroy_module: " ) + dlerror(), Syslog::ERROR );
        dlclose( lib );
        return NULL;
    }

    // Create the object
    Module *module = create_module();

    if( module == NULL )
    {
        logger_.syslog( Str( "Running create_module returned NULL for " ) + path, Syslog::ERROR );
        dlclose( lib );
        return NULL;
    }

    return new LoadedModule( module, destroy_module, lib );

}
