/*
 * Copyright (c) 2005 William Pitcock <nenolod -at- nenolod.net>
 * Rights to this code are as documented in doc/LICENSE.
 *
 * Facilitates watching what channels a user joins.
 */

#include "atheme-compat.h"

struct joinmon_ {
	char *user;
	/* This module is Jamaican...mon. */
	time_t mon_ts;
	char *creator;
	char *reason;
};

typedef struct joinmon_ joinmon_t;

static mowgli_list_t os_monlist = { NULL, NULL, 0 };

static void
write_jmdb(database_handle_t *db)
{
	mowgli_node_t *n;

	MOWGLI_ITER_FOREACH(n, os_monlist.head)
	{
		joinmon_t *l = n->data;

		db_start_row(db, "JM");
		db_write_word(db, l->user);
		db_write_time(db, l->mon_ts);
		db_write_word(db, l->creator);
		db_write_str(db, l->reason);
		db_commit_row(db);
	}
}

static void
db_h_jm(database_handle_t *db, const char *type)
{
	const char *user = db_sread_word(db);
	time_t mon_ts = db_sread_time(db);
	const char *creator = db_sread_word(db);
	const char *reason = db_sread_str(db);

	joinmon_t *l = smalloc(sizeof(joinmon_t));
	l->user = sstrdup(user);
	l->mon_ts = mon_ts;
	l->creator = sstrdup(creator);
	l->reason = sstrdup(reason);
	mowgli_node_add(l, mowgli_node_create(), &os_monlist);
}

static void
watch_user_joins(hook_channel_joinpart_t *hdata)
{
	mowgli_node_t *n;
	chanuser_t *cu = hdata->cu;
	joinmon_t *l;

	if (cu == NULL)
		return;

	if (!(cu->user->server->flags & SF_EOB))
		return;

	MOWGLI_ITER_FOREACH(n, os_monlist.head)
	{
		l = n->data;

		/* Use match so you can monitor patterns like SomeBot* or
		 * t???h?????1
		 */
		if (!match(l->user, cu->user->nick))
		{
			/* Use LG_INFO because there's really no better logtype and creating
			 * one just for this module (ie: having to put stuff in core) is
			 * kind of stupid. Give it it's own logtype if logtypes are ever
			 * addable by modules.
			 */
			slog(LG_INFO, "JOINMON: \2%s\2 who matches \2%s\2 has joined \2%s\2",
					cu->user->nick, l->user, cu->chan->name);
			return;
		}
	}
}

static void
os_cmd_joinmon(sourceinfo_t *si, int parc, char *parv[])
{
	char *action = parv[0];
	char *pattern = parv[1];
	char *reason = parv[2];
	mowgli_node_t *n, *tn;
	joinmon_t *l;

	if (!action)
	{
		command_fail(si, fault_needmoreparams, STR_INSUFFICIENT_PARAMS, "JOINMON");
		command_fail(si, fault_needmoreparams, _("Syntax: JOINMON ADD|DEL|LIST [parameters]"));
		return;
	}

	if (!strcasecmp("ADD", action))
	{
		if (!pattern)
		{
			command_fail(si, fault_needmoreparams, STR_INSUFFICIENT_PARAMS, "JOINMON");
			command_fail(si, fault_needmoreparams, _("Syntax: JOINMON ADD <pattern> [reason]"));
			return;
		}

		if (si->smu == NULL)
		{
			command_fail(si, fault_noprivs, STR_NOT_LOGGED_IN);
			return;
		}

		/* search for it */
		MOWGLI_ITER_FOREACH(n, os_monlist.head)
		{
			l = n->data;

			if (!irccasecmp(l->user, pattern))
			{
				command_success_nodata(si, _("Pattern \2%s\2 is already being monitored."), pattern);
				return;
			}
		}

		l = smalloc(sizeof(joinmon_t));
		l->user = sstrdup(pattern);
		l->mon_ts = CURRTIME;;
		l->creator = sstrdup(get_source_name(si));

		if (reason)
		{
			l->reason = sstrdup(reason);
			logcommand(si, CMDLOG_ADMIN, "JOINMON:ADD: \2%s\2 (Reason: \2%s\2)", pattern, reason);
		}
		else
		{
			l->reason = _("None");
			logcommand(si, CMDLOG_ADMIN, "JOINMON:ADD: \2%s\2", pattern);
		}

		n = mowgli_node_create();
		mowgli_node_add(l, n, &os_monlist);

		command_success_nodata(si, _("\2%s\2 is now being monitored."), pattern);
		return;
	}
	else if (!strcasecmp("DEL", action))
	{
		if (!pattern)
		{
			command_fail(si, fault_needmoreparams, STR_INSUFFICIENT_PARAMS, "JOINMON");
			command_fail(si, fault_needmoreparams, _("Syntax: JOINMON DEL <pattern>"));
			return;
		}

		MOWGLI_ITER_FOREACH_SAFE(n, tn, os_monlist.head)
		{
			l = n->data;

			if (!irccasecmp(l->user, pattern))
			{
				logcommand(si, CMDLOG_ADMIN, "JOINMON:DEL: \2%s\2", l->user);

				mowgli_node_delete(n, &os_monlist);

				sfree(l->user);
				sfree(l->creator);
				sfree(l->reason);
				sfree(l);

				return;
			}
		}
		command_success_nodata(si, _("Pattern \2%s\2 not found in joinmon database."), pattern);
		return;
	}
	else if (!strcasecmp("LIST", action))
	{
		char buf[BUFSIZE];
		struct tm tm;

		MOWGLI_ITER_FOREACH(n, os_monlist.head)
		{
			l = n->data;

			tm = *localtime(&l->mon_ts);
			strftime(buf, BUFSIZE, TIME_FORMAT, &tm);
			command_success_nodata(si, "Pattern: \2%s\2, Reason: \2%s\2 (%s - %s)",
				l->user, l->reason, l->creator, buf);
		}
		command_success_nodata(si, "End of list.");
		logcommand(si, CMDLOG_GET, "JOINMON:LIST");
		return;
	}
	else
	{
		command_fail(si, fault_badparams, STR_INVALID_PARAMS, "JOINMON");
		return;
	}
}

static command_t os_joinmon = {
	.name           = "JOINMON",
	.desc           = N_("Monitors what channels a user is joining."),
	.access         = PRIV_USER_ADMIN,
	.maxparc        = 3,
	.cmd            = &os_cmd_joinmon,
	.help           = { .path = "contrib/joinmon" },
};

static void
mod_init(module_t *const restrict m)
{
	if (!module_find_published("backend/opensex"))
	{
		slog(LG_INFO, "Module %s requires use of the OpenSEX database backend, refusing to load.", m->name);
		m->mflags |= MODFLAG_FAIL;
		return;
	}

	hook_add_event("channel_join");
	hook_add_channel_join(watch_user_joins);
	hook_add_db_write(write_jmdb);

	db_register_type_handler("JM", db_h_jm);

	service_named_bind_command("operserv", &os_joinmon);
}

static void
mod_deinit(const module_unload_intent_t intent)
{
	hook_del_channel_join(watch_user_joins);
	hook_del_db_write(write_jmdb);

	db_unregister_type_handler("JM");

	service_named_unbind_command("operserv", &os_joinmon);
}

SIMPLE_DECLARE_MODULE_V1("contrib/os_joinmon", MODULE_UNLOAD_CAPABILITY_NEVER)
