/*
 * Copyright (c) 2007 Jilles Tjoelker, et al.
 * Rights to this code are as documented in doc/LICENSE.
 *
 * ChanServ ACCESS command
 */

#include "atheme-compat.h"

#if (CURRENT_ABI_REVISION < 730000)
#  include "template.h"
#endif

typedef struct {
	const char *res;
	unsigned int level;
} template_iter_t;

static void
compat_cmd(sourceinfo_t *si, const char *cmdname, char *channel, char *arg1, char *arg2, char *arg3)
{
	int newparc;
	char *newparv[5];
	command_t *cmd;

	newparv[0] = channel;
	newparv[1] = arg1;
	newparv[2] = arg2;
	newparv[3] = arg3;
	newparv[4] = NULL;
	/* this assumes arg3!=NULL implies arg2!=NULL implies arg1!=NULL */
	newparc = 1 + (arg1 != NULL) + (arg2 != NULL) + (arg3 != NULL);
	cmd = command_find(si->service->commands, cmdname);
	if (cmd != NULL)
		command_exec(si->service, si, cmd, newparc, newparv);
	else
		command_fail(si, fault_unimplemented, _("Command \2%s\2 not loaded?"), cmdname);
}

static int
global_template_search(const char *key, void *data, void *privdata)
{
	template_iter_t *iter = privdata;
	default_template_t *def_t = data;

	if (def_t->flags == iter->level)
		iter->res = key;

	return 0;
}

static const char *
get_template_name(mychan_t *mc, unsigned int level)
{
	metadata_t *md;
	const char *p, *q, *r;
	char *s;
	char ss[40];
	static char flagname[400];
	template_iter_t iter;

	md = metadata_find(mc, "private:templates");
	if (md != NULL)
	{
		p = md->value;
		while (p != NULL)
		{
			while (*p == ' ')
				p++;
			q = strchr(p, '=');
			if (q == NULL)
				break;
			r = strchr(q, ' ');
			if (r != NULL && r < q)
				break;
			mowgli_strlcpy(ss, q, sizeof ss);
			if (r != NULL && r - q < (int)(sizeof ss - 1))
			{
				ss[r - q] = '\0';
			}
			if (level == flags_to_bitmask(ss, 0))
			{
				mowgli_strlcpy(flagname, p, sizeof flagname);
				s = strchr(flagname, '=');
				if (s != NULL)
					*s = '\0';
				return flagname;
			}
			p = r;
		}
	}

	iter.res = NULL;
	iter.level = level;
	mowgli_patricia_foreach(global_template_dict, global_template_search, &iter);

	return iter.res;
}

static void
access_list(sourceinfo_t *si, mychan_t *mc, int parc, char *parv[])
{
	mowgli_node_t *n;
	chanacs_t *ca;
	const char *str1, *str2;
	int i = 1;
	bool operoverride = false;

	/* Copied from modules/chanserv/flags.c */
	/* Note: This overrides the normal need of +A access unless private */
	if (use_channel_private && mc->flags & MC_PRIVATE &&
			!chanacs_source_has_flag(mc, si, CA_ACLVIEW))
	{
		if (has_priv(si, PRIV_CHAN_AUSPEX))
			operoverride = true;
		else
		{
			command_fail(si, fault_noprivs, STR_NOT_AUTHORIZED);
			return;
		}
	}

	command_success_nodata(si, _("Entry Nickname/Host          Flags"));
	command_success_nodata(si, "----- ---------------------- -----");

	MOWGLI_ITER_FOREACH(n, mc->chanacs.head)
	{
		ca = n->data;
		/* Change: don't show akicks */
		if (ca->level == CA_AKICK)
			continue;
		str1 = get_template_name(mc, ca->level);
		str2 = ca->tmodified ? time_ago(ca->tmodified) : "?";
		if (str1 != NULL)
			command_success_nodata(si, _("%-5d %-22s %s (%s) [modified %s ago]"), i, ca->entity ? ca->entity->name : ca->host, bitmask_to_flags(ca->level), str1,
				str2);
		else
			command_success_nodata(si, _("%-5d %-22s %s [modified %s ago]"), i, ca->entity ? ca->entity->name : ca->host, bitmask_to_flags(ca->level),
				str2);
		i++;
	}

	command_success_nodata(si, "----- ---------------------- -----");
	command_success_nodata(si, _("End of \2%s\2 FLAGS listing."), mc->name);

	if (operoverride)
		logcommand(si, CMDLOG_ADMIN, "%s ACCESS LIST (oper override)", mc->name);
	else
		logcommand(si, CMDLOG_GET, "%s ACCESS LIST", mc->name);
}

static void
cs_cmd_access(sourceinfo_t *si, int parc, char *parv[])
{
	char *chan, *cmd;
	mychan_t *mc;
	char killit[] = "-*";
	char deftemplate[] = "OP";
	char defaccess[] = "=votirA";

	if (parc < 2)
	{
		command_fail(si, fault_needmoreparams, STR_INSUFFICIENT_PARAMS, "ACCESS");
		command_fail(si, fault_needmoreparams, _("Syntax: ACCESS <#channel> ADD|DEL|LIST [nickname] [level]"));
		return;
	}
	if (parv[0][0] == '#')
		chan = parv[0], cmd = parv[1];
	else if (parv[1][0] == '#')
		cmd = parv[0], chan = parv[1];
	else
	{
		command_fail(si, fault_badparams, STR_INVALID_PARAMS, "ACCESS");
		command_fail(si, fault_badparams, _("Syntax: ACCESS <#channel> ADD|DEL|LIST [nickname] [level]"));
		return;
	}

	mc = mychan_find(chan);
	if (mc == NULL)
	{
		command_fail(si, fault_nosuch_target, STR_IS_NOT_REGISTERED, chan);
		return;
	}

	if (!strcasecmp(cmd, "LIST"))
		access_list(si, mc, parc - 2, parv + 2);
	else if (parc < 3)
	{
		command_fail(si, fault_needmoreparams, STR_INSUFFICIENT_PARAMS, "ACCESS");
		command_fail(si, fault_needmoreparams, _("Syntax: ACCESS <#channel> ADD|DEL <nickname> [level]"));
		return;
	}
	else if (!strcasecmp(cmd, "ADD"))
		compat_cmd(si, "FLAGS", chan, parv[2], parc > 3 ? parv[3] : (get_template_flags(mc, deftemplate) ? deftemplate : defaccess), NULL);
	else if (!strcasecmp(cmd, "DEL"))
		compat_cmd(si, "FLAGS", chan, parv[2], killit, NULL);
	else
		command_fail(si, fault_badparams, _("Invalid command. Use \2/msg %s HELP\2 for a command listing."), si->service->disp);
}

static command_t cs_access = {
	.name           = "ACCESS",
	.desc           = N_("Manipulates channel access lists."),
	.access         = AC_NONE,
	.maxparc        = 4,
	.cmd            = &cs_cmd_access,
	.help           = { .path = "contrib/access" },
};

static void
mod_init(module_t *const restrict m)
{
	service_named_bind_command("chanserv", &cs_access);
}

static void
mod_deinit(const module_unload_intent_t intent)
{
	service_named_unbind_command("chanserv", &cs_access);
}

VENDOR_DECLARE_MODULE_V1("contrib/cs_access_alias", MODULE_UNLOAD_CAPABILITY_OK, CONTRIB_VENDOR_FREENODE)
