/************************************************************************
 *   UnrealIRCd - Unreal Internet Relay Chat Daemon - src/api-mtag.c
 *   (c) 2019- Bram Matthys and The UnrealIRCd team
 *
 *   See file AUTHORS in IRC package for additional names of
 *   the programmers. 
 *
 *   This program is free software; you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation; either version 1, or (at your option)
 *   any later version.
 *
 *   This program 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 General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with this program; if not, write to the Free Software
 *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

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

/** This is the message tags API (message-tags).
 * For an overview of message tags in general (not the API)
 * see https://www.unrealircd.org/docs/Message_tags
 * @defgroup MessagetagAPI Message tag API
 * @{
 */

/** List of message tag handlers */
MODVAR MessageTagHandler *mtaghandlers = NULL;

/* Forward declarations */
static void unload_mtag_handler_commit(MessageTagHandler *m);

/** Adds a new message tag handler.
 * @param module The module which owns this message-tag handler.
 * @param mreq   The details of the request such as which message tag, the handler, etc.
 * @return Returns the handle to the new token if successful, otherwise NULL.
 *         The module's error code contains specific information about the
 *         error.
 */
MessageTagHandler *MessageTagHandlerAdd(Module *module, MessageTagHandlerInfo *mreq)
{
	MessageTagHandler *m;

	/* Some consistency checks to avoid a headache for module devs later on: */
	if ((mreq->flags & MTAG_HANDLER_FLAGS_NO_CAP_NEEDED) && mreq->clicap_handler)
	{
		unreal_log(ULOG_ERROR, "module", "MESSAGETAGHANDLERADD_API_ERROR", NULL,
			   "MessageTagHandlerAdd() from module $module_name: "
			   ".flags is set to MTAG_HANDLER_FLAGS_NO_CAP_NEEDED "
			   "but a .clicap_handler is passed as well. These options are mutually "
			   "exclusive, choose one or the other.",
			   log_data_string("module_name", module->header->name));
		abort();
	} else if (!(mreq->flags & MTAG_HANDLER_FLAGS_NO_CAP_NEEDED) && !mreq->clicap_handler)
	{
		unreal_log(ULOG_ERROR, "module", "MESSAGETAGHANDLERADD_API_ERROR", NULL,
			   "MessageTagHandlerAdd() from module $module_name: "
			   "no .clicap_handler is passed. If the "
		           "message tag really does not require a cap then you must "
		           "set .flags to MTAG_HANDLER_FLAGS_NO_CAP_NEEDED",
		           log_data_string("module_name", module->header->name));
		abort();
	}

	m = MessageTagHandlerFind(mreq->name);
	if (m)
	{
		if (m->unloaded)
		{
			m->unloaded = 0;
		} else {
			if (module)
				module->errorcode = MODERR_EXISTS;
			return NULL;
		}
	} else {
		/* New message tag handler */
		m = safe_alloc(sizeof(MessageTagHandler));
		safe_strdup(m->name, mreq->name);
		AddListItem(m, mtaghandlers);
	}
	/* Add or update the following fields: */
	m->owner = module;
	m->flags = mreq->flags;
	m->is_ok = mreq->is_ok;
	m->should_send_to_client = mreq->should_send_to_client;
	m->clicap_handler = mreq->clicap_handler;

	/* Update reverse dependency (if any) */
	if (m->clicap_handler)
		m->clicap_handler->mtag_handler = m;

	if (module)
	{
		ModuleObject *mobj = safe_alloc(sizeof(ModuleObject));
		mobj->type = MOBJ_MTAG;
		mobj->object.mtag = m;
		AddListItem(mobj, module->objects);
		module->errorcode = MODERR_NOERROR;
	}

	return m;
}

/** Returns the message tag handler for the given name.
 * @param name The message-tag name to search for.
 * @return Returns the handle to the message tag handler,
 *         or NULL if not found.
 */
MessageTagHandler *MessageTagHandlerFind(const char *name)
{
	MessageTagHandler *m;

	for (m = mtaghandlers; m; m = m->next)
	{
		if (!strcasecmp(name, m->name))
			return m;
	}
	return NULL;
}

/** Remove the specified message tag handler - modules should not call this.
 * This is done automatically for modules on unload, so is only called internally.
 * @param m The message tag handler to remove.
 */
void MessageTagHandlerDel(MessageTagHandler *m)
{
	if (m->owner)
	{
		ModuleObject *mobj;
		for (mobj = m->owner->objects; mobj; mobj = mobj->next) {
			if (mobj->type == MOBJ_MTAG && mobj->object.mtag == m)
			{
				DelListItem(mobj, m->owner->objects);
				safe_free(mobj);
				break;
			}
		}
		m->owner = NULL;
	}

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

/** @} */

static void unload_mtag_handler_commit(MessageTagHandler *m)
{
	/* This is an unusual operation, I think we should log it. */
	unreal_log(ULOG_INFO, "module", "UNLOAD_MESSAGE_TAG", NULL,
	           "Unloading message-tag handler for '$token'",
	           log_data_string("token", m->name));

	/* Remove reverse dependency, if any */
	if (m->clicap_handler)
		m->clicap_handler->mtag_handler = NULL;

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

void unload_all_unused_mtag_handlers(void)
{
	MessageTagHandler *m, *m_next;

	for (m = mtaghandlers; m; m = m_next)
	{
		m_next = m->next;
		if (m->unloaded)
			unload_mtag_handler_commit(m);
	}
}
