/*
   (c) Copyright 2001-2008  The world wide DirectFB Open Source Community (directfb.org)
   (c) Copyright 2000-2004  Convergence (integrated media) GmbH

   All rights reserved.

   Written by Denis Oliver Kropp <dok@directfb.org>,
              Andreas Hundt <andi@fischlustig.de>,
              Sven Neumann <neo@directfb.org>,
              Ville Syrjälä <syrjala@sci.fi> and
              Claudio Ciccani <klan@users.sf.net>.

   This library is free software; you can redistribute it and/or
   modify it under the terms of the GNU Lesser General Public
   License as published by the Free Software Foundation; either
   version 2 of the License, or (at your option) any later version.

   This library 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 for more details.

   You should have received a copy of the GNU Lesser General Public
   License along with this library; if not, write to the
   Free Software Foundation, Inc., 59 Temple Place - Suite 330,
   Boston, MA 02111-1307, USA.
*/

#include <config.h>

#include <sys/types.h>
#include <alloca.h>
#include <dirent.h>
#include <string.h>
#include <stdio.h>

#include <direct/conf.h>
#include <direct/debug.h>
#include <direct/mem.h>
#include <direct/messages.h>
#include <direct/modules.h>

#ifdef PIC
#define DYNAMIC_LINKING
#endif

#ifdef DYNAMIC_LINKING
#include <dlfcn.h>
#endif

D_DEBUG_DOMAIN( Direct_Modules, "Direct/Modules", "Module loading and registration" );

/******************************************************************************/

#ifdef DYNAMIC_LINKING

static DirectModuleEntry *lookup_by_name( const DirectModuleDir *directory,
                                          const char            *name );

static DirectModuleEntry *lookup_by_file( const DirectModuleDir *directory,
                                          const char            *file );

static void *open_module  ( DirectModuleEntry *module );
static bool  load_module  ( DirectModuleEntry *module );
static void  unload_module( DirectModuleEntry *module );

#endif

/******************************************************************************/

static int
suppress_module (const char *name)
{
     int i = 0;

     if (!direct_config || !direct_config->disable_module)
          return 0;

     while (direct_config->disable_module[i]) {
          if (strcmp (direct_config->disable_module[i], name) == 0) {
               D_INFO( "Direct/Modules: suppress module '%s'\n", direct_config->disable_module[i] );
               return 1;
          }

          i++;
     }

     return 0;
}

void
direct_modules_register( DirectModuleDir *directory,
                         unsigned int     abi_version,
                         const char      *name,
                         const void      *funcs )
{
     DirectModuleEntry *entry;

     D_ASSERT( directory != NULL );
     D_ASSERT( name != NULL );
     D_ASSERT( funcs != NULL );

     D_DEBUG_AT( Direct_Modules, "Registering '%s' ('%s')...\n", name, directory->path );

#ifdef DYNAMIC_LINKING
     if ((entry = lookup_by_name( directory, name )) != NULL) {
          D_MAGIC_ASSERT( entry, DirectModuleEntry );

          entry->loaded = true;
          entry->funcs  = funcs;

          return;
     }
#endif

     if (directory->loading) {
          entry = directory->loading;
          D_MAGIC_ASSERT( entry, DirectModuleEntry );

          directory->loading = NULL;
     }
     else {
          entry = D_CALLOC( 1, sizeof(DirectModuleEntry) );
          if (!entry) {
               D_OOM();
               return;
          }

          D_MAGIC_SET( entry, DirectModuleEntry );
     }

     entry->directory = directory;
     entry->loaded    = true;
     entry->name      = D_STRDUP( name );
     entry->funcs     = funcs;

     entry->disabled  = suppress_module( name );

     if (abi_version != directory->abi_version) {
          D_ERROR( "Direct/Modules: ABI version of '%s' (%d) does not match %d!\n",
                   entry->file ? entry->file : entry->name,
                   abi_version, directory->abi_version );

          entry->disabled = true;
     }

     direct_list_prepend( &directory->entries, &entry->link );

     D_DEBUG_AT( Direct_Modules, "...registered.\n" );
}

void
direct_modules_unregister( DirectModuleDir *directory,
                           const char      *name )
{
     DirectModuleEntry *entry;

     D_DEBUG_AT( Direct_Modules, "Unregistering '%s' ('%s')...\n", name, directory->path );

#ifdef DYNAMIC_LINKING
     entry = lookup_by_name( directory, name );
     if (!entry) {
          D_DEBUG_AT( Direct_Modules, " '%s' already unregistered!\n", name );
          return;
     }
     else
     {
         D_MAGIC_ASSERT( entry, DirectModuleEntry );

         D_FREE( entry->name );

         direct_list_remove( &directory->entries, &entry->link );

         D_MAGIC_CLEAR( entry );

         D_FREE( entry );
     }
#endif

     D_DEBUG_AT( Direct_Modules, "...unregistered.\n" );
}

int
direct_modules_explore_directory( DirectModuleDir *directory )
{
#ifdef DYNAMIC_LINKING
     int            dir_len;
     DIR           *dir;
     struct dirent *entry = NULL;
     struct dirent  tmp;
     int            count = 0;
     const char    *pathfront = "";
     const char    *path;
     char          *buf;

     D_ASSERT( directory != NULL );
     D_ASSERT( directory->path != NULL );

     D_DEBUG_AT( Direct_Modules, "%s( '%s' )\n", __FUNCTION__, directory->path );

     path = directory->path;

     if(path[0]!='/') {
          pathfront = direct_config->module_dir;
          if(!pathfront)
               pathfront = MODULEDIR;
     }

     buf = alloca( strlen(pathfront) + 1 + strlen(path) + 1 ); /* pre, slash, post, 0 */
     sprintf( buf, "%s%s%s", pathfront, ( pathfront && path && (path[0] != '/') && (pathfront[strlen(pathfront)-1] != '/') ) ? "/" : "", path );

     dir_len = strlen( buf );
     dir     = opendir( buf );

     if (!dir) {
          D_DEBUG_AT( Direct_Modules, "  -> ERROR opening directory: %s!\n", strerror(errno) );
          return 0;
     }

     while (readdir_r( dir, &tmp, &entry ) == 0 && entry) {
          void              *handle;
          DirectModuleEntry *module;
          int                entry_len = strlen(entry->d_name);

          if (entry_len < 4 ||
              entry->d_name[entry_len-1] != 'o' ||
              entry->d_name[entry_len-2] != 's')
               continue;

          if (lookup_by_file( directory, entry->d_name ))
               continue;


          module = D_CALLOC( 1, sizeof(DirectModuleEntry) );
          if (!module)
               continue;

          D_MAGIC_SET( module, DirectModuleEntry );

          module->directory = directory;
          module->dynamic   = true;
          module->file      = D_STRDUP( entry->d_name );


          directory->loading = module;

          if ((handle = open_module( module )) != NULL) {
               if (!module->loaded) {
                    int    len;
                    void (*func)( void );

                    D_ERROR( "Direct/Modules: Module '%s' did not register itself after loading! "
                             "Trying default module constructor...\n", entry->d_name );

                    len = strlen( entry->d_name );

                    entry->d_name[len-3] = 0;

                    func = dlsym( handle, entry->d_name + 3 );
                    if (func) {
                         func();

                         if (!module->loaded) {
                              D_ERROR( "Direct/Modules: ... even did not register after "
                                       "explicitly calling the module constructor!\n" );
                         }
                    }
                    else {
                         D_ERROR( "Direct/Modules: ... default contructor not found!\n" );
                    }

                    if (!module->loaded) {
                         module->disabled = true;

                         direct_list_prepend( &directory->entries,
                                              &module->link );
                    }
               }

               if (module->disabled) {
                    dlclose( handle );

                    module->loaded = false;
               }
               else {
                    module->handle = handle;

                    count++;
               }
          }
          else {
               module->disabled = true;

               direct_list_prepend( &directory->entries, &module->link );
          }

          directory->loading = NULL;
     }

     closedir( dir );

     return count;
#else
     return 0;
#endif
}

const void *
direct_module_ref( DirectModuleEntry *module )
{
     D_MAGIC_ASSERT( module, DirectModuleEntry );

     if (module->disabled)
          return NULL;

#ifdef DYNAMIC_LINKING
     if (!module->loaded && !load_module( module ))
          return NULL;
#endif

     D_DEBUG_AT( Direct_Modules, " module:'%s' refcount will be %d...\n", module->name, (module->refs+1));
     module->refs++;

     return module->funcs;
}

void
direct_module_unref( DirectModuleEntry *module )
{
     D_MAGIC_ASSERT( module, DirectModuleEntry );
     D_ASSERT( module->refs > 0 );

     D_DEBUG_AT( Direct_Modules, " module:'%s' refcount will be %d...\n", module->name, (module->refs-1));

     if (--module->refs)
          return;

#ifdef DYNAMIC_LINKING
     if (module->dynamic)
     {
          DirectModuleEntry *entry;

          unload_module( module );
          /* The above method calls dlclose, this should call the method
             specified with the -fini symbol when creating the .so file.  However
             dlclose appears to work and sometimes not call this function?????
             Therefore after calling unload we lookup the module to see if
             it really has been cleaned up (called module_unregister) if it has
             it will not be in the list and we carry on.  If it failed to call the
             method then we explicity call direct_modules_unregister and this
             makes sure we have cleaned up and allows us to exit and init directfb from wothin
             a single executable.*/
          entry = lookup_by_name( module->directory, module->name );
          if(entry)
          {
               direct_modules_unregister( module->directory, module->name );
          }
     }
#endif
}

/******************************************************************************/

#ifdef DYNAMIC_LINKING

static DirectModuleEntry *
lookup_by_name( const DirectModuleDir *directory,
                const char            *name )
{
     DirectLink *l;

     D_ASSERT( directory != NULL );
     D_ASSERT( name != NULL );

     direct_list_foreach (l, directory->entries) {
          DirectModuleEntry *entry = (DirectModuleEntry*) l;

          D_MAGIC_ASSERT( entry, DirectModuleEntry );

          if (!entry->name)
               continue;

          if (!strcmp( entry->name, name ))
               return entry;
     }

     return NULL;
}

static DirectModuleEntry *
lookup_by_file( const DirectModuleDir *directory,
                const char            *file )
{
     DirectLink *l;

     D_ASSERT( directory != NULL );
     D_ASSERT( file != NULL );

     direct_list_foreach (l, directory->entries) {
          DirectModuleEntry *entry = (DirectModuleEntry*) l;

          D_MAGIC_ASSERT( entry, DirectModuleEntry );

          if (!entry->file)
               continue;

          if (!strcmp( entry->file, file ))
               return entry;
     }

     return NULL;
}

static bool
load_module( DirectModuleEntry *module )
{
     D_MAGIC_ASSERT( module, DirectModuleEntry );
     D_ASSERT( module->dynamic == true );
     D_ASSERT( module->file != NULL );
     D_ASSERT( module->loaded == false );
     D_ASSERT( module->disabled == false );

     module->handle = open_module( module );

     return module->loaded;
}

static void
unload_module( DirectModuleEntry *module )
{
     D_MAGIC_ASSERT( module, DirectModuleEntry );
     D_ASSERT( module->dynamic == true );
     D_ASSERT( module->handle != NULL );
     D_ASSERT( module->loaded == true );

     D_DEBUG_AT( Direct_Modules, "UnLoading '%s'...\n", module->name );
     dlclose( module->handle );

     module->handle = NULL;
     module->loaded = false;
}

static void *
open_module( DirectModuleEntry *module )
{
     DirectModuleDir *directory;
     void            *handle;
     char            *pathfront;
     const char      *path;

     D_MAGIC_ASSERT( module, DirectModuleEntry );

     directory = module->directory;
     path      = directory->path;
     pathfront = "";

     if(path[0]!='/') {
          pathfront = direct_config->module_dir;
          if(!pathfront)
               pathfront = MODULEDIR;
     }

     char buf[ strlen(pathfront) + 1 + strlen(path) + 1 + strlen(module->file) + 1 ];
     sprintf( buf, "%s%s%s/%s", pathfront, ( pathfront && path && (path[0] != '/') && (pathfront[strlen(pathfront)-1] != '/') ) ? "/" : "", path, module->file );

     D_DEBUG_AT( Direct_Modules, "Loading '%s'...\n", buf );

     handle = dlopen( buf, RTLD_NOW );
     if (!handle)
          D_DLERROR( "Direct/Modules: Unable to dlopen `%s'!\n", buf );

     return handle;
}

#endif

