/************************************************************************
 * UnrealIRCd - Unreal Internet Relay Chat Daemon - src/api-apicallback.c
 * (c) 2022- Bram Matthys and The UnrealIRCd Team
 * License: GPLv2 or later
 */

/** @file
 * @brief APICallback API
 */
#include "unrealircd.h"

/** This is for API callbacks like RegisterWebCallback.
 */

/** List of API Callbacks */
MODVAR APICallback *apicallbacks = NULL;

/* Forward declarations */
static void unload_apicallback_commit(APICallback *m);
#ifdef APICALLBACKDEBUG
static void print_apicallbacks(void);
#endif

/** Adds a new API Callback.
 * @param module The module which owns this API callback.
 * @param mreq   The details of the request such as the name and callback
 * @return Returns the handle to the API callback if successful, otherwise NULL.
 *         The module's error code contains specific information about the
 *         error.
 */
APICallback *APICallbackAdd(Module *module, APICallback *mreq)
{
	APICallback *m;
	ModuleObject *mobj;

	/* Some consistency checks to avoid a headache for module devs later on: */
	if (!mreq->callback_type)
	{
		unreal_log(ULOG_ERROR, "module", "API_CALLBACK_ADD_API_ERROR", NULL,
			   "APICallbackAdd() from module $module_name: "
			   "Missing required fields.",
			   log_data_string("module_name", module->header->name));
		abort();
	}

	m = APICallbackFind(mreq->name, mreq->callback_type);
	if (m)
	{
		if (m->unloaded)
		{
			m->unloaded = 0;
		} else {
			if (module)
				module->errorcode = MODERR_EXISTS;
			return NULL;
		}
	} else {
		/* New API callback */
		m = safe_alloc(sizeof(APICallback));
		safe_strdup(m->name, mreq->name);
		AddListItem(m, apicallbacks);
	}
	/* Add or update the following fields: */
	m->owner = module;
	m->callback_type = mreq->callback_type;
	memcpy(&m->callback, &mreq->callback, sizeof(m->callback));

	/* Add module object */
	mobj = safe_alloc(sizeof(ModuleObject));
	mobj->type = MOBJ_API_CALLBACK;
	mobj->object.apicallback = m;
	AddListItem(mobj, module->objects);
	module->errorcode = MODERR_NOERROR;

#ifdef APICALLBACKDEBUG
	unreal_log(ULOG_DEBUG, "module", "API_CALLBACK_DEBUG", NULL, "APICallbackAdd()");
	print_apicallbacks();
#endif
	return m;
}

/** Returns the API callback for the given name and callback type.
 * @param name The method to search for.
 * @param callback_type To which callback_type this belongs (scope)
 * @return Returns the handle to the API callback,
 *         or NULL if not found.
 */
APICallback *APICallbackFind(const char *name, APICallbackType callback_type)
{
	APICallback *m;

#ifdef APICALLBACKDEBUG
	unreal_log(ULOG_DEBUG, "module", "API_CALLBACK_DEBUG", NULL, "APICallbackFind()");
	print_apicallbacks();
#endif
	for (m = apicallbacks; m; m = m->next)
	{
		if ((m->callback_type == callback_type) &&
		    !strcasecmp(name, m->name))
		{
			return m;
		}
	}
	return NULL;
}

/** Remove the specified API callback - modules should not call this.
 * This is done automatically for modules on unload, so is only called internally.
 * @param m The API Callback to remove.
 */
void APICallbackDel(APICallback *m)
{
	if (m->owner)
	{
		ModuleObject *mobj;
		for (mobj = m->owner->objects; mobj; mobj = mobj->next)
		{
			if (mobj->type == MOBJ_API_CALLBACK && mobj->object.apicallback == m)
			{
				DelListItem(mobj, m->owner->objects);
				safe_free(mobj);
				break;
			}
		}
		m->owner = NULL;
	}

	if (loop.rehashing)
		m->unloaded = 1;
	else
		unload_apicallback_commit(m);
}

/** @} */

static void unload_apicallback_commit(APICallback *m)
{
	/* This is an unusual operation, I think we should log it. */
	unreal_log(ULOG_INFO, "module", "UNLOAD_API_CALLBACK", NULL,
	           "Unloading API callback for '$object_name'",
	           log_data_string("object_name", m->name));

	/* Destroy the object */
	DelListItem(m, apicallbacks);
	safe_free(m->name);
	safe_free(m);
}

void unload_all_unused_apicallbacks(void)
{
	APICallback *m, *m_next;

#ifdef APICALLBACKDEBUG
	unreal_log(ULOG_DEBUG, "module", "API_CALLBACK_DEBUG", NULL, "unload_all_unused_apicallbacks() BEFORE");
	print_apicallbacks();
#endif
	for (m = apicallbacks; m; m = m_next)
	{
		m_next = m->next;
		if (m->unloaded)
			unload_apicallback_commit(m);
	}
#ifdef APICALLBACKDEBUG
	unreal_log(ULOG_DEBUG, "module", "API_CALLBACK_DEBUG", NULL, "unload_all_unused_apicallbacks() AFTER");
	print_apicallbacks();
#endif
}

#ifdef APICALLBACKDEBUG
void print_apicallbacks(void)
{
	APICallback *m, *m_next;

	unreal_log(ULOG_DEBUG, "module", "API_CALLBACK_LIST", NULL, "----");
	for (m = apicallbacks; m; m = m_next)
	{
		m_next = m->next;
		unreal_log(ULOG_DEBUG, "module", "API_CALLBACK_LIST", NULL,
			"$name ($deleted)",
			log_data_string("name", m->name),
			log_data_string("deleted", m->unloaded ? "deleted" : ""));
	}
	unreal_log(ULOG_DEBUG, "module", "API_CALLBACK_LIST", NULL, "----");
}
#endif
