/**
 * @file mod.c  Loadable modules
 *
 * Copyright (C) 2010 Creytiv.com
 */
#include <sys/types.h>
#include <string.h>
#include <re_types.h>
#include <re_fmt.h>
#include <re_mem.h>
#include <re_mbuf.h>
#include <re_list.h>
#include <re_mod.h>
#include "mod_internal.h"


#define DEBUG_MODULE "mod"
#define DEBUG_LEVEL 5
#include <re_dbg.h>


/** Defines a loadable module */
struct mod {
	struct le le;                 /**< Linked list element */
	void *h;                      /**< Module handler      */
	const struct mod_export *me;  /**< Module exports      */
};


static struct list modl;  /* struct mod */


/**
 * Initialise module loading
 */
void mod_init(void)
{
	list_init(&modl);
}


/**
 * Unload all modules
 */
void mod_close(void)
{
	list_flush(&modl);
}


static void mod_destructor(void *data)
{
	struct mod *m = data;
	const struct mod_export *me = m->me;
	int err;

	if (me && me->close && (err = me->close())) {
		DEBUG_NOTICE("close: error (%m)\n", err);
	}

	list_unlink(&m->le);

	_mod_close(m->h);
}


/**
 * Find a module by name in the list of loaded modules
 *
 * @param name Name of module to find
 *
 * @return Module if found, NULL if not found
 */
struct mod *mod_find(const char *name)
{
	struct le *le;
	struct pl x;

	if (!name)
		return NULL;

	if (re_regex(name, strlen(name), "[/]*[^./]+" MOD_EXT, NULL, &x))
		return NULL;

	for (le = modl.head; le; le = le->next) {
		struct mod *m = le->data;

		if (0 == pl_strcasecmp(&x, m->me->name))
			return m;
	}

	return NULL;
}


/**
 * Load and initialise a loadable module by name
 *
 * @param mp   Pointer to allocated module object
 * @param name Name of loadable module
 *
 * @return 0 if success, otherwise errorcode
 */
int mod_load(struct mod **mp, const char *name)
{
	struct mod *m;
	int err = 0;

	if (!mp || !name)
		return EINVAL;

	/* check if already loaded */
	m = mod_find(name);
	if (m) {
		DEBUG_NOTICE("module already loaded: %s\n", name);
		return EALREADY;
	}

	m = mem_zalloc(sizeof(*m), mod_destructor);
	if (!m)
		return ENOMEM;

	list_append(&modl, &m->le, m);

	m->h = _mod_open(name);
	if (!m->h) {
		err = ENOENT;
		goto out;
	}

	m->me = _mod_sym(m->h, "exports");
	if (!m->me) {
		err = ELIBBAD;
		goto out;
	}

	if (m->me->init && (err = m->me->init()))
		goto out;

 out:
	if (err)
		mem_deref(m);
	else
		*mp = m;

	return err;
}


/**
 * Add and initialise an external module with exports
 *
 * @param mp   Pointer to allocated module object
 * @param me   Module exports
 *
 * @return 0 if success, otherwise errorcode
 */
int mod_add(struct mod **mp, const struct mod_export *me)
{
	struct mod *m;
	int err = 0;

	if (!mp || !me)
		return EINVAL;

	/* check if already loaded */
	m = mod_find(me->name);
	if (m) {
		DEBUG_NOTICE("module already loaded: %s\n", me->name);
		return EALREADY;
	}

	m = mem_zalloc(sizeof(*m), mod_destructor);
	if (!m)
		return ENOMEM;

	list_append(&modl, &m->le, m);

	m->me = me;

	if (m->me->init)
		err = m->me->init();

	if (err)
		mem_deref(m);
	else
		*mp = m;

	return err;
}


/**
 * Get module export from a loadable module
 *
 * @param m Loadable module
 *
 * @return Module export
 */
const struct mod_export *mod_export(const struct mod *m)
{
	return m ? m->me : NULL;
}


/**
 * Get the list of loaded modules
 *
 * @return Module list
 */
struct list *mod_list(void)
{
	return &modl;
}


/**
 * Debug loadable modules
 *
 * @param pf     Print handler for debug output
 * @param unused Unused parameter
 *
 * @return 0 if success, otherwise errorcode
 */
int mod_debug(struct re_printf *pf, void *unused)
{
	struct le *le;
	int err;

	(void)unused;

	err = re_hprintf(pf, "\n--- Modules (%u) ---\n", list_count(&modl));

	for (le = modl.head; le && !err; le = le->next) {
		const struct mod *m = le->data;
		const struct mod_export *me = m->me;

		err = re_hprintf(pf, " %16s type=%-12s ref=%u\n",
				 me->name, me->type, mem_nrefs(m));
	}

	err |= re_hprintf(pf, "\n");

	return err;
}
