/*
 * flood.c: handle channel flooding. 
 *
 * This attempts to give you some protection from flooding.  Basically, it keeps
 * track of how far apart (timewise) messages come in from different people.
 * If a single nickname sends more than 3 messages in a row in under a
 * second, this is considered flooding.  It then activates the ON FLOOD with
 * the nickname and type (appropriate for use with IGNORE). 
 *
 * Thanks to Tomi Ollila <f36664r@puukko.hut.fi> for this one. 
 */


#include "irc.h"

#include "alias.h"
#include "hook.h"
#include "ircaux.h"
#include "ignore.h"
#include "flood.h"
#include "vars.h"
#include "output.h"
#include "list.h"
#include "misc.h"
#include "server.h"
#include "userlist.h"
#include "timer.h"
#include "ignore.h"
#include "status.h"
#include "hash2.h"
#include "cset.h"

static	char	*ignore_types[] =
{
	"",
	"MSG",
	"PUBLIC",
	"NOTICE",
	"WALL",
	"WALLOP",
	"CTCP",
	"INVITE",
	"CDCC",
	"ACTION"
};

#if 0
typedef struct flood_stru
{
	char	nick[NICKNAME_LEN + 1];
	char	channel[BIG_BUFFER_SIZE+1];
	int	type;
	char	flood;
	long	cnt;
	time_t	start;
}	Flooding;
#endif

#define FLOOD_HASHSIZE 31


HashEntry no_flood_list[FLOOD_HASHSIZE];
HashEntry flood_list[FLOOD_HASHSIZE];

static int remove_oldest_flood_hashlist(HashEntry *, time_t, int);




extern	char	*FromUserHost;
extern	unsigned int window_display;
extern	int	from_server;

static double allow_flood = 0.0;
static double this_flood = 0.0;

#define NO_RESET 0
#define RESET 1

int get_flood_rate(int type, ChannelList * channel)
{
	int flood_rate = get_int_var(FLOOD_RATE_VAR);
	if (channel)
	{
		switch(type)
		{
			case JOIN_FLOOD:
				flood_rate = get_cset_int_var(channel->csets, JOINFLOOD_TIME_CSET);
				break;
			case PUBLIC_FLOOD:
				flood_rate = get_cset_int_var(channel->csets, PUBFLOOD_TIME_CSET);
				break;
			case NICK_FLOOD:
				flood_rate = get_cset_int_var(channel->csets, NICKFLOOD_TIME_CSET);
				break;
			case KICK_FLOOD:
				flood_rate = get_cset_int_var(channel->csets, KICKFLOOD_TIME_CSET);
				break;
			case DEOP_FLOOD:
				flood_rate = get_cset_int_var(channel->csets, DEOPFLOOD_TIME_CSET);
				break;
			default:
				break;
		}
	}
	else
	{
		switch(type)
		{
			case CDCC_FLOOD:
			case CTCP_FLOOD:
				flood_rate = get_int_var(CDCC_FLOOD_RATE_VAR);
			case CTCP_ACTION_FLOOD:
			default:
				break;
		}
	}
	return flood_rate;
}

int get_flood_count(int type, ChannelList * channel)
{
	int flood_count = get_int_var(FLOOD_AFTER_VAR);
	if (channel) {
		switch(type)
		{
			case JOIN_FLOOD:
				flood_count = get_cset_int_var(channel->csets, KICK_ON_JOINFLOOD_CSET);
				break;
			case PUBLIC_FLOOD:
				flood_count = get_cset_int_var(channel->csets, KICK_ON_PUBFLOOD_CSET);
				break;
			case NICK_FLOOD:
				flood_count = get_cset_int_var(channel->csets, KICK_ON_NICKFLOOD_CSET);
				break;
			case KICK_FLOOD:
				flood_count = get_cset_int_var(channel->csets, KICK_ON_KICKFLOOD_CSET);
				break;
			case DEOP_FLOOD:
				flood_count = get_cset_int_var(channel->csets, KICK_ON_DEOPFLOOD_CSET);
				break;
			default:
			break;
		}
	} 
	else
	{
		switch(type)
		{
			case CDCC_FLOOD:
			case CTCP_FLOOD:
				flood_count = get_int_var(CDCC_FLOOD_AFTER_VAR);
			case CTCP_ACTION_FLOOD:
			default:
				break;
		}
	}
	return flood_count;
}

int set_flood(int type, time_t flood_time, int reset, NickList *tmpnick)
{
	if (!tmpnick)
		return 0;
	switch(type)
	{
		case JOIN_FLOOD:
			if (reset == RESET)
			{
				tmpnick->joincount = 1; 
				tmpnick->jointime = flood_time;
			} else tmpnick->joincount++;
			break;
		case PUBLIC_FLOOD:
			if (reset == RESET)
			{
				tmpnick->floodcount = 1;
				tmpnick->floodtime = tmpnick->idle_time = flood_time;
			} else tmpnick->floodcount++;
			break;
		case NICK_FLOOD:
			if (reset == RESET)
			{
				tmpnick->nickcount = 1;
				tmpnick->nicktime = flood_time;
			} else tmpnick->nickcount++;
			break;
		case KICK_FLOOD:
			if (reset == RESET)
			{
				tmpnick->kickcount = 1;
				tmpnick->kicktime = flood_time;
			} else tmpnick->kickcount++;
			break;
		case DEOP_FLOOD:
			if (reset == RESET)
			{
				tmpnick->dopcount = 1;
				tmpnick->doptime = flood_time;
			} else tmpnick->dopcount++;
			break;
		default:
		break;
	}
	return 1;
}

int is_other_flood(ChannelList *channel, NickList *tmpnick, int type, int *t_flood)
{
time_t diff = 0, flood_time = 0;
int doit = 0;
int count = 0;
int flood_rate = 0, flood_count = 0;

	flood_time = time(NULL);
	context;
	
	if (!channel || !tmpnick)
		return 0;
	if (!strcmp(get_server_nickname(from_server), tmpnick->nick))
		return 0;
	if (find_name_in_genericlist(tmpnick->nick, no_flood_list, FLOOD_HASHSIZE, 0))
		return 0;

	set_flood(type, flood_time, NO_RESET, tmpnick);
	switch(type)
	{
		case JOIN_FLOOD:
			if (!get_cset_int_var(channel->csets, JOINFLOOD_CSET))
				break;
			diff = flood_time - tmpnick->jointime;
			count = tmpnick->joincount;
			doit = 1;
			break;
		case PUBLIC_FLOOD:
			if (!get_cset_int_var(channel->csets, PUBFLOOD_CSET))
				break;
			diff = flood_time - tmpnick->floodtime;
			count = tmpnick->floodcount;
			doit = 1;
			break;
		case NICK_FLOOD:
			if (!get_cset_int_var(channel->csets, NICKFLOOD_CSET))
				break;
			diff = flood_time - tmpnick->nicktime;
			count = tmpnick->nickcount;
			doit = 1;
			break;
		case DEOP_FLOOD:
			if (!get_cset_int_var(channel->csets, DEOPFLOOD_CSET))
				break;
			diff = flood_time - tmpnick->doptime;
			count = tmpnick->dopcount;
			doit = 1;
			break;
		case KICK_FLOOD:
			if (!get_cset_int_var(channel->csets, KICKFLOOD_CSET))
				break;
			diff = flood_time - tmpnick->kicktime;
			count = tmpnick->kickcount;
			doit = 1;
			break;
		default:
			break;
	}
	if (doit)
	{
		int is_user = 0;
		flood_count = get_flood_count(type, channel);
		flood_rate = get_flood_rate(type, channel);
		if ((tmpnick->userlist && tmpnick->userlist->level >= 90) || (tmpnick->botlist))
			is_user = 1;
		if (!is_user && (count >= flood_count))
		{
			int flooded = 0;
			if (!diff || (flood_rate && (diff < flood_rate)))
			{
				*t_flood = diff;
				flooded = 1;
			}
			set_flood(type, flooded? 0L:flood_time, RESET, tmpnick);
			return flooded;
		}
	} 
	return 0;
} 

char *get_ignore_types(unsigned int type)
{
int x = 0;
	while (type)
	{
		type = type >> 1;
		x++;
	}
	return ignore_types[x];
}
/*
 * check_flooding: This checks for message flooding of the type specified for
 * the given nickname.  This is described above.  This will return 0 if no
 * flooding took place, or flooding is not being monitored from a certain
 * person.  It will return 1 if flooding is being check for someone and an ON
 * FLOOD is activated. 
 */
int check_flooding(char *nick, int type, char *line, char *channel)
{
	static	int	users = 0,
			pos = 0;
	time_t	flood_time = time(NULL),
		diff = 0;

	Flooding *tmp;
	int flood_rate, flood_count;


	if (!(users = get_int_var(FLOOD_USERS_VAR)))
		return 1;
	if (find_name_in_genericlist(nick, no_flood_list, FLOOD_HASHSIZE, 0))
		return 1;
	if (!(tmp = find_name_in_floodlist(nick, flood_list, FLOOD_HASHSIZE, 0)))
	{
		if (pos >= users)
		{
			pos -= remove_oldest_flood_hashlist(&flood_list[0], 0, (users + 1 - pos));
		}
		tmp = add_name_to_floodlist(nick, channel, flood_list, FLOOD_HASHSIZE);
		tmp->type = type;
		tmp->cnt = 1;
		tmp->start = flood_time;
		tmp->flood = 0;
		pos++;
		return 1;
	} 
	if (!(tmp->type & type))
	{
		tmp->type |= type; 
		return 1;
	}

	flood_count = get_flood_count(type, NULL); /* FLOOD_AFTER_VAR */
	flood_rate = get_flood_rate(type, NULL); /* FLOOD_RATE_VAR */
	if (!flood_count || !flood_rate)
		return 1;
	tmp->cnt++;
	if (tmp->cnt > flood_count)
	{
		int ret;
		diff = flood_time - tmp->start;
		if (diff != 0)
			this_flood = (double)tmp->cnt / (double)diff;
		else
			this_flood = 0;
		allow_flood = (double)flood_count / (double)flood_rate;
		if (!diff || !this_flood || (this_flood > allow_flood))
		{
			if (tmp->flood == 0)
			{
				tmp->flood = 1;
				if ((ret = do_hook(FLOOD_LIST, "%s %s %s", nick, get_ignore_types(type),line)) != 1)
					return ret;
				switch(type)
				{
					case MSG_FLOOD:
					case NOTICE_FLOOD:
					case CDCC_FLOOD:
					case CTCP_FLOOD:
						if (flood_prot(nick, FromUserHost, get_ignore_types(type), type, get_int_var(IGNORE_TIME_VAR), channel))
							return 0;
						break;
					case CTCP_ACTION_FLOOD:
						if (flood_prot(nick, FromUserHost, get_ignore_types(CTCP_FLOOD), type, get_int_var(IGNORE_TIME_VAR), channel))
							return 0;
					default:
						break;
				}
				if (get_int_var(FLOOD_WARNING_VAR))
					put_it("%s", convert_output_format(fget_string_var(FORMAT_FLOOD_FSET), "%s %s %s %s %s", update_clock(GET_TIME), get_ignore_types(type), nick, FromUserHost, channel?channel:"unknown"));
			}
			return 1;
		}
		else
		{
			tmp->flood = 0;
			tmp->cnt = 1;
			tmp->start = flood_time;
		}
	}
	return 1;
}

int flood_prot (char *nick, char *userhost, char *type, int ctcp_type, int ignoretime, char *channel)
{
ChannelList *chan;
NickList *Nick;
char tmp[BIG_BUFFER_SIZE+1];
char *uh;
int	old_window_display;
int	kick_on_flood = 1;

	if ((ctcp_type == CDCC_FLOOD || ctcp_type == CTCP_FLOOD || ctcp_type == CTCP_ACTION_FLOOD) && !get_int_var(CTCP_FLOOD_PROTECTION_VAR))
		return 0;
	else if (!get_int_var(FLOOD_PROTECTION_VAR))
		return 0;
	else if (!my_stricmp(nick, get_server_nickname(from_server)))
		return 0;
	switch (ctcp_type)
	{
		case MSG_FLOOD:
			break;
		case NOTICE_FLOOD:
			break;
		case PUBLIC_FLOOD:
			if (channel)
			{
				if ((chan = lookup_channel(channel, from_server, 0)))
				{
					kick_on_flood = get_cset_int_var(chan->csets, PUBFLOOD_CSET);
					if ((Nick = find_nicklist_in_channellist(nick, chan, 0)) && kick_on_flood)
					{
						if (chan->chop && ((!Nick->userlist && !Nick->botlist) || (Nick->userlist && Nick->userlist->level < 90)))
							send_to_server("KICK %s %s :\002%s\002 flooder", chan->channel, nick, type);
					} 
				}
			}
			break;
		case CTCP_ACTION_FLOOD:
		case CTCP_FLOOD:
		default:
			if (get_int_var(FLOOD_KICK_VAR) && kick_on_flood && channel)
			{
				for (chan = server_list[from_server].chan_list; chan; chan = chan->next)
				{
					if ((Nick = find_nicklist_in_channellist(nick, chan, 0)))
					{
						if (chan->chop && ((!Nick->userlist && !Nick->botlist) || (Nick->userlist && Nick->userlist->level < 90)))
							send_to_server("KICK %s %s :\002%s\002 flooder", chan->channel, nick, type);
					}
				}
			}
	}
	if (!ignoretime)
		return 0;
	uh = clear_server_flags(userhost);
	sprintf(tmp, "*!*%s", uh);
	old_window_display = window_display;
	window_display = 0;
	ignore_nickname(tmp, ignore_type(type, strlen(type)), 0);
	window_display = old_window_display;
	sprintf(tmp, "%d ^IGNORE *!*%s NONE", ignoretime, uh);
	timercmd("TIMER", tmp, NULL, NULL);
	bitchsay("Auto-ignoring %s for %d minutes [\002%s\002 flood]", nick, ignoretime/60, type);
	return 1;
}

static int remove_oldest_flood_hashlist(HashEntry *list, time_t timet, int count)
{
Flooding *ptr;
register time_t t;
int total = 0;
register unsigned long x;
	t = time(NULL);
	if (!count)
	{
		for (x = 0; x < FLOOD_HASHSIZE; x++)
		{
			ptr = (Flooding *) (list + x)->list;
			if (!ptr || !*ptr->name)
				continue;
			while (ptr)
			{
				if ((ptr->start + timet) <= t)
				{
					ptr = find_name_in_floodlist(ptr->name, flood_list, FLOOD_HASHSIZE, 1);
					new_free(&(ptr->channel));
					new_free((char **)&ptr);
					total++;
					ptr = (Flooding *) (list + x)->list;
				} else ptr = ptr->next;
			}
		}
	}
	else
	{
		for (x = 0; x < FLOOD_HASHSIZE; x++)
		{
			Flooding *next = NULL;
			ptr = (Flooding *) (list + x)->list;
			if (!ptr || !*ptr->name)
				continue;
			while(ptr && count)
			{
				ptr = find_name_in_floodlist(ptr->name, flood_list, FLOOD_HASHSIZE, 1);
				next = ptr->next;
				new_free(&(ptr->channel));
				new_free((char **)&ptr);
				total++; count--;			
				ptr = (Flooding *) (list + x)->list;
				ptr = next;
			}
		}
	}
	return total;
}

void clean_flood_list()
{
	remove_oldest_flood_hashlist(&flood_list[0], get_int_var(FLOOD_RATE_VAR)+1, 0);
}
