/*
 * Copyright (c) 2007 Jilles Tjoelker
 * Copyright (c) 2008 Robin Burchell
 * Rights to this code are as documented in doc/LICENSE.
 *
 * Services-side autojoin using SVSJOIN
 */

#include "atheme-compat.h"

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

static void
ns_cmd_ajoin_syntaxerr(sourceinfo_t *si)
{
	command_fail(si, fault_badparams, STR_INSUFFICIENT_PARAMS, "AJOIN");
	command_fail(si, fault_badparams, _("Syntax: AJOIN <LIST|ADD|DEL|CLEAR> [#channel]"));
}

static void
ns_cmd_ajoin(sourceinfo_t *si, int parc, char *parv[])
{
	char buf[512];
	char *chan;
	metadata_t *md;

	if (!parv[0])
		return ns_cmd_ajoin_syntaxerr(si);

	if (!strcasecmp(parv[0], "LIST"))
	{
		command_success_nodata(si, "\2AJOIN LIST\2:");

		if ((md = metadata_find(si->smu, "private:autojoin")))
		{
			mowgli_strlcpy(buf, md->value, sizeof buf);
			chan = strtok(buf, ",");

			while (chan != NULL)
			{
				command_success_nodata(si, "%s", chan);
				chan = strtok(NULL, ",");
			}
		}

		command_success_nodata(si, _("End of \2AJOIN LIST\2"));
	}
	else if (!strcasecmp(parv[0], "ADD"))
	{
		if (!parv[1])
			return ns_cmd_ajoin_syntaxerr(si);

		if ((md = metadata_find(si->smu, "private:autojoin")))
		{
			mowgli_strlcpy(buf, md->value, sizeof buf);
			chan = strtok(buf, ",");

			while (chan != NULL)
			{
				if (!strcasecmp(chan, parv[1]))
				{
					command_fail(si, fault_badparams, _("%s is already on your AJOIN list."), parv[1]);
					return;
				}

				chan = strtok(NULL, ",");
			}

			// Little arbitrary, but stop both overflow and RAM consumption going out of control
			if (strlen(md->value) + strlen(parv[1]) > 400)
			{
				command_fail(si, fault_badparams, _("Sorry, you have too many AJOIN entries set."));
				return;
			}

			mowgli_strlcpy(buf, md->value, sizeof buf);
			mowgli_strlcat(buf, ",", sizeof buf);
			mowgli_strlcat(buf, parv[1], sizeof buf);
			metadata_delete(si->smu, "private:autojoin");
			metadata_add(si->smu, "private:autojoin", buf);
		}
		else
		{
			metadata_add(si->smu, "private:autojoin", parv[1]);
		}

		command_success_nodata(si, _("%s added to AJOIN successfully."), parv[1]);
	}
	else if (!strcasecmp(parv[0], "CLEAR"))
	{
		metadata_delete(si->smu, "private:autojoin");
		command_success_nodata(si, _("AJOIN list cleared successfully."));
	}
	else if (!strcasecmp(parv[0], "DEL"))
	{
		if (!parv[1])
			return ns_cmd_ajoin_syntaxerr(si);

		if (!(md = metadata_find(si->smu, "private:autojoin")))
		{
			command_fail(si, fault_badparams, _("%s is not on your AJOIN list."), parv[1]);
			return;
		}

		// Thanks to John Brooks for his help with this.
		char *list = md->value;
		char *remove1 = parv[1];

		int listlen = 0;
		int rmlen = 0;
		int itempos = 0;
		int i = 0, j = 0;

		// This loop will find the item (if present), find the length of the item,
		// and find the length of the entire string.
		for (; list[i]; i++)
		{
			if (!rmlen)
			{
				// We have not found the string yet
				if (tolower(list[i]) == tolower(remove1[j]))
				{
					if (j == 0)
					{
						// First character of a potential match; remember it's location
						itempos = i;
					}

					j++;

					if (!remove1[j])
					{
						// Found the entire string
						rmlen = j;
					}
				}
				else
					j = 0;
			}
		}

		if (remove1[j])
		{
			command_fail(si, fault_badparams, _("%s is not on your AJOIN list."), parv[1]);
			return;
		}

		listlen = i;

		// listlen is the length of the list, rmlen is the length of the item to remove,
		// itempos is the beginning of that item.
		if (!list[itempos + rmlen])
		{
			// This item is the last item in the list, so we can simply truncate
			if (itempos > 0)
			{
				itempos--;
				list[itempos] = '\0';
			}
			else
				metadata_delete(si->smu, "private:autojoin");
		}
		else
		{
			// There are items after this one, so we must copy memory
			// Account for the comma following this item (if there is a space,
			// account for that too, depends on how you format your list)
			rmlen += 1;
			memmove(list + itempos, list + itempos + rmlen, listlen - rmlen - itempos);
			list[listlen - rmlen] = '\0';
		}

		command_success_nodata(si, _("%s removed from AJOIN successfully."), parv[1]);
	}
}

static void
ajoin_on_identify(user_t *u)
{
	myuser_t *mu = u->myuser;
	metadata_t *md;
	char buf[512];
	char *chan;

	if (!(md = metadata_find(mu, "private:autojoin")))
		return;

	mowgli_strlcpy(buf, md->value, sizeof buf);
	chan = strtok(buf, " ,");
	while (chan != NULL)
	{
		if(ircd->type == PROTOCOL_ELEMENTAL_IRCD)
		{
			sts(":%s ENCAP * SVSJOIN %s %s", ME, CLIENT_NAME(u), chan);
		}
		else
		{
			sts(":%s SVSJOIN %s %s", CLIENT_NAME(nicksvs.me->me), CLIENT_NAME(u), chan);
		}

		chan = strtok(NULL, ",");
	}
}

static command_t ns_ajoin = {
	.name           = "AJOIN",
	.desc           = N_("Manages automatic-join on identify."),
	.access         = AC_AUTHENTICATED,
	.maxparc        = 2,
	.cmd            = &ns_cmd_ajoin,
	.help           = { .path = "contrib/ajoin" },
};

static void
mod_init(module_t *const restrict m)
{
	hook_add_event("user_identify");
	hook_add_user_identify(ajoin_on_identify);

	service_named_bind_command("nickserv", &ns_ajoin);
}

static void
mod_deinit(const module_unload_intent_t intent)
{
	hook_del_user_identify(ajoin_on_identify);

	service_named_unbind_command("nickserv", &ns_ajoin);
}

SIMPLE_DECLARE_MODULE_V1("contrib/ns_ajoin", MODULE_UNLOAD_CAPABILITY_OK)
