/*
	module.c: modular code loader

	copyright 1995-2023 by the mpg123 project - free software under the terms of the LGPL 2.1
	see COPYING and AUTHORS files in distribution or http://mpg123.org
	initially written by Nicholas J Humfrey
*/

/* Need snprintf(). */
#define _DEFAULT_SOURCE
#define _BSD_SOURCE
#include "config.h"
#include "stringlists.h"
#include "../compat/compat.h"
#include <errno.h>

#include "module.h"
#include "../common/debug.h"

#ifndef USE_MODULES
#error This is a build without modules. Why am I here?
#endif

#define MODULE_SYMBOL_PREFIX "mpg123_"
#define MODULE_SYMBOL_SUFFIX "_module_info"

/* Windows code can convert these from UTF-8 (or ASCII, does not matter)
   to wide and then replace / by \. No need to define another list. */
static const char* modulesearch[] =
{
	 "../lib/mpg123"
	,"plugins"
	,"libout123/modules/.libs"
	,"libout123/modules"
	,"../libout123/modules/.libs"
	,"../libout123/modules"
};

static char *get_module_dir(int verbose, const char* bindir)
{
	char *moddir = NULL;
	char *defaultdir;
	/* First the environment override, then relative to bindir, then installation prefix. */
	defaultdir = INT123_compat_getenv("MPG123_MODDIR");
	if(defaultdir)
	{
		if(verbose > 1)
			fprintf(stderr, "Trying module directory from environment: %s\n", defaultdir);
		if(INT123_compat_isdir(defaultdir))
			moddir = defaultdir;
		else
			free(defaultdir);
	}
	else
	{
		if(bindir) /* Search relative to binary. */
		{
			size_t i;
			if(verbose > 1)
				fprintf(stderr, "Module dir search relative to: %s\n", bindir);
			for(i=0; i<sizeof(modulesearch)/sizeof(char*); ++i)
			{
				moddir = INT123_compat_catpath(bindir, modulesearch[i]);
				if(!moddir)
					continue;
				if(verbose > 1)
					fprintf(stderr, "Looking for module dir: %s\n", moddir);
				if(INT123_compat_isdir(moddir))
					break; /* found it! */
				else
				{
					free(moddir);
					moddir=NULL;
				}
			}
		}
		if(!moddir) /* Resort to installation prefix. */
		{
			if(INT123_compat_isdir(PKGLIBDIR))
			{
				if(verbose > 1)
					fprintf(stderr, "Using default module dir: %s\n", PKGLIBDIR);
				moddir = INT123_compat_strdup(PKGLIBDIR);
			}
		}
	}

	if(verbose > 1)
		fprintf(stderr, "Module dir: %s\n", moddir != NULL ? moddir : "<nil>");
	return moddir;
}

/* Open a module in given directory. */
mpg123_module_t* open_module_here( const char *dir, const char* type
,	const char* name, int verbose )
{
	void *handle = NULL;
	mpg123_module_t *module = NULL;
	char *module_file = NULL;
	size_t module_file_len = 0;
	char *module_symbol = NULL;
	size_t module_symbol_len = 0;
	char *module_path = NULL;

	/* Work out the path of the module to open */
	module_file_len = strlen(type) + 1 + strlen(name) + strlen(LT_MODULE_EXT) + 1;
	module_file = malloc(module_file_len);
	if(!module_file)
	{
		if(verbose > -1)
			error1( "Failed to allocate memory for module name: %s", INT123_strerror(errno) );
		return NULL;
	}
	snprintf(module_file, module_file_len, "%s_%s%s", type, name, LT_MODULE_EXT);
	module_path = INT123_compat_catpath(dir, module_file);
	free(module_file);
	if(!module_path)
	{
		if(verbose > -1)
			error("Failed to construct full path (out of memory?).");
		return NULL;
	}
	if(verbose > 1)
		fprintf(stderr, "Module path: %s\n", module_path );

	/* Open the module */
	handle = INT123_compat_dlopen(module_path);
	free(module_path);
	if (handle==NULL)
	{
		if(verbose > -1)
			error1("Failed to open module %s.", name);
		return NULL;
	}
	
	/* Work out the symbol name */
	module_symbol_len = strlen( MODULE_SYMBOL_PREFIX ) +
						strlen( type )  +
						strlen( MODULE_SYMBOL_SUFFIX ) + 1;
	module_symbol = malloc(module_symbol_len);
	if (module_symbol == NULL) {
		if(verbose > -1)
			error1( "Failed to allocate memory for module symbol: %s", INT123_strerror(errno) );
		return NULL;
	}
	snprintf( module_symbol, module_symbol_len, "%s%s%s", MODULE_SYMBOL_PREFIX, type, MODULE_SYMBOL_SUFFIX );
	debug1( "Module symbol: %s", module_symbol );
	
	/* Get the information structure from the module */
	module = (mpg123_module_t*)INT123_compat_dlsym(handle, module_symbol);
	free( module_symbol );
	if (module==NULL) {
		if(verbose > -1)
			error("Failed to get module symbol.");
		return NULL;
	}
	
	/* Check the API version */
	if (MPG123_MODULE_API_VERSION != module->api_version)
	{
		if(verbose > -1)
			error2( "API version of module does not match (got %i, expected %i).", module->api_version, MPG123_MODULE_API_VERSION);
		INT123_compat_dlclose(handle);
		return NULL;
	}

	/* Store handle in the data structure */
	module->handle = handle;
	return module;
}


/* Open a module, including directory search. */
mpg123_module_t* INT123_open_module( const char* type, const char* name, int verbose
,	const char* bindir )
{
	mpg123_module_t *module = NULL;
	char *moddir  = NULL;

	moddir = get_module_dir(verbose, bindir);
	if(!moddir)
	{
		if(verbose > -1)
			error("Failure getting module directory! (Perhaps set MPG123_MODDIR?)");
		return NULL;
	}

	module = open_module_here(moddir, type, name, verbose);

	free(moddir);
	return module;
}

void INT123_close_module( mpg123_module_t* module, int verbose )
{
	INT123_compat_dlclose(module->handle);
}

int INT123_list_modules( const char *type, char ***names, char ***descr, int verbose
,	const char* bindir )
{
	char *moddir  = NULL;
	int count = 0;
	struct compat_dir *dir;
	char *filename;

	debug1("verbose:%i", verbose);

	*names = NULL;
	*descr = NULL;

	moddir = get_module_dir(verbose, bindir);
	if(moddir == NULL)
	{
		if(verbose > -1)
			error("Failure getting module directory! (Perhaps set MPG123_MODDIR?)");
		return -1;
	}
	debug1("module dir: %s", moddir);

	/* Open the module directory */
	dir = INT123_compat_diropen(moddir);
	if (dir==NULL) {
		if(verbose > -1)
			error2("Failed to open the module directory (%s): %s\n"
			,	moddir, INT123_strerror(errno));
		free(moddir);
		return -1;
	}

	while((filename=INT123_compat_nextfile(dir)))
	{
		/* Pointers to the pieces. */
		char *module_name = NULL;
		char *module_type = NULL;
		char *uscore_pos = NULL;
		mpg123_module_t *module = NULL;
		char* ext;
		size_t name_len;

		/* Various checks as loop shortcuts, avoiding too much nesting. */
		debug1("checking entry: %s", filename);

		name_len = strlen(filename);
		if(name_len < strlen(LT_MODULE_EXT))
			goto list_modules_continue;
		ext = filename
		+	name_len
		-	strlen(LT_MODULE_EXT);
		if(strcmp(ext, LT_MODULE_EXT))
			goto list_modules_continue;
		debug("has suffix");

		/* Extract the module type and name */
		uscore_pos = strchr( filename, '_' );
		if(   uscore_pos==NULL
		  || (uscore_pos>=filename+name_len+1) )
		{
			debug("no underscore");
			goto list_modules_continue;
		}
		*uscore_pos = '\0';
		module_type = filename;
		module_name = uscore_pos+1;
		/* Only list modules of desired type. */
		if(strcmp(type, module_type))
		{
			debug("wrong type");
			goto list_modules_continue;
		}
		debug("has type");

		/* Extract the short name of the module */
		name_len -= uscore_pos - filename + 1;
		if(name_len <= strlen(LT_MODULE_EXT))
		{
			debug("name too short");
			goto list_modules_continue;
		}
		name_len -= strlen(LT_MODULE_EXT);
		module_name[name_len] = '\0';

		debug("opening module");
		/* Open the module
		   Yes, this re-builds the file name we chopped to pieces just now. */
		if((module=open_module_here(moddir, module_type, module_name, verbose)))
		{
			if( INT123_stringlists_add( names, descr
			,	module->name, module->description, &count) )
				if(verbose > -1)
					error("OOM");
			/* Close the module again */
			INT123_close_module(module, verbose);
		}
list_modules_continue:
		free(filename);
	}
	INT123_compat_dirclose(dir);
	return count;
}


