/* SJOIN-related functions.
 *
 * 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.
 */

/* If this file is compiled with UNREAL_HACK defined, the create-channel
 * callback, do_channel_create(), will be modified to force Unreal to
 * update channel times properly.  Note that this will cause the exported
 * functions (do_sjoin, init_sjoin, and exit_sjoin) to have "_unreal"
 * appended to their names; sjoin.h performs the renaming using #defines
 * if UNREAL_HACK is defined.
 *
 * Defining BAHAMUT_HACK does the same thing for Bahamut.
 */

#include "services.h"
#include "modules.h"
#include "modules/chanserv/chanserv.h"

#include "sjoin.h"

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

static Module *module;
static Module *module_database;

int CSSetChannelTime;

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

/* Handle an SJOIN command.
 * Bahamut no-SSJOIN format:
 *	av[0] = TS3 timestamp
 *	av[1] = TS3 timestamp - channel creation time
 *	av[2] = channel
 *	av[3] = channel modes
 *	av[4] = limit / key (depends on modes in av[3])
 *	av[5] = limit / key (depends on modes in av[3])
 *	av[ac-1] = nickname(s), with modes, joining channel
 * Bahamut SSJOIN (server source) / Hybrid / PTlink / Unreal SJOIN2/SJ3 format:
 *	av[0] = TS3 timestamp - channel creation time
 *	av[1] = channel
 *	av[2] = modes (limit/key in av[3]/av[4] as needed)
 *	av[ac-1] = users
 *      (Note that Unreal may omit the modes if there aren't any.
 *       Unreal SJ3 also includes bans and exceptions in av[ac-1]
 *       with prefix characters of & and " respectively.)
 * Bahamut SSJOIN format (client source):
 *	av[0] = TS3 timestamp - channel creation time
 *	av[1] = channel
 */

void do_sjoin(const char *source, int ac, char **av)
{
    User *user;
    char *t, *nick;
    char *channel;
    Channel *c = NULL, *ctemp;

    if (isdigit(av[1][0])) {
	/* Plain SJOIN format, zap join timestamp */
	memmove(&av[0], &av[1], sizeof(char *) * (ac-1));
	ac--;
    }

    channel = av[1];
    if (ac >= 3) {
	/* SJOIN from server: nick list in last param */
	t = av[ac-1];
    } else {
	/* SJOIN for a single client: source is nick */
	/* We assume the nick has no spaces, so we can discard const */
	if (strchr(source, ' '))
	    fatal("sjoin: source nick contains spaces!");
	t = (char *)source;
    }
    while (*(nick=t)) {
	int32 modes = 0, thismode;

        t = nick + strcspn(nick, " ");
	if (*t)
	    *t++ = 0;
	if (*nick == '&' || *nick == '"') {
	    /* Ban (&) or exception (") */
	    char *av[3];
	    av[0] = channel;
	    av[1] = (char *)((*nick=='&') ? "+b" : "+e");
	    av[2] = nick+1;
	    do_cmode(source, 3, av);
	    continue;
	}
	do {
	    thismode = cumode_prefix_to_flag(*nick);
	    modes |= thismode;
	} while (thismode && *nick++); /* increment nick only if thismode!=0 */
	user = get_user(nick);
	if (!user) {
	    module_log("sjoin: SJOIN to channel %s for non-existent nick %s"
		       " (%s)", channel, nick, merge_args(ac-1, av));
	    continue;
	}
	if (debug)
	    module_log("debug: %s SJOINs %s", nick, channel);
	if ((ctemp = join_channel(user, channel, modes)) != NULL)
	    c = ctemp;
    }

    /* Did anyone actually join the channel? */
    if (c) {
	/* Store channel timestamp in channel structure, unless we set it
	 * ourselves. */
	if (!c->ci)
	    c->creation_time = strtotime(av[0], NULL);
	/* Set channel modes if there are any.  Note how argument list is
	 * conveniently set up for do_cmode(). */
	if (ac > 3)
	    do_cmode(source, ac-2, av+1);
    }
}

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

/* Clear out all channel modes using an SJOIN (for CLEAR_USERS). */

static int sjoin_clear_users(const char *sender, Channel *chan, int what,
			     const void *param)
{
    if (what & CLEAR_USERS) {
	int i;
	send_cmd(ServerName, "SJOIN %ld %s + :",
		 (long)(chan->creation_time - 1), chan->name);
	ARRAY_FOREACH (i, chan->excepts)
	    free(chan->excepts[i]);
	chan->excepts_count = 0;
    }
    return 0;
}

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

/* Callback to set the creation time for a registered channel to the
 * channel's registration time.  This callback is added before the main
 * ChanServ module is loaded, so c->ci will not yet be set.
 */

static typeof(get_channelinfo) *p_get_channelinfo = NULL;

static int do_channel_create(Channel *c, User *u, int32 modes)
{
    if (CSSetChannelTime && p_get_channelinfo) {
	ChannelInfo *ci = p_get_channelinfo(c->name);
	if (ci) {
	    c->creation_time = ci->time_registered;
#ifdef UNREAL_HACK
	    /* NOTE: this is a bit of a kludge, since Unreal's SJOIN
	     * doesn't let us set just the channel creation time while
	     * leaving the modes alone. */
	    send_cmd(ServerName, "SJOIN %ld %s %co %s :",
		     (long)c->creation_time, c->name,
		     (modes & CUMODE_o ? '+' : '-'), u->nick);
#else
	    send_cmd(ServerName, "SJOIN %ld %s + :%s%s",
		     (long)c->creation_time, c->name,
		     (modes & CUMODE_o ? "@" : ""), u->nick);
#endif
#ifdef BAHAMUT_HACK
	    if (modes & CUMODE_o) {
		/* Bahamut ignores users in the user list which aren't on
		 * or behind the server sending the SJOIN, so we need an
		 * extra MODE to explicitly give ops back to the initial
		 * joining user. */
		send_cmode_cmd(ServerName, c->name, "+o :%s", u->nick);
	    }
#endif
	}
    }
    return 0;
}

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

/* Callback to watch for modules being loaded. */

static int do_load_module(Module *mod, const char *name)
{
    if (strncmp(name, "database/", 9) == 0) {
	module_database = mod;
	p_get_channelinfo = get_module_symbol(NULL, "get_channelinfo");
	if (!p_get_channelinfo)
	    module_log("sjoin: unable to resolve symbol `get_channelinfo' in"
		       " database module, channel time setting disabled");
    }
    return 0;
}

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

/* Callback to watch for modules being unloaded. */

static int do_unload_module(Module *mod)
{
    if (mod == module_database) {
	p_get_channelinfo = NULL;
	module_database = NULL;
    }
    return 0;
}

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

/* Initialization. */

int init_sjoin(Module *module_)
{
    module = module_;
    if (!add_callback(NULL, "load module", do_load_module)
     || !add_callback(NULL, "unload module", do_unload_module)
     || !add_callback(NULL, "channel create", do_channel_create)
     || !add_callback(NULL, "clear channel", sjoin_clear_users)
    ) {
	module_log("sjoin: Unable to add callbacks");
	exit_sjoin();
	return 0;
    }
    return 1;
}

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

/* Cleanup. */

void exit_sjoin(void)
{
    remove_callback(NULL, "clear channel", sjoin_clear_users);
    remove_callback(NULL, "channel create", do_channel_create);
    remove_callback(NULL, "unload module", do_unload_module);
    remove_callback(NULL, "load module", do_load_module);
}

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