/*
 fe-netsplit.c : irssi

    Copyright (C) 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/fe-common/irc/module-formats.h>
#include <irssi/src/core/signals.h>
#include <irssi/src/core/levels.h>
#include <irssi/src/core/settings.h>

#include <irssi/src/irc/core/irc-servers.h>
#include <irssi/src/irc/core/irc-commands.h>
#include <irssi/src/core/ignore.h>
#include <irssi/src/irc/core/netsplit.h>

#include <irssi/src/fe-common/core/printtext.h>

#define SPLIT_WAIT_TIME 5 /* how many seconds to wait for the QUIT split messages to stop */

static int split_tag;
static int netsplit_max_nicks, netsplit_nicks_hide_threshold;
static int printing_splits;

static int get_last_split(IRC_SERVER_REC *server)
{
	GSList *tmp;
	time_t last;

        last = 0;
	for (tmp = server->split_servers; tmp != NULL; tmp = tmp->next) {
		NETSPLIT_SERVER_REC *rec = tmp->data;

		if (rec->last > last) last = rec->last;
	}

	return last;
}

typedef struct {
	char *name;
	int nick_count, maxnickpos;
	GString *nicks;
} TEMP_SPLIT_CHAN_REC;

typedef struct {
        IRC_SERVER_REC *server_rec;
	GSList *servers; /* if many servers splitted from the same one */
	GSList *channels;
} TEMP_SPLIT_REC;

static GSList *get_source_servers(const char *server, GSList **servers)
{
	GSList *list, *next, *tmp;

	list = NULL;
	for (tmp = *servers; tmp != NULL; tmp = next) {
		NETSPLIT_SERVER_REC *rec = tmp->data;
		next = tmp->next;

		if (g_ascii_strcasecmp(rec->server, server) == 0) {
			rec->prints = 0;
			list = g_slist_append(list, rec);
			*servers = g_slist_remove(*servers, rec);
		}
	}

	return list;
}

static TEMP_SPLIT_CHAN_REC *find_split_chan(TEMP_SPLIT_REC *rec,
					    const char *name)
{
	GSList *tmp;

	for (tmp = rec->channels; tmp != NULL; tmp = tmp->next) {
		TEMP_SPLIT_CHAN_REC *chanrec = tmp->data;

		if (g_ascii_strcasecmp(chanrec->name, name) == 0)
			return chanrec;
	}

	return NULL;
}

static void get_server_splits(void *key, NETSPLIT_REC *split,
			      TEMP_SPLIT_REC *rec)
{
	TEMP_SPLIT_CHAN_REC *chanrec;
	GSList *tmp;

	if (split->printed ||
	    g_slist_find(rec->servers, split->server) == NULL)
		return;

	split->printed = TRUE;
	for (tmp = split->channels; tmp != NULL; tmp = tmp->next) {
		NETSPLIT_CHAN_REC *splitchan = tmp->data;

		if (ignore_check(SERVER(rec->server_rec), split->nick,
				 split->address, splitchan->name, "",
				 MSGLEVEL_QUITS))
			continue;

		chanrec = find_split_chan(rec, splitchan->name);
		if (chanrec == NULL) {
			chanrec = g_new0(TEMP_SPLIT_CHAN_REC, 1);
			chanrec->name = splitchan->name;
			chanrec->nicks = g_string_new(NULL);

			rec->channels = g_slist_append(rec->channels, chanrec);
		}

		split->server->prints++;
		chanrec->nick_count++;
		if (netsplit_nicks_hide_threshold <= 0 ||
		    chanrec->nick_count <= netsplit_nicks_hide_threshold) {
			if (splitchan->op)
				g_string_append_c(chanrec->nicks, '@');
			else if (splitchan->voice)
				g_string_append_c(chanrec->nicks, '+');
			g_string_append_printf(chanrec->nicks, "%s, ", split->nick);

			if (chanrec->nick_count == netsplit_max_nicks)
                                chanrec->maxnickpos = chanrec->nicks->len;
		}
	}
}

static void print_server_splits(IRC_SERVER_REC *server, TEMP_SPLIT_REC *rec, const char *filter_channel)
{
	GString *destservers;
	char *sourceserver;
	GSList *tmp;

	g_return_if_fail(rec->servers != NULL);

	destservers = g_string_new(NULL);
	for (tmp = rec->servers; tmp != NULL; tmp = tmp->next) {
		NETSPLIT_SERVER_REC *rec = tmp->data;

		if (rec->prints > 0) {
			g_string_append_printf(destservers, "%s, ",
					  rec->destserver);
		}
	}
	if (destservers->len == 0) {
                /* no nicks to print in this server */
		g_string_free(destservers, TRUE);
		return;
	}
	g_string_truncate(destservers, destservers->len-2);

	sourceserver = ((NETSPLIT_SERVER_REC *) (rec->servers->data))->server;
	for (tmp = rec->channels; tmp != NULL; tmp = tmp->next) {
		TEMP_SPLIT_CHAN_REC *chan = tmp->data;

		if (filter_channel != NULL &&
		    strcasecmp(chan->name, filter_channel) != 0)
			continue;

		g_string_truncate(chan->nicks, chan->nicks->len-2);

		if (netsplit_max_nicks > 0 &&
		    chan->nick_count > netsplit_max_nicks) {
			g_string_truncate(chan->nicks, chan->maxnickpos);
			printformat(server, chan->name, MSGLEVEL_QUITS,
				    IRCTXT_NETSPLIT_MORE, sourceserver,
				    destservers->str, chan->nicks->str,
				    chan->nick_count - netsplit_max_nicks);
		} else {
			printformat(server, chan->name, MSGLEVEL_QUITS,
				    IRCTXT_NETSPLIT, sourceserver,
				    destservers->str, chan->nicks->str);
		}
	}

	g_string_free(destservers, TRUE);
}

static void temp_split_chan_free(TEMP_SPLIT_CHAN_REC *rec)
{
	g_string_free(rec->nicks, TRUE);
	g_free(rec);
}

static void print_splits(IRC_SERVER_REC *server, const char *filter_channel)
{
	TEMP_SPLIT_REC temp;
	GSList *servers;

	printing_splits = TRUE;

	servers = g_slist_copy(server->split_servers);
	while (servers != NULL) {
		NETSPLIT_SERVER_REC *sserver = servers->data;

		/* get all the splitted servers that have the same
		   source server */
                temp.servers = get_source_servers(sserver->server, &servers);
                temp.server_rec = server;
		temp.channels = NULL;

		g_hash_table_foreach(server->splits,
				     (GHFunc) get_server_splits, &temp);
		print_server_splits(server, &temp, filter_channel);

		g_slist_foreach(temp.channels,
				(GFunc) temp_split_chan_free, NULL);
		g_slist_free(temp.servers);
		g_slist_free(temp.channels);
	}

	printing_splits = FALSE;
}

static int check_server_splits(IRC_SERVER_REC *server)
{
	time_t last;

	g_return_val_if_fail(IS_IRC_SERVER(server), FALSE);

	last = get_last_split(server);
	if (time(NULL)-last < SPLIT_WAIT_TIME)
		return FALSE;

	print_splits(server, NULL);
        return TRUE;
}

/* something is going to be printed to screen, print our current netsplit
   message before it. */
static void sig_print_starting(TEXT_DEST_REC *dest)
{
	IRC_SERVER_REC *rec;

	if (printing_splits)
		return;

	if (!IS_IRC_SERVER(dest->server))
		return;

	rec = IRC_SERVER(dest->server);
	if (rec->split_servers != NULL) {
		/* if split_servers exists, the server rec should be
		   still valid. otherwise, calling server->ischannel
		   may not be safe. */
		if (dest->target != NULL && !server_ischannel((SERVER_REC *) rec, dest->target))
			return;

		print_splits(rec, NULL);
	}
}

static int sig_check_splits(void)
{
	GSList *tmp;
	int stop;

	stop = TRUE;
	for (tmp = servers; tmp != NULL; tmp = tmp->next) {
		IRC_SERVER_REC *rec = tmp->data;

		if (!IS_IRC_SERVER(rec))
			continue;

		if (rec->split_servers != NULL) {
			if (!check_server_splits(rec))
				stop = FALSE;
		}
	}

	if (stop) {
		g_source_remove(split_tag);
		signal_remove("print starting", (SIGNAL_FUNC) sig_print_starting);
                split_tag = -1;
	}
	return 1;
}

static void sig_netsplit_servers(void)
{
	if (settings_get_bool("hide_netsplit_quits") && split_tag == -1) {
		split_tag = g_timeout_add(1000,
					  (GSourceFunc) sig_check_splits,
					  NULL);
		signal_add("print starting", (SIGNAL_FUNC) sig_print_starting);
	}
}

static int split_equal(NETSPLIT_REC *n1, NETSPLIT_REC *n2)
{
        return g_ascii_strcasecmp(n1->nick, n2->nick);
}

static void split_get(void *key, NETSPLIT_REC *rec, GSList **list)
{
	*list = g_slist_insert_sorted(*list, rec,
				      (GCompareFunc) split_equal);
}

static void split_print(NETSPLIT_REC *rec, SERVER_REC *server)
{
	NETSPLIT_CHAN_REC *chan;
        char *chanstr;

	chan = rec->channels->data;
	chanstr = chan == NULL ?
                      g_strdup("") :
                      g_strconcat(chan->op ? "@" : (chan->voice ? "+" : ""), chan->name, NULL);

	printformat(server, NULL, MSGLEVEL_CLIENTCRAP, IRCTXT_NETSPLITS_LINE,
		    rec->nick, chanstr, rec->server->server,
		    rec->server->destserver);

	g_free(chanstr);
}

/* SYNTAX: NETSPLIT */
static void cmd_netsplit(const char *data, IRC_SERVER_REC *server)
{
	GSList *list;

        CMD_IRC_SERVER(server);

	if (server->split_servers == NULL) {
		printformat(server, NULL, MSGLEVEL_CLIENTNOTICE,
			    IRCTXT_NO_NETSPLITS);
		return;
	}

	printformat(server, NULL, MSGLEVEL_CLIENTCRAP, IRCTXT_NETSPLITS_HEADER);

        list = NULL;
	g_hash_table_foreach(server->splits, (GHFunc) split_get, &list);
	g_slist_foreach(list, (GFunc) split_print, server);
        g_slist_free(list);

	printformat(server, NULL, MSGLEVEL_CLIENTCRAP, IRCTXT_NETSPLITS_FOOTER);
}

static void read_settings(void)
{
        netsplit_max_nicks = settings_get_int("netsplit_max_nicks");
	netsplit_nicks_hide_threshold =
		settings_get_int("netsplit_nicks_hide_threshold");
	if (netsplit_nicks_hide_threshold < netsplit_max_nicks)
		netsplit_max_nicks = netsplit_nicks_hide_threshold;
}

void fe_netsplit_init(void)
{
	settings_add_int("misc", "netsplit_max_nicks", 10);
	settings_add_int("misc", "netsplit_nicks_hide_threshold", 15);
	split_tag = -1;
	printing_splits = FALSE;

	read_settings();
	signal_add("netsplit new", (SIGNAL_FUNC) sig_netsplit_servers);
	signal_add("setup changed", (SIGNAL_FUNC) read_settings);
	command_bind_irc("netsplit", NULL, (SIGNAL_FUNC) cmd_netsplit);
}

void fe_netsplit_deinit(void)
{
	if (split_tag != -1) {
		g_source_remove(split_tag);
		signal_remove("print starting", (SIGNAL_FUNC) sig_print_starting);
	}

	signal_remove("netsplit new", (SIGNAL_FUNC) sig_netsplit_servers);
	signal_remove("setup changed", (SIGNAL_FUNC) read_settings);
	command_unbind("netsplit", (SIGNAL_FUNC) cmd_netsplit);
}
