/* Autokick list handling.
 *
 * IRC Services is copyright (c) 1996-2009 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/nickserv/nickserv.h"
#include "modules/operserv/operserv.h"

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

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

static int akick_del(User *u, AutoKick *akick);
static int akick_del_callback(User *u, int num, va_list args);
static int akick_list(User *u, int index, ChannelInfo *ci, int *sent_header,
		      int is_view);
static int akick_list_callback(User *u, int num, va_list args);

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

void do_akick(User *u)
{
    char *chan   = strtok(NULL, " ");
    char *cmd    = strtok(NULL, " ");
    char *mask   = strtok(NULL, " ");
    char *reason = strtok_remaining();
    ChannelInfo *ci;
    int i;
    int is_list = (cmd && (stricmp(cmd,"LIST") == 0
			   || stricmp(cmd,"VIEW") == 0
			   || stricmp(cmd,"COUNT") == 0));

    if (!cmd || (!mask &&
		(stricmp(cmd, "ADD") == 0 || stricmp(cmd, "DEL") == 0))) {
	syntax_error(s_ChanServ, u, "AKICK", CHAN_AKICK_SYNTAX);
    } 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 (!check_access_cmd(u, ci, "AKICK", is_list ? "LIST" : cmd)
	       && !is_services_admin(u)) {
	if (ci->founder && valid_ngi(u) && ci->founder == u->ngi->id)
	    notice_lang(s_ChanServ, u, CHAN_IDENTIFY_REQUIRED, s_ChanServ,
			chan);
	else
	    notice_lang(s_ChanServ, u, ACCESS_DENIED);

    } else if (stricmp(cmd, "ADD") == 0) {

	char *mask2, *user, *host;
	const char *nick;

	if (readonly) {
	    notice_lang(s_ChanServ, u, CHAN_AKICK_DISABLED);
	    return;
	}

	/* Make sure we have a valid nick!user@host mask (fill in missing
	 * parts with "*").  Error out on @ in nick (also catches a@b!c),
	 * missing host, or empty nick/user/host. */
	mask2 = sstrdup(mask);
	nick = mask2;
	user = strchr(mask2, '!');
	if (user) {
	    *user++ = 0;
	} else {
	    nick = "*";
	    user = mask2;
	}
	host = strchr(user, '@');
	if (host)
	    *host++ = 0;
	if (!*nick || !*user || !host || !*host || strchr(nick, '@')) {
	    notice_lang(s_ChanServ, u, BAD_NICKUSERHOST_MASK);
	    free(mask2);
	    return;
	}
	mask = smalloc(strlen(nick)+strlen(user)+strlen(host)+3);
	sprintf(mask, "%s!%s@%s", nick, user, host);
	free(mask2);

	ARRAY_FOREACH (i, ci->akick) {
	    if (!ci->akick[i].mask)
		continue;
	    if (stricmp(ci->akick[i].mask, mask) == 0) {
		notice_lang(s_ChanServ, u, CHAN_AKICK_ALREADY_EXISTS,
			    ci->akick[i].mask, chan);
		return;
	    }
	}

	ARRAY_SEARCH_SCALAR(ci->akick, mask, NULL, i);
	if (i == ci->akick_count) {
	    if (ci->akick_count >= CSAutokickMax) {
		notice_lang(s_ChanServ, u, CHAN_AKICK_REACHED_LIMIT,
			CSAutokickMax);
		return;
	    }
	    ARRAY_EXTEND(ci->akick);
	}
	ci->akick[i].mask = mask;
	ci->akick[i].reason = reason ? sstrdup(reason) : NULL;
	time(&ci->akick[i].set);
	ci->akick[i].lastused = 0;
	strscpy(ci->akick[i].who, u->nick, NICKMAX);
	notice_lang(s_ChanServ, u, CHAN_AKICK_ADDED, mask, chan);
	put_channelinfo(ci);

    } else if (stricmp(cmd, "DEL") == 0) {

	if (readonly) {
	    notice_lang(s_ChanServ, u, CHAN_AKICK_DISABLED);
	    return;
	}

	/* Special case: is it a number/list?  Only do search if it isn't. */
	if (isdigit(*mask) && strspn(mask, "1234567890,-") == strlen(mask)) {
	    int count, deleted, last = -1;
	    deleted = process_numlist(mask, &count, akick_del_callback, u,
					ci, &last);
	    if (!deleted) {
		if (count == 1) {
		    notice_lang(s_ChanServ, u, CHAN_AKICK_NO_SUCH_ENTRY,
				last, ci->name);
		} else {
		    notice_lang(s_ChanServ, u, CHAN_AKICK_NO_MATCH, ci->name);
		}
		return;  /* don't bother with put_channelinfo() */
	    } else if (deleted == 1) {
		notice_lang(s_ChanServ, u, CHAN_AKICK_DELETED_ONE, ci->name);
	    } else {
		notice_lang(s_ChanServ, u, CHAN_AKICK_DELETED_SEVERAL,
			    deleted, ci->name);
	    }
	} else {
	    ARRAY_FOREACH (i, ci->akick) {
		if (!ci->akick[i].mask)
		    continue;
		if (stricmp(ci->akick[i].mask, mask) == 0)
		    break;
	    }
	    if (i < ci->akick_count) {
		notice_lang(s_ChanServ, u, CHAN_AKICK_DELETED, mask, chan);
		akick_del(u, &ci->akick[i]);
	    } else {
		notice_lang(s_ChanServ, u, CHAN_AKICK_NOT_FOUND, mask, chan);
		return;
	    }
	}
	put_channelinfo(ci);

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

	if (ci->akick_count == 0) {
	    notice_lang(s_ChanServ, u, CHAN_AKICK_LIST_EMPTY, chan);
	    return;
	}
	if (mask && isdigit(*mask) &&
			strspn(mask, "1234567890,-") == strlen(mask)) {
	    process_numlist(mask, NULL, akick_list_callback, u, ci,
			    &sent_header, is_view);
	} else {
	    ARRAY_FOREACH (i, ci->akick) {
		if (!ci->akick[i].mask)
		    continue;
		if (mask) {
		    if (!match_wild_nocase(mask, ci->akick[i].mask))
			continue;
		}
		akick_list(u, i, ci, &sent_header, is_view);
	    }
	}
	if (!sent_header)
	    notice_lang(s_ChanServ, u, CHAN_AKICK_NO_MATCH, chan);

    } else if (stricmp(cmd, "ENFORCE") == 0) {
	Channel *c = get_channel(ci->name);
	struct c_userlist *cu, *next;
	char *argv[3];
	int count = 0;

	if (!c) {
	    notice_lang(s_ChanServ, u, CHAN_X_NOT_IN_USE, ci->name);
	    return;
	}
	LIST_FOREACH_SAFE (cu, c->users, next) {
	    if (check_kick(cu->user, c->name)) {
		/* check_kick() will do the actual kick, but will not
		 * remove the user from the channel's user list or the
		 * user's channel list */
		argv[0] = c->name;
		argv[1] = cu->user->nick;
		argv[2] = CSAutokickReason;
		do_kick(s_ChanServ, 3, argv);
		count++;
	    }
	}

	notice_lang(s_ChanServ, u, CHAN_AKICK_ENFORCE_DONE, chan, count);

    } else if (stricmp(cmd, "COUNT") == 0) {
	int count = 0, i;
	ARRAY_FOREACH (i, ci->akick) {
	    if (ci->akick[i].mask)
		count++;
	}
        notice_lang(s_ChanServ, u, CHAN_AKICK_COUNT, ci->name, count);

    } else {
	syntax_error(s_ChanServ, u, "AKICK", CHAN_AKICK_SYNTAX);
    }
}

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

/* `last' is set to the last index this routine was called with */
static int akick_del(User *u, AutoKick *akick)
{
    if (!akick->mask)
	return 0;
    free(akick->mask);
    free(akick->reason);
    akick->mask = NULL;
    akick->reason = NULL;
    return 1;
}


static int akick_del_callback(User *u, int num, va_list args)
{
    ChannelInfo *ci = va_arg(args, ChannelInfo *);
    int *last = va_arg(args, int *);
    *last = num;
    if (num < 1 || num > ci->akick_count)
	return 0;
    return akick_del(u, &ci->akick[num-1]);
}

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

static int akick_list(User *u, int index, ChannelInfo *ci, int *sent_header,
		      int is_view)
{
    AutoKick *akick = &ci->akick[index];
    char buf[BUFSIZE];

    if (!akick->mask)
	return 0;
    if (!*sent_header) {
	notice_lang(s_ChanServ, u, CHAN_AKICK_LIST_HEADER, ci->name);
	*sent_header = 1;
    }
    if (akick->reason)
	snprintf(buf, sizeof(buf), " (%s)", akick->reason);
    else
	*buf = 0;
    if (is_view) {
	char setbuf[BUFSIZE], usedbuf[BUFSIZE];
	strftime_lang(setbuf, sizeof(setbuf), u->ngi,
		      STRFTIME_DATE_TIME_FORMAT, akick->set);
	if (akick->lastused) {
	    strftime_lang(usedbuf, sizeof(usedbuf), u->ngi,
			  STRFTIME_DATE_TIME_FORMAT, akick->lastused);
	    notice_lang(s_ChanServ, u, CHAN_AKICK_VIEW_FORMAT,
			index+1, akick->mask,
			akick->who[0] ? akick->who : "<unknown>",
			setbuf, usedbuf, buf);
	} else {
	    notice_lang(s_ChanServ, u, CHAN_AKICK_VIEW_UNUSED_FORMAT,
			index+1, akick->mask,
			akick->who[0] ? akick->who : "<unknown>", setbuf, buf);
	}
    } else {
	notice(s_ChanServ, u->nick, "  %3d %s%s", index+1, akick->mask, buf);
    }
    return 1;
}


static int akick_list_callback(User *u, int num, va_list args)
{
    ChannelInfo *ci = va_arg(args, ChannelInfo *);
    int *sent_header = va_arg(args, int *);
    int is_view = va_arg(args, int);
    if (num < 1 || num > ci->akick_count)
	return 0;
    return akick_list(u, num-1, ci, sent_header, is_view);
}

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