/* Ban exception related functions.
 *
 * IRC Services is copyright (c) 1996-2002 Andrew Church.
 *     E-mail: <achurch@achurch.org>
 * Parts written by Andrew Kempe and others.
 * This program is free but copyrighted software; see the file COPYING for
 * details.
 */

#include "services.h"
#include "modules.h"
#include "language.h"
#include "modules/chanserv/chanserv.h"

#include "banexcept.h"

/*************************************************************************/

static Module *module;
static Module *module_chanserv;

static const char **p_s_ChanServ = NULL;  /* we never use it if it's NULL */
#define s_ChanServ (*p_s_ChanServ)

/*************************************************************************/
/*************************************************************************/

/* Callback to handle MODE +/-e. */

static int do_channel_mode(const char *source, Channel *chan,
			   int modechar, int add, char **av)
{
    if (modechar == 'e') {
	if (add) {
	    if (chan->exceptcount >= chan->exceptsize) {
		chan->exceptsize += 8;
		chan->excepts = srealloc(chan->excepts,
					 sizeof(char *) * chan->exceptsize);
	    }
	    chan->excepts[chan->exceptcount++] = sstrdup(av[0]);
	} else {
	    char **s = chan->excepts;
	    int i = 0;
	    while (i < chan->exceptcount && strcmp(*s, av[0]) != 0) {
		i++;
		s++;
	    }
	    if (i < chan->exceptcount) {
		chan->exceptcount--;
		if (i < chan->exceptcount)
		    memmove(s, s+1, sizeof(char *)*(chan->exceptcount-i));
	    } else {
		module_log("banexcept: MODE %s -e %s: exception not found",
			   chan->name, *av);
	    }
	    av++;
	}
	return 0;
    }
    return 0;
}

/*************************************************************************/

/* Callback to handle clearing exceptions for clear_channel(). */

static void clear_excepts(const char *sender, Channel *chan, const User *u);
static int do_clear_channel(const char *sender, Channel *chan, int what,
			    const void *param)
{
    if (what & (CLEAR_USERS | CLEAR_BANS))
	clear_excepts(sender, chan, (what & CLEAR_BANS) ? param : NULL);
    return 0;
}


static void clear_excepts(const char *sender, Channel *chan, const User *u)
{
    int i, count;
    char **excepts;

    if (!chan->exceptcount)
	return;
    count = chan->exceptcount;
    excepts = smalloc(sizeof(char *) * count);
    memcpy(excepts, chan->excepts, sizeof(char *) * count);
    for (i = 0; i < count; i++) {
	if (!u || match_usermask(excepts[i], u))
	    set_cmode(sender, chan, "-e", excepts[i]);
    }
    free(excepts);
}

/*************************************************************************/

/* Callback to handle ChanServ CLEAR EXCEPTIONS. */

static int do_cs_clear(User *u, Channel *c, const char *what)
{
    if (stricmp(what, "EXCEPTIONS") == 0) {
	clear_excepts(s_ChanServ, c, NULL);
	set_cmode(NULL, c);
	notice_lang(s_ChanServ, u, CHAN_CLEARED_EXCEPTIONS, c->name);
	return 1;
    }
    return 0;
}

/*************************************************************************/
/*************************************************************************/

/* Callback to watch for modules being loaded. */

static int do_load_module(Module *mod, const char *name)
{
    if (strcmp(name, "chanserv/main") == 0) {
	module_chanserv = mod;
	p_s_ChanServ = get_module_symbol(mod, "s_ChanServ");
	if (p_s_ChanServ) {
	    if (!(add_callback(mod, "CLEAR", do_cs_clear)))
		module_log("banexcept: Unable to add ChanServ CLEAR callback");
	} else {
	    module_log("banexcept: Unable to resolve symbol `s_ChanServ' in"
		       " module `chanserv/main', CLEAR EXCEPTIONS will not"
		       " be available");
	}
    }
    return 0;
}

/*************************************************************************/

/* Callback to watch for modules being unloaded. */

static int do_unload_module(Module *mod)
{
    if (mod == module_chanserv) {
	p_s_ChanServ = NULL;
	module_chanserv = NULL;
    }
    return 0;
}

/*************************************************************************/
/*************************************************************************/

static int old_CLEARMODES_DONE = -1;

/*************************************************************************/

int init_banexcept(Module *module_)
{
    module = module_;
    if (!add_callback(NULL, "channel MODE", do_channel_mode)
     || !add_callback(NULL, "clear channel", do_clear_channel)
     || !add_callback(NULL, "load module", do_load_module)
     || !add_callback(NULL, "unload module", do_unload_module)
    ) {
	module_log("banexcept: Unable to add callbacks");
	exit_banexcept();
	return 0;
    }
    protocol_features |= PF_BANEXCEPT;
    old_CLEARMODES_DONE =
	setstring(OPER_CLEARMODES_DONE, OPER_CLEARMODES_EXCEPT_DONE);
    return 1;
}

/*************************************************************************/

void exit_banexcept(void)
{
    if (old_CLEARMODES_DONE >= 0)
	setstring(OPER_CLEARMODES_DONE, old_CLEARMODES_DONE);
    old_CLEARMODES_DONE = -1;
    remove_callback(NULL, "unload module", do_unload_module);
    remove_callback(NULL, "load module", do_load_module);
    remove_callback(NULL, "clear channel", do_clear_channel);
    remove_callback(NULL, "channel MODE", do_channel_mode);
}

/*************************************************************************/
