/************************************************************************
 *   IRC - Internet Relay Chat, src/api-channelmode.c
 *   (C) 2003-2007 Bram Matthys (Syzop) 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 The channel mode API used by modules.
 */

#include "unrealircd.h"

/** This is the extended channel mode API,
 * see also https://www.unrealircd.org/docs/Dev:Channel_Mode_API
 * for more information.
 * @defgroup ChannelModeAPI Channel mode API
 * @{
 */

/** List of all channel modes, their handlers, etc */
Cmode *channelmodes = NULL;

/** @} */

/** Channel parameter to slot# mapping - used by GETPARAMSLOT() macro */
MODVAR unsigned char param_to_slot_mapping[256];
/** Extended channel modes in use - used by ISUPPORT/005 numeric only */
char extchmstr[4][64];

/* Private functions (forward declaration) and variables */
static void make_cmodestr(void);
static char previous_chanmodes[256];
static char previous_prefix[256];
static Cmode *ParamTable[MAXPARAMMODES+1];
static void unload_extcmode_commit(Cmode *cmode);

/** Create the strings that are used for CHANMODES=a,b,c,d in numeric 005 */
void make_extcmodestr()
{
	char *p;
	Cmode *cm;
	int i;
	
	extchmstr[0][0] = extchmstr[1][0] = extchmstr[2][0] = extchmstr[3][0] = '\0';
	
	/* type 1: lists (like b/e) */
	/* [NOT IMPLEMENTED IN EXTCMODES] */

	/* type 2: 1 par to set/unset (has .unset_with_param) */
	p = extchmstr[1];
	for (cm=channelmodes; cm; cm = cm->next)
		if (cm->paracount && cm->letter && cm->unset_with_param && (cm->type != CMODE_MEMBER))
			*p++ = cm->letter;
	*p = '\0';

	/* type 3: 1 param to set, 0 params to unset (does not have .unset_with_param) */
	p = extchmstr[2];
	for (cm=channelmodes; cm; cm = cm->next)
		if (cm->paracount && cm->letter && !cm->unset_with_param)
			*p++ = cm->letter;
	*p = '\0';
	
	/* type 4: paramless modes */
	p = extchmstr[3];
	for (cm=channelmodes; cm; cm = cm->next)
		if (!cm->paracount && cm->letter)
			*p++ = cm->letter;
	*p = '\0';
}

/** Create the string that is used in numeric 004 */
static void make_cmodestr(void)
{
	Cmode *cm;
	char *p = &cmodestring[0];
	CoreChannelModeTable *tab = &corechannelmodetable[0];
	int i;
	while (tab->mode != 0x0)
	{
		*p = tab->flag;
		p++;
		tab++;
	}
	for (cm=channelmodes; cm; cm = cm->next)
		if (cm->letter)
			*p++ = cm->letter;
	*p = '\0';
}

/** Check for changes - if any are detected, we broadcast the change */
void extcmodes_check_for_changed_channel_modes(void)
{
	char chanmodes[256];
	ISupport *isup;

	//sort_cmodes();
	make_cmodestr();
	make_extcmodestr();

	snprintf(chanmodes, sizeof(chanmodes), "%s%s", CHPAR1, EXPAR1);
	safe_strdup(me.server->features.chanmodes[0], chanmodes);
	snprintf(chanmodes, sizeof(chanmodes), "%s", EXPAR2);
	safe_strdup(me.server->features.chanmodes[1], chanmodes);
	snprintf(chanmodes, sizeof(chanmodes), "%s", EXPAR3);
	safe_strdup(me.server->features.chanmodes[2], chanmodes);
	snprintf(chanmodes, sizeof(chanmodes), "%s", EXPAR4);
	safe_strdup(me.server->features.chanmodes[3], chanmodes);

	ircsnprintf(chanmodes, sizeof(chanmodes), "%s,%s,%s,%s",
	            me.server->features.chanmodes[0],
	            me.server->features.chanmodes[1],
	            me.server->features.chanmodes[2],
	            me.server->features.chanmodes[3]);

	isup = ISupportFind("CHANMODES");
	if (!isup)
	{
		strlcpy(previous_chanmodes, chanmodes, sizeof(previous_chanmodes));
		return; /* not booted yet. then we are done here. */
	}
	
	ISupportSetValue(isup, chanmodes);
	
	if (*previous_chanmodes && strcmp(chanmodes, previous_chanmodes))
	{
		unreal_log(ULOG_INFO, "mode", "CHANNEL_MODES_CHANGED", NULL,
		           "Channel modes changed at runtime: $old_channel_modes -> $new_channel_modes",
		           log_data_string("old_channel_modes", previous_chanmodes),
		           log_data_string("new_channel_modes", chanmodes));
		/* Broadcast change to all (locally connected) servers */
		sendto_server(NULL, 0, 0, NULL, "PROTOCTL CHANMODES=%s", chanmodes);
	}

	strlcpy(previous_chanmodes, chanmodes, sizeof(previous_chanmodes));
}

void make_prefix(char **isupport_prefix, char **isupport_statusmsg)
{
	static char prefix[256];
	static char prefix_prefix[256];
	char prefix_modes[256];
	int rank[256];
	Cmode *cm;
	int n;

	*prefix = *prefix_prefix = *prefix_modes = '\0';

	for (n=0, cm=channelmodes; cm && n < ARRAY_SIZEOF(rank)-1; cm = cm->next)
	{
		if ((cm->type == CMODE_MEMBER) && cm->letter)
		{
			strlcat_letter(prefix_prefix, cm->prefix, sizeof(prefix_prefix));
			strlcat_letter(prefix_modes, cm->letter, sizeof(prefix_modes));
			rank[n] = cm->rank;
			n++;
		}
	}

	if (*prefix_prefix)
	{
		int i, j;
		/* Now sort the damn thing */
		for (i=0; i < n; i++)
		{
			for (j=i+1; j < n; j++)
			{
				if (rank[i] < rank[j])
				{
					/* swap */
					char save;
					int save_rank;
					save = prefix_prefix[i];
					prefix_prefix[i] = prefix_prefix[j];
					prefix_prefix[j] = save;
					save = prefix_modes[i];
					prefix_modes[i] = prefix_modes[j];
					prefix_modes[j] = save;
					save_rank = rank[i];
					rank[i] = rank[j];
					rank[j] = save_rank;
				}
			}
		}
		snprintf(prefix, sizeof(prefix), "(%s)%s", prefix_modes, prefix_prefix);
	}

	*isupport_prefix = prefix;
	*isupport_statusmsg = prefix_prefix;
}

void extcmodes_check_for_changed_prefixes(void)
{
	ISupport *isup;
	char *prefix, *statusmsg;

	make_prefix(&prefix, &statusmsg);
	ISupportSet(NULL, "PREFIX", prefix);
	ISupportSet(NULL, "STATUSMSG", statusmsg);

	if (*previous_prefix && strcmp(prefix, previous_prefix))
	{
		unreal_log(ULOG_INFO, "mode", "PREFIX_CHANGED", NULL,
		           "Prefix changed at runtime: $old_prefix -> $new_prefix",
		           log_data_string("old_prefix", previous_prefix),
		           log_data_string("new_prefix", prefix));
		/* Broadcast change to all (locally connected) servers */
		sendto_server(NULL, 0, 0, NULL, "PROTOCTL PREFIX=%s", prefix);
	}

	strlcpy(previous_prefix, prefix, sizeof(previous_prefix));
}

/** Check for changes - if any are detected, we broadcast the change */
void extcmodes_check_for_changes(void)
{
	extcmodes_check_for_changed_channel_modes();
	extcmodes_check_for_changed_prefixes();
}

/** Initialize the extended channel modes system */
void extcmode_init(void)
{
	memset(&extchmstr, 0, sizeof(extchmstr));
	memset(&param_to_slot_mapping, 0, sizeof(param_to_slot_mapping));
	*previous_chanmodes = '\0';
	*previous_prefix = '\0';
}

/** Update letter->slot mapping and slot->handler mapping */
void extcmode_para_addslot(Cmode *cm, int slot)
{
	if ((slot < 0) || (slot > MAXPARAMMODES))
		abort();
	cm->param_slot = slot;
	ParamTable[slot] = cm;
	param_to_slot_mapping[cm->letter] = slot;
}

/** Update letter->slot mapping and slot->handler mapping */
void extcmode_para_delslot(Cmode *cm, int slot)
{
	if ((slot < 0) || (slot > MAXPARAMMODES))
		abort();
	ParamTable[slot] = NULL;
	param_to_slot_mapping[cm->letter] = 0;
}

void channelmode_add_sorted(Cmode *n)
{
	Cmode *m;

	if (channelmodes == NULL)
	{
		channelmodes = n;
		return;
	}

	for (m = channelmodes; m; m = m->next)
	{
		if (m->letter == '\0')
			abort();
		if (sort_character_lowercase_before_uppercase(n->letter, m->letter))
		{
			/* Insert us before */
			if (m->prev)
				m->prev->next = n;
			else
				channelmodes = n; /* new head */
			n->prev = m->prev;

			n->next = m;
			m->prev = n;
			return;
		}
		if (!m->next)
		{
			/* Append us at end */
			m->next = n;
			n->prev = m;
			return;
		}
	}
}

/** @defgroup ChannelModeAPI Channel mode API
 * @{
 */

/** Register a new channel mode (Channel mode API).
 * @param module	The module requesting this channel mode (usually: modinfo->handle)
 * @param req		Details of the channel mode request
 * @param mode		Store the mode value (bit) here on success
 * @returns the newly created channel mode, or NULL in case of error.
 */
Cmode *CmodeAdd(Module *module, CmodeInfo req, Cmode_t *mode)
{
	int paraslot = -1;
	int existing = 0;
	Cmode *cm;

	for (cm=channelmodes; cm; cm = cm->next)
	{
		if (cm->letter == req.letter)
		{
			if (cm->unloaded)
			{
				cm->unloaded = 0;
				existing = 1;
				break;
			} else {
				if (module)
					module->errorcode = MODERR_EXISTS;
				return NULL;
			}
		}
	}

	if (!cm)
	{
		unsigned long long l;
		char found = 0;

		if (req.type == CMODE_NORMAL)
		{
			for (l = 1; l < ULLONG_MAX/2; l *= 2)
			{
				found = 0;
				for (cm=channelmodes; cm; cm = cm->next)
				{
					if (cm->mode == l)
					{
						found = 1;
						break;
					}
				}
				if (!found)
					break;
			}
			/* If 'found' is still true, then we are out of space */
			if (found)
			{
				unreal_log(ULOG_ERROR, "module", "CHANNEL_MODE_OUT_OF_SPACE", NULL,
					   "CmodeAdd: out of space!!!");
				if (module)
					module->errorcode = MODERR_NOSPACE;
				return NULL;
			}
			cm = safe_alloc(sizeof(Cmode));
			cm->letter = req.letter;
			cm->mode = l;
			*mode = cm->mode;
		} else if (req.type == CMODE_MEMBER)
		{
			if (!req.prefix || !req.sjoin_prefix || !req.paracount ||
			    !req.unset_with_param || !req.rank)
			{
				unreal_log(ULOG_ERROR, "module", "CMODEADD_API_ERROR", NULL,
					   "CmodeAdd(): module is missing required information. "
					   "Module: $module_name",
					   log_data_string("module_name", module->header->name));
				module->errorcode = MODERR_INVALID;
				return NULL;
			}
			cm = safe_alloc(sizeof(Cmode));
			cm->letter = req.letter;
		} else {
			abort();
		}
		channelmode_add_sorted(cm);
	}

	if ((req.paracount == 1) && (req.type == CMODE_NORMAL))
	{
		if (existing)
		{
			/* Re-use parameter slot of the module with the same modechar that is unloading */
			paraslot = cm->param_slot;
		}
		else
		{
			/* Allocate a new one */
			for (paraslot = 0; ParamTable[paraslot]; paraslot++)
			{
				if (paraslot == MAXPARAMMODES - 1)
				{
					unreal_log(ULOG_ERROR, "module", "CHANNEL_MODE_OUT_OF_SPACE", NULL,
						   "CmodeAdd: out of space!!! Place 2.");
					if (module)
						module->errorcode = MODERR_NOSPACE;
					return NULL;
				}
			}
		}
	}

	cm->letter = req.letter;
	cm->type = req.type;
	cm->prefix = req.prefix;
	cm->sjoin_prefix = req.sjoin_prefix;
	cm->rank = req.rank;
	cm->paracount = req.paracount;
	cm->is_ok = req.is_ok;
	cm->put_param = req.put_param;
	cm->get_param = req.get_param;
	cm->conv_param = req.conv_param;
	cm->free_param = req.free_param;
	cm->dup_struct = req.dup_struct;
	cm->sjoin_check = req.sjoin_check;
	cm->local = req.local;
	cm->unset_with_param = req.unset_with_param;
	cm->flood_type_action = req.flood_type_action;
	cm->owner = module;
	cm->unloaded = 0;

	if (cm->type == CMODE_NORMAL)
	{
		*mode = cm->mode;
		if (cm->paracount == 1)
			extcmode_para_addslot(cm, paraslot);
	}

	if (module)
	{
		ModuleObject *cmodeobj = safe_alloc(sizeof(ModuleObject));
		cmodeobj->object.cmode = cm;
		cmodeobj->type = MOBJ_CMODE;
		AddListItem(cmodeobj, module->objects);
		module->errorcode = MODERR_NOERROR;
	}
	return cm;
}

/** Delete a previously registered channel mode - not called by modules.
 * For modules this is done automatically on unload, no need to call this explicitly.
 */
void CmodeDel(Cmode *cmode)
{
	if (cmode->owner)
	{
		ModuleObject *cmodeobj;
		for (cmodeobj = cmode->owner->objects; cmodeobj; cmodeobj = cmodeobj->next) {
			if (cmodeobj->type == MOBJ_CMODE && cmodeobj->object.cmode == cmode) {
				DelListItem(cmodeobj, cmode->owner->objects);
				safe_free(cmodeobj);
				break;
			}
		}
		cmode->owner = NULL;
	}
	if (loop.rehashing)
		cmode->unloaded = 1;
	else
		unload_extcmode_commit(cmode);

}

/** @} */

/** After a channel mode is deregistered for sure, unload it completely.
 * This is done after a REHASH when no new module has registered the mode.
 * Then we can unload it for good. This also sends MODE -.. out etc.
 */
static void unload_extcmode_commit(Cmode *cmode)
{
	Channel *channel;

	if (!cmode)
		return;

	if (cmode->type == CMODE_NORMAL)
	{
		/* Unset channel mode and send MODE to everyone */
		if (cmode->paracount == 0)
		{
			/* Paramless mode, easy */
			for (channel = channels; channel; channel = channel->nextch)
			{
				if (channel->mode.mode & cmode->mode)
				{
					MessageTag *mtags = NULL;

					new_message(&me, NULL, &mtags);
					sendto_channel(channel, &me, NULL, 0, 0, SEND_LOCAL, mtags,
						       ":%s MODE %s -%c",
						       me.name, channel->name, cmode->letter);
					sendto_server(NULL, 0, 0, mtags,
						":%s MODE %s -%c 0",
						me.id, channel->name, cmode->letter);
					free_message_tags(mtags);

					channel->mode.mode &= ~cmode->mode;
				}
			}
		} else
		{
			/* Parameter mode, more complicated */
			for (channel = channels; channel; channel = channel->nextch)
			{
				if (channel->mode.mode & cmode->mode)
				{
					MessageTag *mtags = NULL;

					new_message(&me, NULL, &mtags);
					if (cmode->unset_with_param)
					{
						const char *param = cmode->get_param(GETPARASTRUCT(channel, cmode->letter));
						sendto_channel(channel, &me, NULL, 0, 0, SEND_LOCAL, mtags,
							       ":%s MODE %s -%c %s",
							       me.name, channel->name, cmode->letter, param);
						sendto_server(NULL, 0, 0, mtags,
							":%s MODE %s -%c %s 0",
							me.id, channel->name, cmode->letter, param);
					} else {
						sendto_channel(channel, &me, NULL, 0, 0, SEND_LOCAL, mtags,
							       ":%s MODE %s -%c",
							       me.name, channel->name, cmode->letter);
						sendto_server(NULL, 0, 0, mtags,
							":%s MODE %s -%c 0",
							me.id, channel->name, cmode->letter);
					}
					free_message_tags(mtags);

					cmode->free_param(GETPARASTRUCT(channel, cmode->letter), 0);
					channel->mode.mode &= ~cmode->mode;
				}
			}
			extcmode_para_delslot(cmode, cmode->param_slot);
		}
	} else
	if (cmode->type == CMODE_MEMBER)
	{
		for (channel = channels; channel; channel = channel->nextch)
		{
			Member *m;
			for (m = channel->members; m; m = m->next)
			{
				if (strchr(m->member_modes, cmode->letter))
				{
					MessageTag *mtags = NULL;

					new_message(&me, NULL, &mtags);
					sendto_channel(channel, &me, NULL, 0, 0, SEND_LOCAL, mtags,
						       ":%s MODE %s -%c %s",
						       me.name, channel->name, cmode->letter, m->client->name);
					sendto_server(NULL, 0, 0, mtags,
						":%s MODE %s -%c %s 0",
						me.id, channel->name, cmode->letter, m->client->id);
					free_message_tags(mtags);
					del_member_mode(m->client, channel, cmode->letter);
				}
			}
		}
	}

	DelListItem(cmode, channelmodes);
	safe_free(cmode);
}

/** Unload all unused channel modes after a REHASH */
void unload_all_unused_extcmodes(void)
{
	Cmode *cm, *cm_next;

	for (cm=channelmodes; cm; cm = cm_next)
	{
		cm_next = cm->next;
		if (cm->letter && cm->unloaded)
		{
			unload_extcmode_commit(cm);
		}
	}

}

/** @defgroup ChannelModeAPI Channel mode API
 * @{
 */

/** Get parameter for a channel mode.
 * @param channel	The channel
 * @param mode		The mode character (eg: 'f')
 */
const char *cm_getparameter(Channel *channel, char mode)
{
	return GETPARAMHANDLERBYLETTER(mode)->get_param(GETPARASTRUCT(channel, mode));
}

/** Get parameter for a channel mode - special version for SJOIN.
 * This version doesn't take a channel, but a mode.mode_params.
 * It is only used by SJOIN and should not be used in 3rd party modules.
 * @param p	The list, eg oldmode.mode_params
 * @param mode	The mode letter
 */
const char *cm_getparameter_ex(void **p, char mode)
{
	return GETPARAMHANDLERBYLETTER(mode)->get_param(GETPARASTRUCTEX(p, mode));
}

/** Set parameter for a channel mode.
 * @param channel	The channel
 * @param mode		The mode character (eg: 'f')
 * @param str		The parameter string
 * @note Module users should not use this function directly, it is only used by MODE and SJOIN.
 */
void cm_putparameter(Channel *channel, char mode, const char *str)
{
	GETPARASTRUCT(channel, mode) = GETPARAMHANDLERBYLETTER(mode)->put_param(GETPARASTRUCT(channel, mode), str);
}

/** Free a channel mode parameter.
 * @param channel	The channel
 * @param mode		The mode character (eg: 'f')
 */
void cm_freeparameter(Channel *channel, char mode)
{
	int n = GETPARAMHANDLERBYLETTER(mode)->free_param(GETPARASTRUCT(channel, mode), 1);
	if (n == 0)
		GETPARASTRUCT(channel, mode) = NULL;
}


/** Set parameter for a channel mode - special version for SJOIN.
 * This version doesn't take a channel, but a mode.mode_params.
 * It is only used by SJOIN and should not be used in 3rd party modules.
 * @param p	The list, eg oldmode.mode_params
 * @param mode	The mode letter
 * @param str	The mode parameter string to set
 */
void cm_putparameter_ex(void **p, char mode, const char *str)
{
	GETPARASTRUCTEX(p, mode) = GETPARAMHANDLERBYLETTER(mode)->put_param(GETPARASTRUCTEX(p, mode), str);
}

/** Default handler for - require channel operator or higher (+o/+a/+q)
 * @param client	The client issueing the MODE
 * @param channel	The channel
 * @param mode		The mode letter (eg: 'f')
 * @param para		The parameter, if any (can be NULL)
 * @param checkt	The check type, one of .....
 * @param what		MODE_ADD / MODE_DEL (???)
 * @returns EX_ALLOW or EX_DENY
 */
int extcmode_default_requirechop(Client *client, Channel *channel, char mode, const char *para, int checkt, int what)
{
	if (IsUser(client) && check_channel_access(client, channel, "oaq"))
		return EX_ALLOW;
	if (checkt == EXCHK_ACCESS_ERR) /* can only be due to being halfop */
		sendnumeric(client, ERR_NOTFORHALFOPS, mode);
	return EX_DENY;
}

/** Default handler for - require halfop or higher (+h/+o/+a/+q)
 * @param client	The client issueing the MODE
 * @param channel	The channel
 * @param mode		The mode letter (eg: 'f')
 * @param para		The parameter, if any (can be NULL)
 * @param checkt	The check type, one of .....
 * @param what		MODE_ADD / MODE_DEL (???)
 * @returns EX_ALLOW or EX_DENY
 */
int extcmode_default_requirehalfop(Client *client, Channel *channel, char mode, const char *para, int checkt, int what)
{
	if (IsUser(client) && (check_channel_access(client, channel, "oaq") || check_channel_access(client, channel, "h")))
		return EX_ALLOW;
	return EX_DENY;
}

/** Duplicate all channel mode parameters - only used by SJOIN.
 * @param xi	Input list
 * @param xi	Output list
 */
void extcmode_duplicate_paramlist(void **xi, void **xo)
{
	int i;
	Cmode *handler;
	void *inx;

	for (i = 0; i < MAXPARAMMODES; i++)
	{
		handler = CMP_GETHANDLERBYSLOT(i);
		if (!handler)
			continue; /* nothing there.. */
		inx = xi[handler->param_slot]; /* paramter data of input is here */
		if (!inx)
			continue; /* not set */
		xo[handler->param_slot] = handler->dup_struct(inx); /* call dup_struct with that input and set the output param to that */
	}
}

/** Free all channel mode parameters - only used by SJOIN.
 * @param ar	The list
 */
void extcmode_free_paramlist(void **ar)
{
	int i;
	Cmode *handler;

	for (i = 0; i < MAXPARAMMODES; i++)
	{
		handler = GETPARAMHANDLERBYSLOT(i);
		if (!handler)
			continue; /* nothing here... */
		handler->free_param(ar[handler->param_slot], 0);
		ar[handler->param_slot] = NULL;
	}
}

/** @} */

/** Internal function: returns 1 if the specified module has 1 or more extended channel modes registered */
int module_has_extcmode_param_mode(Module *mod)
{
	Cmode *cm;

	for (cm=channelmodes; cm; cm = cm->next)
		if ((cm->letter) && (cm->owner == mod) && (cm->paracount))
			return 1;

	return 0;
}

/** Channel member privileges - getting, setting, checking vhoaq status, etc.
 * These functions get or set the access rights of channel members, such as +vhoaq.
 * They can also convert between modes (vhoaq), prefixes and sjoin prefixes.
 * @defgroup ChannelMember Channel members access privileges
 * @{
 */

/** Retrieve channel access for a user on a channel, returns modes eg "qao".
 * @param client	The client
 * @param channel	The channel
 * @returns The modes, sorted by high ranking to lower ranking, eg "qao".
 * An empty string ("") is returned when not in the channel or no modes.
 */
const char *get_channel_access(Client *client, Channel *channel)
{
	Membership *mb;

	mb = find_membership_link(client->user->channel, channel);
	if (!mb)
		return "";
	return mb->member_modes;
}

/** Check channel access for user.
 * @param client	The client to check
 * @param channel	The channel to check
 * @param modes		Which mode(s) to check for
 * @returns If the client in channel has any of the modes set, 1 is returned.
 * Otherwise 0 is returned, which is also the case if the user is
 * not a user or is not in the channel at all.
 */
int check_channel_access(Client *client, Channel *channel, const char *modes)
{
	Membership *mb;
	const char *p;

	if (!IsUser(client))
		return 0; /* eg server */

	mb = find_membership_link(client->user->channel, channel);
	if (!mb)
		return 0; /* not a member */

	for (p = mb->member_modes; *p; p++)
		if (strchr(modes, *p))
			return 1; /* match new style */

	return 0; /* nomatch */
}

/** Check channel access for user.
 * @param client	The client to check
 * @param channel	The channel to check
 * @param modes		Which mode(s) to check for
 * @returns If the client in channel has any of the modes set, 1 is returned.
 * Otherwise 0 is returned, which is also the case if the user is
 * not a user or is not in the channel at all.
 */
int check_channel_access_membership(Membership *mb, const char *modes)
{
	const char *p;

	if (!mb)
		return 0;

	for (p = mb->member_modes; *p; p++)
		if (strchr(modes, *p))
			return 1; /* match new style */

	return 0; /* nomatch */
}

/** Check channel access for user.
 * @param client	The client to check
 * @param channel	The channel to check
 * @param modes		Which mode(s) to check for
 * @returns If the client in channel has any of the modes set, 1 is returned.
 * Otherwise 0 is returned, which is also the case if the user is
 * not a user or is not in the channel at all.
 */
int check_channel_access_member(Member *mb, const char *modes)
{
	const char *p;

	if (!mb)
		return 0;

	for (p = mb->member_modes; *p; p++)
		if (strchr(modes, *p))
			return 1; /* match new style */

	return 0; /* nomatch */
}

/** Check channel access for user.
 * @param current	Flags currently set on the client (eg mb->member_modes)
 * @param modes		Which mode(s) to check for
 * @returns If the client in channel has any of the modes set, 1 is returned.
 * Otherwise 0 is returned.
 */
int check_channel_access_string(const char *current_modes, const char *modes)
{
	const char *p;

	for (p = current_modes; *p; p++)
		if (strchr(modes, *p))
			return 1;

	return 0; /* nomatch */
}

/** Check channel access for user.
 * @param current	Flags currently set on the client (eg mb->member_modes)
 * @param letter	Which mode letter to check for
 * @returns If the client in channel has any of the modes set, 1 is returned.
 * Otherwise 0 is returned.
 */
int check_channel_access_letter(const char *current_modes, const char letter)
{
	return strchr(current_modes, letter) ? 1 : 0;
}

Cmode *find_channel_mode_handler(char letter)
{
	Cmode *cm;

	for (cm=channelmodes; cm; cm = cm->next)
		if (cm->letter == letter)
			return cm;
	return NULL;
}

/** Is 'letter' a valid mode used for access/levels/ranks? (vhoaq and such)
 * @param letter	The channel mode letter to check, eg 'v'
 * @returns 1 if valid, 0 if the channel mode does not exist or is not a level mode.
 */
int valid_channel_access_mode_letter(char letter)
{
	Cmode *cm;

	if ((cm = find_channel_mode_handler(letter)) && (cm->type == CMODE_MEMBER))
		return 1;

	return 0;
}

void addlettertomstring(char *str, char letter)
{
	Cmode *cm;
	int n;
	int my_rank;
	char *p;

	if (!(cm = find_channel_mode_handler(letter)) || (cm->type != CMODE_MEMBER))
		return; // should we BUG on this? if something makes it this far, it can never be good right?

	my_rank = cm->rank;

	n = strlen(str);
	if (n >= MEMBERMODESLEN-1)
		return; // panic!

	for (p = str; *p; p++)
	{
		cm = find_channel_mode_handler(*p);
		if (!cm)
			continue; /* wtf */
		if (cm->rank < my_rank)
		{
			/* We need to insert us here */
			n = strlen(p);
			memmove(p+1, p, n+1); // +1 for NUL byte
			*p = letter;
			return;
		}
	}
	/* We should be at the end */
	str[n] = letter;
	str[n+1] = '\0';
}

void add_member_mode_fast(Member *mb, Membership *mbs, char letter)
{
	addlettertomstring(mb->member_modes, letter);
	addlettertomstring(mbs->member_modes, letter);
}

void del_member_mode_fast(Member *mb, Membership *mbs, char letter)
{
	delletterfromstring(mb->member_modes, letter);
	delletterfromstring(mbs->member_modes, letter);
}

int find_mbs(Client *client, Channel *channel, Member **mb, Membership **mbs)
{
	*mbs = NULL;

	if (!(*mb = find_member_link(channel->members, client)))
		return 0;

	if (!(*mbs = find_membership_link(client->user->channel, channel)))
		return 0;
	
	return 1;
}

void add_member_mode(Client *client, Channel *channel, char letter)
{
	Member *mb;
	Membership *mbs;

	if (!find_mbs(client, channel, &mb, &mbs))
		return;

	add_member_mode_fast(mb, mbs, letter);
}

void del_member_mode(Client *client, Channel *channel, char letter)
{
	Member *mb;
	Membership *mbs;

	if (!find_mbs(client, channel, &mb, &mbs))
		return;

	del_member_mode_fast(mb, mbs, letter);
}

char sjoin_prefix_to_mode(char s)
{
	Cmode *cm;

	/* Filter this out early to avoid spurious results */
	if (s == '\0')
		return '\0';

	/* First the hardcoded list modes: */
	if (s == '&')
		return 'b';
	if (s == '"')
		return 'e';
	if (s == '\'')
		return 'I';

	/* Now the dynamic ones (+vhoaq): */
	for (cm=channelmodes; cm; cm = cm->next)
		if ((cm->sjoin_prefix == s) && (cm->type == CMODE_MEMBER))
			return cm->letter;

	/* Not found */
	return '\0';
}

char mode_to_sjoin_prefix(char s)
{
	Cmode *cm;

	/* Filter this out early to avoid spurious results */
	if (s == '\0')
		return '\0';

	/* First the hardcoded list modes: */
	if (s == 'b')
		return '&';
	if (s == 'e')
		return '"';
	if (s == 'I')
		return '\'';

	/* Now the dynamic ones (+vhoaq): */
	for (cm=channelmodes; cm; cm = cm->next)
		if ((cm->letter == s) && (cm->type == CMODE_MEMBER))
			return cm->sjoin_prefix;

	/* Not found */
	return '\0';
}

const char *modes_to_sjoin_prefix(const char *modes)
{
	static char buf[MEMBERMODESLEN];
	const char *m;
	char f;

	*buf = '\0';
	for (m = modes; *m; m++)
	{
		f = mode_to_sjoin_prefix(*m);
		if (f)
			strlcat_letter(buf, f, sizeof(buf));
	}

	return buf;
}

char mode_to_prefix(char s)
{
	Cmode *cm;

	/* Filter this out early to avoid spurious results */
	if (s == '\0')
		return '\0';

	/* Now the dynamic ones (+vhoaq): */
	for (cm=channelmodes; cm; cm = cm->next)
		if ((cm->letter == s) && (cm->type == CMODE_MEMBER))
			return cm->prefix;

	/* Not found */
	return '\0';
}

const char *modes_to_prefix(const char *modes)
{
	static char buf[MEMBERMODESLEN];
	const char *m;
	char f;

	*buf = '\0';
	for (m = modes; *m; m++)
	{
		f = mode_to_prefix(*m);
		if (f)
			strlcat_letter(buf, f, sizeof(buf));
	}

	return buf;
}

char prefix_to_mode(char s)
{
	Cmode *cm;

	/* Filter this out early to avoid spurious results */
	if (s == '\0')
		return '\0';

	/* Now the dynamic ones (+vhoaq): */
	for (cm=channelmodes; cm; cm = cm->next)
		if ((cm->prefix == s) && (cm->type == CMODE_MEMBER))
			return cm->letter;

	/* Not found */
	return '\0';
}

char rank_to_mode(int rank)
{
	Cmode *cm;
	for (cm=channelmodes; cm; cm = cm->next)
		if ((cm->type == CMODE_MEMBER) && (cm->rank == rank))
			return cm->letter;
	return '\0';
}

int mode_to_rank(char mode)
{
	Cmode *cm;
	for (cm=channelmodes; cm; cm = cm->next)
		if ((cm->type == CMODE_MEMBER) && (cm->letter == mode))
			return cm->rank;
	return '\0';
}

int prefix_to_rank(char prefix)
{
	Cmode *cm;
	for (cm=channelmodes; cm; cm = cm->next)
		if ((cm->type == CMODE_MEMBER) && (cm->prefix == prefix))
			return cm->rank;
	return '\0';
}

char rank_to_prefix(int rank)
{
	Cmode *cm;
	for (cm=channelmodes; cm; cm = cm->next)
		if ((cm->type == CMODE_MEMBER) && (cm->rank == rank))
			return cm->prefix;
	return '\0';
}

char lowest_ranking_prefix(const char *prefix)
{
	const char *p;
	int winning_rank = INT_MAX;

	for (p = prefix; *p; p++)
	{
		int rank = prefix_to_rank(*p);
		if (rank < winning_rank)
			winning_rank = rank;
	}
	if (winning_rank == INT_MAX)
		return '\0'; /* No result */
	return rank_to_prefix(winning_rank);
}

char lowest_ranking_mode(const char *mode)
{
	const char *p;
	int winning_rank = INT_MAX;

	for (p = mode; *p; p++)
	{
		int rank = mode_to_rank(*p);
		if (rank < winning_rank)
			winning_rank = rank;
	}
	if (winning_rank == INT_MAX)
		return '\0'; /* No result */
	return rank_to_mode(winning_rank);
}

/** Generate all member modes that are equal or greater than 'modes'.
 * Eg calling this with "o" would generate "oaq" with the default loaded modules.
 * This is used in sendto_channel() to make multiple check_channel_access_member()
 * calls more easy / faster.
 */
void channel_member_modes_generate_equal_or_greater(const char *modes, char *buf, size_t buflen)
{
	const char *p;
	int rank;
	Cmode *cm;

	*buf = '\0';

	/* First we must grab the lowest ranking mode, eg 'vhoaq' results in rank for 'v' */
	rank = lowest_ranking_mode(modes);
	if (!rank)
		return; /* zero matches */

	for (cm=channelmodes; cm; cm = cm->next)
	if ((cm->type == CMODE_MEMBER) && (cm->rank >= rank))
		strlcat_letter(buf, cm->letter, buflen);
}

/** @} */
