/*
 massjoin.c : irssi

    Copyright (C) 1999-2000 Timo Sirainen

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License along
    with this program; if not, write to the Free Software Foundation, Inc.,
    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/

#include "module.h"
#include <irssi/src/core/signals.h>
#include <irssi/src/core/settings.h>

#include <irssi/src/irc/core/irc-servers.h>
#include <irssi/src/irc/core/irc-channels.h>
#include <irssi/src/irc/core/irc-nicklist.h>

static int massjoin_tag;
static int massjoin_max_joins;

/* Massjoin support - really useful when trying to do things (like op/deop)
   to people after netjoins. It sends
   "massjoin #channel nick!user@host nick2!user@host ..." signals */
static void event_join(IRC_SERVER_REC *server, const char *data,
		       const char *nick, const char *address)
{
	char *params, *channel, *account, *realname, *ptr;
	IRC_CHANNEL_REC *chanrec;
	NICK_REC *nickrec;
	GSList *nicks, *tmp;
	gboolean send_massjoin;

	g_return_if_fail(data != NULL);

	params = event_get_params(data, 3, &channel, &account, &realname);

	ptr = strchr(channel, 7); /* ^G does something weird.. */
	if (ptr != NULL) *ptr = '\0';

	/* find channel */
	chanrec = irc_channel_find(server, channel);
	if (chanrec == NULL) {
		g_free(params);
		return;
	}

	if (g_ascii_strcasecmp(nick, server->nick) == 0) {
		/* do not overwrite our /UPGRADEd ownnick */
		if (chanrec->session_rejoin) {
			g_free(params);
			return;
		} else {
			/* You joined, do not massjoin */
			send_massjoin = FALSE;
		}
	} else {
		send_massjoin = TRUE;
	}

	/* check that the nick isn't already in nicklist. seems to happen
	   sometimes (server desyncs or something?) */
	nickrec = nicklist_find(CHANNEL(chanrec), nick);
	if (nickrec != NULL) {
		/* destroy the old record */
		nicklist_remove(CHANNEL(chanrec), nickrec);
	}

	/* add user to nicklist */
	nickrec = irc_nicklist_insert(chanrec, nick, FALSE, FALSE, FALSE, send_massjoin, NULL);
	if (nickrec == NULL) {
		/* invalid nick? */
		g_free(params);
		return;
	}
	if (*account != '\0' && g_strcmp0(nickrec->account, account) != 0) {
		nicklist_set_account(CHANNEL(chanrec), nickrec, account);
	}

        nicklist_set_host(CHANNEL(chanrec), nickrec, address);

	if (send_massjoin && chanrec->massjoins == 0) {
		/* no nicks waiting in massjoin queue */
		chanrec->massjoin_start = time(NULL);
		chanrec->last_massjoins = 0;
	}

	if (nickrec->realname == NULL) {
		/* Check if user is already in some other channel,
		   get the realname and other stuff from there */
		nicks = nicklist_get_same(SERVER(server), nick);
		for (tmp = nicks; tmp != NULL; tmp = tmp->next->next) {
			NICK_REC *rec = tmp->next->data;

			if (rec->realname != NULL) {
				nickrec->last_check = rec->last_check;
				nickrec->realname = g_strdup(rec->realname);
				nickrec->gone = rec->gone;
				nickrec->serverop = rec->serverop;
				break;
			}
		}
		g_slist_free(nicks);
	}

	if (*realname != '\0' && g_strcmp0(nickrec->realname, realname) != 0) {
		g_free(nickrec->realname);
		nickrec->realname = g_strdup(realname);
	}

	if (send_massjoin) {
		chanrec->massjoins++;
	}
	g_free(params);
}

static void event_chghost(IRC_SERVER_REC *server, const char *data,
			  const char *nick, const char *old_address)
{
	char *params, *user, *host, *address;
	GSList *nicks, *tmp;

	g_return_if_fail(nick != NULL);
	g_return_if_fail(data != NULL);

	params = event_get_params(data, 2, &user, &host);

	/* check that the nick isn't already in nicklist. seems to happen
	   sometimes (server desyncs or something?) */
	nicks = nicklist_get_same(SERVER(server), nick);
	address = nicks != NULL ? g_strconcat(user, "@", host, NULL) : NULL;
	for (tmp = nicks; tmp != NULL; tmp = tmp->next->next) {
		NICK_REC *rec = tmp->next->data;

		nicklist_set_host(CHANNEL(tmp->data), rec, address);
	}
	g_free(address);
	g_slist_free(nicks);
	g_free(params);
}

static void event_account(IRC_SERVER_REC *server, const char *data,
			  const char *nick, const char *address)
{
	char *params, *account;
	GSList *nicks, *tmp;

	g_return_if_fail(nick != NULL);
	g_return_if_fail(data != NULL);

	params = event_get_params(data, 1, &account);
	nicks = nicklist_get_same(SERVER(server), nick);
	for (tmp = nicks; tmp != NULL; tmp = tmp->next->next) {
		NICK_REC *rec = tmp->next->data;

		nicklist_set_account(CHANNEL(tmp->data), rec, account);
	}
	g_slist_free(nicks);
	g_free(params);
}

static void event_part(IRC_SERVER_REC *server, const char *data,
		       const char *nick, const char *addr)
{
	char *params, *channel, *reason;
	IRC_CHANNEL_REC *chanrec;
	NICK_REC *nickrec;

	g_return_if_fail(data != NULL);

	if (g_ascii_strcasecmp(nick, server->nick) == 0) {
		/* you left channel, no need to do anything here */
		return;
	}

	params = event_get_params(data, 2, &channel, &reason);

	/* find channel */
	chanrec = irc_channel_find(server, channel);
	if (chanrec == NULL) {
		g_free(params);
		return;
	}

	/* remove user from nicklist */
	nickrec = nicklist_find(CHANNEL(chanrec), nick);
	if (nickrec != NULL) {
		if (nickrec->send_massjoin) {
			/* quick join/part after which it's useless to send
			   nick in massjoin */
			chanrec->massjoins--;
		}
		nicklist_remove(CHANNEL(chanrec), nickrec);
	}
	g_free(params);
}

static void event_quit(IRC_SERVER_REC *server, const char *data,
		       const char *nick)
{
        IRC_CHANNEL_REC *channel;
	NICK_REC *nickrec;
	GSList *nicks, *tmp;

	g_return_if_fail(data != NULL);

	if (g_ascii_strcasecmp(nick, server->nick) == 0) {
		/* you quit, don't do anything here */
		return;
	}

	/* Remove nick from all channels */
	nicks = nicklist_get_same(SERVER(server), nick);
	for (tmp = nicks; tmp != NULL; tmp = tmp->next->next) {
                channel = tmp->data;
		nickrec = tmp->next->data;

		if (nickrec->send_massjoin) {
			/* quick join/quit after which it's useless to
			   send nick in massjoin */
			channel->massjoins--;
		}
		nicklist_remove(CHANNEL(channel), nickrec);
	}
	g_slist_free(nicks);

	/* invalidate any outstanding accountqueries for the nick */
	irc_channels_query_purge_accountquery(server, nick);
}

static void event_kick(IRC_SERVER_REC *server, const char *data)
{
	char *params, *channel, *nick, *reason;
	IRC_CHANNEL_REC *chanrec;
	NICK_REC *nickrec;

	g_return_if_fail(data != NULL);

	params = event_get_params(data, 3, &channel, &nick, &reason);

	if (g_ascii_strcasecmp(nick, server->nick) == 0) {
		/* you were kicked, no need to do anything */
		g_free(params);
		return;
	}

	/* Remove user from nicklist */
	chanrec = irc_channel_find(server, channel);
	nickrec = chanrec == NULL ? NULL :
		nicklist_find(CHANNEL(chanrec), nick);

	if (chanrec != NULL && nickrec != NULL) {
		if (nickrec->send_massjoin) {
			/* quick join/kick after which it's useless to
			   send nick in massjoin */
			chanrec->massjoins--;
		}
		nicklist_remove(CHANNEL(chanrec), nickrec);
	}

	g_free(params);
}

static void massjoin_send_hash(gpointer key, NICK_REC *nick, GSList **list)
{
	if (nick->send_massjoin) {
		nick->send_massjoin = FALSE;
		*list = g_slist_append(*list, nick);
	}
}

/* Send channel's massjoin list signal */
static void massjoin_send(IRC_CHANNEL_REC *channel)
{
	GSList *list;

	list = NULL;
	g_hash_table_foreach(channel->nicks, (GHFunc) massjoin_send_hash, &list);

	channel->massjoins = 0;
	signal_emit("massjoin", 2, channel, list);
	g_slist_free(list);
}

static void server_check_massjoins(IRC_SERVER_REC *server, time_t max)
{
	GSList *tmp;

	/*
	   1) First time always save massjoin count to last_massjoins
	   2) Next time check if there's been less than massjoin_max_joins
	      (yes, the name is misleading..) joins since previous check.
	        yes) send a massjoin signal and reset last_massjoin count
	        no) unless we've waited for massjoin_max_wait seconds already,
		    goto 2.

	   So, with single joins the massjoin signal is sent 1-2 seconds after
	   the join.
	*/

	/* Scan all channels through for massjoins */
	for (tmp = server->channels; tmp != NULL; tmp = tmp->next) {
		IRC_CHANNEL_REC *rec = tmp->data;

		if (!IS_IRC_CHANNEL(rec) || rec->massjoins <= 0)
			continue;

		if (rec->massjoin_start < max || /* We've waited long enough */
		    (rec->last_massjoins > 0 &&
		     rec->massjoins-massjoin_max_joins < rec->last_massjoins)) { /* Less than x joins since last check */
			/* send them */
			massjoin_send(rec);
		} else {
			/* Wait for some more.. */
			rec->last_massjoins = rec->massjoins;
		}
	}

}

static int sig_massjoin_timeout(void)
{
	GSList *tmp;
	time_t max;

	max = time(NULL)-settings_get_int("massjoin_max_wait");
	for (tmp = servers; tmp != NULL; tmp = tmp->next) {
		IRC_SERVER_REC *server = tmp->data;

                if (IS_IRC_SERVER(server))
			server_check_massjoins(server, max);
	}

	return 1;
}

static void read_settings(void)
{
	massjoin_max_joins = settings_get_int("massjoin_max_joins");
}

void massjoin_init(void)
{
        settings_add_int("misc", "massjoin_max_wait", 5000);
        settings_add_int("misc", "massjoin_max_joins", 3);
	massjoin_tag = g_timeout_add(1000, (GSourceFunc) sig_massjoin_timeout, NULL);

	read_settings();
	signal_add_first("event join", (SIGNAL_FUNC) event_join);
	signal_add("event chghost", (SIGNAL_FUNC) event_chghost);
	signal_add("event account", (SIGNAL_FUNC) event_account);
	signal_add("event part", (SIGNAL_FUNC) event_part);
	signal_add("event kick", (SIGNAL_FUNC) event_kick);
	signal_add("event quit", (SIGNAL_FUNC) event_quit);
	signal_add("setup changed", (SIGNAL_FUNC) read_settings);
}

void massjoin_deinit(void)
{
	g_source_remove(massjoin_tag);

	signal_remove("event join", (SIGNAL_FUNC) event_join);
	signal_remove("event chghost", (SIGNAL_FUNC) event_chghost);
	signal_remove("event account", (SIGNAL_FUNC) event_account);
	signal_remove("event part", (SIGNAL_FUNC) event_part);
	signal_remove("event kick", (SIGNAL_FUNC) event_kick);
	signal_remove("event quit", (SIGNAL_FUNC) event_quit);
	signal_remove("setup changed", (SIGNAL_FUNC) read_settings);
}
