/* SOP/AOP/VOP handling for ChanServ.
 *
 * 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 "conffile.h"
#include "language.h"
#include "commands.h"
#include "modules/nickserv/nickserv.h"
#include "modules/operserv/operserv.h"

#include "chanserv.h"
#include "cs-local.h"

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

static Module *module;
static Module *module_chanserv;

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

/* Return the name of the list or the syntax message number corresponding
 * to the given level.  Does no error checking. */
# define XOP_LISTNAME(level) \
    ((level)==ACCLEV_SOP ? "SOP" : (level)==ACCLEV_AOP ? "AOP" : \
     (level)==ACCLEV_HOP ? "HOP" : "VOP")
# define XOP_SYNTAX(level) \
    ((level)==ACCLEV_SOP ? CHAN_SOP_SYNTAX : \
     (level)==ACCLEV_AOP ? CHAN_AOP_SYNTAX : \
     (level)==ACCLEV_HOP ? CHAN_HOP_SYNTAX : CHAN_VOP_SYNTAX)

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

/* Local functions. */

static void handle_xop(User *u, int level);
static int xop_del_callback(User *u, int num, va_list args);
static int xop_list(User *u, int index, ChannelInfo *ci, int *sent_header,
		    int relindex);
static int xop_list_callback(User *u, int num, va_list args);

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

static void do_sop(User *u);
static void do_aop(User *u);
static void do_hop(User *u);
static void do_vop(User *u);

static Command cmds[] = {
    { "SOP",	  do_sop,      NULL,  CHAN_HELP_SOP,            -1,-1 },
    { "AOP",	  do_aop,      NULL,  CHAN_HELP_AOP,            -1,-1 },
    { "VOP",	  do_vop,      NULL,  CHAN_HELP_VOP,            -1,-1 },
    { NULL }
};

static Command cmds_halfop[] = {
    { "HOP",	  do_hop,      NULL,  CHAN_HELP_HOP,            -1,-1 },
    { NULL }
};

/*************************************************************************/
/*************************** The *OP commands ****************************/
/*************************************************************************/

/* SOP, VOP, AOP, and HOP wrappers.  These just call handle_xop() with the
 * appropriate level.
 */

static void do_sop(User *u)
{
    handle_xop(u, ACCLEV_SOP);
}

static void do_aop(User *u)
{
    handle_xop(u, ACCLEV_AOP);
}

static void do_hop(User *u)
{
    handle_xop(u, ACCLEV_HOP);
}

static void do_vop(User *u)
{
    handle_xop(u, ACCLEV_VOP);
}

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

/* Central handler for all *OP commands. */

static void handle_xop(User *u, int level)
{
    char *chan = strtok(NULL, " ");
    char *cmd = strtok(NULL, " ");
    char *nick = strtok(NULL, " ");
    ChannelInfo *ci;
    NickInfo *ni;
    NickGroupInfo *ngi;
    int i;
    const char *listname = XOP_LISTNAME(level);
    int is_list = (cmd && (stricmp(cmd,"LIST")==0 || stricmp(cmd,"COUNT")==0));
    int is_servadmin = is_services_admin(u);

    if (!cmd || (!is_list && !nick) || (stricmp(cmd,"COUNT")==0 && nick)) {
	syntax_error(s_ChanServ, u, listname, XOP_SYNTAX(level));
    } else if (!(ci = get_channelinfo(chan))) {
	notice_lang(s_ChanServ, u, CHAN_X_NOT_REGISTERED, chan);
    } else if (ci->flags & CI_VERBOTEN) {
	notice_lang(s_ChanServ, u, CHAN_X_FORBIDDEN, chan);
    } else if (!is_servadmin
	       && !check_access_cmd(u, ci, "ACCESS", is_list ? "LIST" : cmd)) {
	notice_lang(s_ChanServ, u, ACCESS_DENIED);

    } else if (stricmp(cmd, "ADD") == 0) {
	if (readonly) {
	    notice_lang(s_ChanServ, u, CHAN_ACCESS_DISABLED);
	    return;
	}
	switch (access_add(ci, nick, level,
			   is_servadmin ? ACCLEV_FOUNDER : get_access(u,ci))) {
	  case RET_ADDED:
	    notice_lang(s_ChanServ, u, CHAN_XOP_ADDED, nick, chan, listname);
	    break;
	  case RET_CHANGED:
	    notice_lang(s_ChanServ, u, CHAN_XOP_LEVEL_CHANGED,
			nick, chan, listname);
	    break;
	  case RET_UNCHANGED:
	    notice_lang(s_ChanServ, u, CHAN_XOP_LEVEL_UNCHANGED,
			nick, chan, listname);
	    break;
	  case RET_LISTFULL:
	    notice_lang(s_ChanServ, u, CHAN_XOP_REACHED_LIMIT, CSAccessMax);
	    break;
	  case RET_NOSUCHNICK:
	    notice_lang(s_ChanServ, u, CHAN_XOP_NICKS_ONLY);
	    break;
	  case RET_NICKFORBID:
	    notice_lang(s_ChanServ, u, NICK_X_FORBIDDEN, nick);
	    break;
	  case RET_PERMISSION:
	    notice_lang(s_ChanServ, u, PERMISSION_DENIED);
	    break;
	}

    } else if (stricmp(cmd, "DEL") == 0) {
	if (!is_servadmin && level >= get_access(u, ci)) {
	    notice_lang(s_ChanServ, u, PERMISSION_DENIED);
	    return;
	} else if (readonly) {
	    if (is_servadmin) {
		notice_lang(s_ChanServ, u, READ_ONLY_MODE);
	    } else {
		notice_lang(s_ChanServ, u, CHAN_ACCESS_DISABLED);
		return;
	    }
	}

	if (isdigit(*nick) && strspn(nick, "1234567890,-") == strlen(nick)) {
	    int count, deleted, last;
	    int offset = 0; /* used internally by the deletion routine. */
	    deleted = process_numlist(nick, &count, xop_del_callback, u,
			ci, &offset, &last, level);
	    if (!deleted) {
		if (count == 1) {
		    notice_lang(s_ChanServ, u, CHAN_XOP_NO_SUCH_ENTRY,
				last, ci->name, listname);
		} else {
		    notice_lang(s_ChanServ, u, CHAN_XOP_NO_MATCH,
		    		ci->name, listname);
		}
		return;  /* don't bother with put_channelinfo() */
	    } else if (deleted == 1) {
		notice_lang(s_ChanServ, u, CHAN_XOP_DELETED_ONE,
				ci->name, listname);
	    } else {
		notice_lang(s_ChanServ, u, CHAN_XOP_DELETED_SEVERAL,
				deleted, ci->name, listname);
	    }
	} else {
	    ni = get_nickinfo(nick);
	    if (!ni) {
		notice_lang(s_ChanServ, u, NICK_X_NOT_REGISTERED, nick);
		return;
	    }
	    if (!(ngi = get_ngi(ni))) {
		notice_lang(s_ChanServ, u, INTERNAL_ERROR);
		return;
	    }
	    ARRAY_SEARCH_SCALAR(ci->access, nickgroup, ngi->id, i);
	    if (i == ci->access_count || ci->access[i].level != level) {
		notice_lang(s_ChanServ, u, CHAN_XOP_NOT_FOUND,
				nick, chan, listname);
		return;
	    }
	    ci->access[i].nickgroup = 0;
	    notice_lang(s_ChanServ, u, CHAN_XOP_DELETED,
			ngi->nicks[ngi->mainnick], ci->name, listname);
	}
	put_channelinfo(ci);

    } else if (stricmp(cmd, "LIST") == 0) {
	int sent_header = 0;
	int relindex = 0;

	if (ci->access_count == 0) {
	    notice_lang(s_ChanServ, u, CHAN_XOP_LIST_EMPTY, chan, listname);
	    return;
	}
	if (nick && strspn(nick, "1234567890,-") == strlen(nick)) {
	    process_numlist(nick, NULL, xop_list_callback, u, ci,
						    &sent_header, level);
	} else {
	    ARRAY_FOREACH (i, ci->access) {
		if (!ci->access[i].nickgroup || ci->access[i].level != level)
		    continue;
		relindex++;
		if (nick && ci->access[i].nickgroup) {
		    if (!(ngi = get_ngi_id(ci->access[i].nickgroup))
		     || !match_wild_nocase(nick, ngi->nicks[ngi->mainnick])
		    ) {
			continue;
		    }
		}
		xop_list(u, i, ci, &sent_header, relindex);
	    }
	}
	if (!sent_header)
	    notice_lang(s_ChanServ, u, CHAN_XOP_NO_MATCH, chan, listname);

    } else if (stricmp(cmd, "COUNT") == 0) {
	int count = 0;

	if (ci->access_count == 0) {
	    notice_lang(s_ChanServ, u, CHAN_XOP_LIST_EMPTY, chan, listname);
	    return;
	}
	ARRAY_FOREACH (i, ci->access) {
	    if (ci->access[i].nickgroup && ci->access[i].level == level)
		count++;
	}
	if (count)
	    notice_lang(s_ChanServ, u, CHAN_XOP_COUNT, chan, listname, count);
	else
	    notice_lang(s_ChanServ, u, CHAN_XOP_LIST_EMPTY, chan, listname);

    } else {
	syntax_error(s_ChanServ, u, listname, XOP_SYNTAX(level));
    }
}

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

/* Delete the num'th access list entry with access level oplevel.
 * Note: Permission checking is assumed to have been done.
 */

static int xop_del_callback(User *u, int num, va_list args)
{
    ChannelInfo *ci = va_arg(args, ChannelInfo *);
    int *offset = va_arg(args, int *);
    int *last = va_arg(args, int *);
    int oplevel = va_arg(args, int);
    int i;

    *last = num;
    num -= *offset; /* Compensate for deleted items */
    if (num < 1)
	return 0;
    if (num > ci->access_count)
	return -1;  /* Out of range */
    ARRAY_FOREACH (i, ci->access) {
	if (ci->access[i].nickgroup && ci->access[i].level == oplevel) {
	    num--;
	    if (num <= 0)
		break;
	}
    }
    if (num > 0)
	return -1;  /* Out of range for given access level */
    ci->access[i-1].nickgroup = 0;
    (*offset)++;
    return 1;
}

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

static int xop_list(User *u, int index, ChannelInfo *ci, int *sent_header,
		    int relindex)
{
    ChanAccess *access = &ci->access[index];
    NickGroupInfo *ngi;

    if (!(ngi = get_ngi_id(access->nickgroup)))
	return 0;
    if (!*sent_header) {
	notice_lang(s_ChanServ, u, CHAN_XOP_LIST_HEADER,
		    XOP_LISTNAME(access->level), ci->name);
	*sent_header = 1;
    }
    notice_lang(s_ChanServ, u, CHAN_XOP_LIST_FORMAT, relindex,
		ngi->nicks[ngi->mainnick]);
    return 1;
}

static int xop_list_callback(User *u, int num, va_list args)
{
    ChannelInfo *ci = va_arg(args, ChannelInfo *);
    int *sent_header = va_arg(args, int *);
    int oplevel = va_arg(args, int);
    int i;

    if (num < 1 || num > ci->access_count)
	return 0;
    ARRAY_FOREACH (i, ci->access) {
	if (ci->access[i].nickgroup && ci->access[i].level == oplevel) {
	    num--;
	    if (num <= 0)
		break;
	}
    }
    return num>0 ? 0 : xop_list(u, i-1, ci, sent_header, num);
}

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

/* Callback to display help text for SOP and AOP. */

static int do_help(User *u, const char *param)
{
    if (stricmp(param, "SOP") == 0) {
	notice_help(s_ChanServ, u, CHAN_HELP_SOP);
	notice_help(s_ChanServ, u, CHAN_HELP_SOP_MID1);
	notice_help(s_ChanServ, u, CHAN_HELP_SOP_MID2);
	notice_help(s_ChanServ, u, CHAN_HELP_SOP_END);
	return 1;
    } else if (stricmp(param, "AOP") == 0) {
	notice_help(s_ChanServ, u, CHAN_HELP_AOP);
	notice_help(s_ChanServ, u, CHAN_HELP_AOP_MID);
	notice_help(s_ChanServ, u, CHAN_HELP_AOP_END);
	return 1;
    }
    return 0;
}

/*************************************************************************/
/***************************** Module stuff ******************************/
/*************************************************************************/

const int32 module_version = MODULE_VERSION_CODE;

ConfigDirective module_config[] = {
    { NULL }
};

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

int init_module(Module *module_)
{
    module = module_;

    module_chanserv = find_module("chanserv/main");
    if (!module_chanserv) {
	module_log("Main ChanServ module not loaded");
	return 0;
    }
    use_module(module_chanserv);

    if (!register_commands(module_chanserv, cmds)
	|| ((protocol_features & PF_HALFOP)
	    && !register_commands(module_chanserv, cmds_halfop))
    ) {
	module_log("Unable to register commands");
	exit_module(0);
	return 0;
    }

    if (!add_callback(module_chanserv, "HELP", do_help)) {
	module_log("Unable to add callbacks");
	exit_module(0);
	return 0;
    }

    return 1;
}

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

int exit_module(int shutdown_unused)
{
#ifdef CLEAN_COMPILE
    shutdown_unused = shutdown_unused;
#endif

    if (module_chanserv) {
	remove_callback(module_chanserv, "HELP", do_help);
	if (protocol_features & PF_HALFOP)
	    unregister_commands(module_chanserv, cmds_halfop);
	unregister_commands(module_chanserv, cmds);
	unuse_module(module_chanserv);
	module_chanserv = NULL;
    }

    return 1;
}

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