#ident "@(#)lastlog.c 1.5"
/*
 * lastlog.c: handles the lastlog features of irc. 
 *
 * Written By Michael Sandrof
 *
 * Copyright(c) 1990 
 *
 * See the COPYRIGHT file, or do a HELP IRCII COPYRIGHT 
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include "irc.h"
#include "lastlog.h"
#include "window.h"
#include "screen.h"
#include "vars.h"
#include "ircaux.h"
#include "output.h"
#include "misc.h"
#include "hook.h"
#include "status.h"
#include "fset.h"
#include "tcommand.h"

/*
 * lastlog_level: current bitmap setting of which things should be stored in
 * the lastlog.  The LOG_MSG, LOG_NOTICE, etc., defines tell more about this 
 */
static unsigned long lastlog_level;
static unsigned long notify_level;

static unsigned long msglog_level = 0;
unsigned long beep_on_level = 0;


FILE *logptr = NULL;

/*
 * msg_level: the mask for the current message level.  What?  Did he really
 * say that?  This is set in the set_lastlog_msg_level() routine as it
 * compared to the lastlog_level variable to see if what ever is being added
 * should actually be added 
 */
static unsigned long msg_level = LOG_CRAP;

static char *levels[] =
{
	"CRAP", "PUBLIC", "MSGS", "NOTICES",
	"WALLS", "WALLOPS", "NOTES", "OPNOTES",
	"SNOTES", "ACTIONS", "DCC", "CTCP",
	"USERLOG1", "USERLOG2", "USERLOG3", "USERLOG4",
	"USERLOG5", "BEEP", "SEND_MSG", "KILL"
};

#define NUMBER_OF_LEVELS (sizeof(levels) / sizeof(char *))


/* set_lastlog_msg_level: sets the message level for recording in the lastlog */
unsigned long 
set_lastlog_msg_level (unsigned long level)
{
	unsigned long old;

	old = msg_level;
	msg_level = level;
	return (old);
}

/*
 * bits_to_lastlog_level: converts the bitmap of lastlog levels into a nice
 * string format.  Note that this uses the global buffer, so watch out 
 */
char *
bits_to_lastlog_level (unsigned long level)
{
	static char buffer[281];	/* this *should* be enough for this */
	int i;
	unsigned long p;

	if (level == LOG_ALL)
		strcpy (buffer, "ALL");
	else if (level == 0)
		strcpy (buffer, "NONE");
	else
	{
		*buffer = '\0';
		for (i = 0, p = 1; i < NUMBER_OF_LEVELS; i++, p <<= 1)
		{
			if (level & p)
			{
				strmcat (buffer, levels[i], 280);
				strmcat (buffer, " ", 280);
			}
		}
	}
	return (buffer);
}

unsigned long 
parse_lastlog_level (char *str)
{
	char *ptr, *rest;
	int len, i;
	unsigned long p, level;
	int neg;

	level = 0;
	while ((str = next_arg (str, &rest)) != NULL)
	{
		while (str)
		{
			if ((ptr = strchr (str, ',')) != NULL)
				*ptr++ = '\0';
			if ((len = strlen (str)) != 0)
			{
				if (my_strnicmp (str, "ALL", len) == 0)
					level = LOG_ALL;
				else if (my_strnicmp (str, "NONE", len) == 0)
					level = 0;
				else
				{
					if (*str == '-')
					{
						str++;
						len--;
						neg = 1;
					}
					else
						neg = 0;
					for (i = 0, p = 1; i < NUMBER_OF_LEVELS; i++, p <<= 1)
					{
						if (!my_strnicmp (str, levels[i], len))
						{
							if (neg)
								level &= (LOG_ALL ^ p);
							else
								level |= p;
							break;
						}
					}
					if (i == NUMBER_OF_LEVELS)
						say ("Unknown lastlog level: %s",
						     str);
				}
			}
			str = ptr;
		}
		str = rest;
	}
	return (level);
}

/*
 * set_lastlog_level: called whenever a "SET LASTLOG_LEVEL" is done.  It
 * parses the settings and sets the lastlog_level variable appropriately.  It
 * also rewrites the LASTLOG_LEVEL variable to make it look nice 
 */
void 
set_lastlog_level (Window * win, char *str, int unused)
{
	lastlog_level = parse_lastlog_level (str);
	set_string_var (LASTLOG_LEVEL_VAR, bits_to_lastlog_level (lastlog_level));
	curr_scr_win->lastlog_level = lastlog_level;
}

/*
 * set_msglog_level: called whenever a "SET MSGLOG_LEVEL" is done.  It
 * parses the settings and sets the msglog_level variable appropriately.  It
 * also rewrites the MSGLOG_LEVEL variable to make it look nice 
 */
void 
set_msglog_level (Window * win, char *str, int unused)
{
	msglog_level = parse_lastlog_level (str);
	set_string_var (MSGLOG_LEVEL_VAR, bits_to_lastlog_level (msglog_level));
}

void 
remove_from_lastlog (Window * window)
{
	Lastlog *tmp, *end_holder;

	if (window->lastlog_tail)
	{
		end_holder = window->lastlog_tail;
		tmp = window->lastlog_tail->prev;
		window->lastlog_tail = tmp;
		if (tmp)
			tmp->next = NULL;
		else
			window->lastlog_head = window->lastlog_tail;
		window->lastlog_size--;
		new_free (&end_holder->msg);
		new_free ((char **) &end_holder);
	}
	else
		window->lastlog_size = 0;
}

/*
 * set_lastlog_size: sets up a lastlog buffer of size given.  If the lastlog
 * has gotten larger than it was before, all previous lastlog entry remain.
 * If it get smaller, some are deleted from the end. 
 */
void 
set_lastlog_size (Window * win_unused, char *unused, int size)
{
	int i, diff;
	Window *win = NULL;

	while (traverse_all_windows (&win))
	{
		if (win->lastlog_size > size)
		{
			diff = win->lastlog_size - size;
			for (i = 0; i < diff; i++)
				remove_from_lastlog (win);
		}
		win->lastlog_max = size;
	}
}

/*
 * lastlog: the /LASTLOG command.  Displays the lastlog to the screen. If
 * args contains a valid integer, only that many lastlog entries are shown
 * (if the value is less than lastlog_size), otherwise the entire lastlog is
 * displayed 
 */
void
cmd_lastlog (struct command *cmd, char *args)
{
	int cnt, from = 0, p, i, level = 0, my_msg_level, len, mask = 0, header = 1,
	  lines = 0;
	Lastlog *start_pos;
	char *the_match = NULL, *arg;
	char *blah = NULL;
	FILE *fp = NULL;

	message_from (NULL, LOG_CURRENT);
	cnt = curr_scr_win->lastlog_size;

	while ((arg = new_next_arg (args, &args)) != NULL)
	{
		if (*arg == '-')
		{
			arg++;
			if (!(len = strlen (arg)))
			{
				header = 0;
				continue;
			}
			else if (!my_strnicmp (arg, "MAX", len))
			{
				char *ptr = NULL;
				ptr = new_next_arg (args, &args);
				if (ptr)
					lines = atoi (ptr);
				if (lines < 0)
					lines = 0;
			}
			else if (!my_strnicmp (arg, "LITERAL", len))
			{
				if (the_match)
				{
					say ("Second -LITERAL argument ignored");
					(void) new_next_arg (args, &args);
					continue;
				}
				if ((the_match = new_next_arg (args, &args)) != NULL)
					continue;
				say ("Need pattern for -LITERAL");
				return;
			}
			else if (!my_strnicmp (arg, "BEEP", len))
			{
				if (the_match)
				{
					say ("-BEEP is exclusive; ignored");
					continue;
				}
				else
					the_match = "\007";
			}
#if 0
			else if (!my_strnicmp (arg, "CLEAR", len))
			{
				free_lastlog (curr_scr_win);
				say ("Cleared lastlog");
				return;
			}
#endif
			else if (!my_strnicmp (arg, "FILE", len))
			{
				if (args && *args)
				{
					char *filename;
					filename = next_arg (args, &args);
					if (!(fp = fopen (filename, "w")))
					{
						bitchsay ("cannot open file %s", filename);
						return;
					}
				}
				else
				{
					bitchsay ("Filename needed for save");
					return;
				}
			}
			else
			{
				for (i = 0, p = 1; i < NUMBER_OF_LEVELS; i++, p <<= 1)
				{
					if (my_strnicmp (levels[i], arg, len) == 0)
					{
						mask |= p;
						break;
					}
				}
				if (i == NUMBER_OF_LEVELS)
				{
					bitchsay ("Unknown flag: %s", arg);
					message_from (NULL, LOG_CRAP);
					return;
				}
			}
		}
		else
		{
			if (level == 0)
			{
				if (the_match || isdigit (*arg))
				{
					cnt = atoi (arg);
					level++;
				}
				else
					the_match = arg;
			}
			else if (level == 1)
			{
				from = atoi (arg);
				level++;
			}
		}
	}
	start_pos = curr_scr_win->lastlog_head;
	for (i = 0; (i < from) && start_pos; start_pos = start_pos->next)
		if (!mask || (mask & start_pos->level))
			i++;

	for (i = 0; (i < cnt) && start_pos; start_pos = start_pos->next)
		if (!mask || (mask & start_pos->level))
			i++;

	level = curr_scr_win->lastlog_level;
	my_msg_level = set_lastlog_msg_level (0);
	if (start_pos == NULL)
		start_pos = curr_scr_win->lastlog_tail;
	else
		start_pos = start_pos->prev;

	/* Let's not get confused here, display a seperator.. -lynx */
	strip_ansi_in_echo = 0;
	if (header && !fp)
		say ("Lastlog:");

	if (the_match) {

		blah = (char *) alloca (strlen (the_match) + 4);
		sprintf (blah, "*%s*", the_match);
	}
	for (i = 0; (i < cnt) && start_pos; start_pos = start_pos->prev)
	{
		if (!mask || (mask & start_pos->level))
		{
			i++;
			if (!the_match || wild_match (blah, start_pos->msg))
			{
				if (!fp)
				{
					put_it ("%s", !get_int_var (LASTLOG_ANSI_VAR) ? stripansicodes (start_pos->msg) : start_pos->msg);
				}
				else
					fprintf (fp, "%s\n", !get_int_var (LASTLOG_ANSI_VAR) ? stripansicodes (start_pos->msg) : start_pos->msg);
				if (lines == 0)
					continue;
				else if (lines == 1)
					break;
				lines--;
			}
		}
	}
	if (header && !fp)
		say ("End of Lastlog");
	strip_ansi_in_echo = 1;
	curr_scr_win->lastlog_level = level;
	message_from (NULL, LOG_CRAP);
	set_lastlog_msg_level (my_msg_level);
}

/*
 * add_to_lastlog: adds the line to the lastlog.  If the LASTLOG_CONVERSATION
 * variable is on, then only those lines that are user messages (private
 * messages, channel messages, wall's, and any outgoing messages) are
 * recorded, otherwise, everything is recorded 
 */
void 
add_to_lastlog (Window * window, const char *line)
{
	Lastlog *new;

	if (window == NULL)
		window = curr_scr_win;
	if (window->lastlog_level & msg_level)
	{
		/* no nulls or empty lines (they contain "> ") */
		if (line && (strlen (line) > 2))
		{
			new = (Lastlog *) new_malloc (sizeof (Lastlog));
			new->next = window->lastlog_head;
			new->prev = NULL;
			new->level = msg_level;
			new->msg = NULL;
			new->time = time (NULL);
			malloc_strcpy (&(new->msg), line);

			if (window->lastlog_head)
				window->lastlog_head->prev = new;
			window->lastlog_head = new;

			if (window->lastlog_tail == NULL)
				window->lastlog_tail = window->lastlog_head;

			if (window->lastlog_size++ >= window->lastlog_max)
				remove_from_lastlog (window);
		}
	}
}

unsigned long 
real_notify_level (void)
{
	return (notify_level);
}

unsigned long 
real_lastlog_level (void)
{
	return (lastlog_level);
}

void 
set_notify_level (Window * win, char *str, int unused)
{
	notify_level = parse_lastlog_level (str);
	set_string_var (NOTIFY_LEVEL_VAR, bits_to_lastlog_level (notify_level));
	curr_scr_win->notify_level = notify_level;
}

int 
logmsg (unsigned long log_type, char *from, char *string, int flag)
{
	char *timestr;
	time_t t;
	char *filename = NULL;
	char *expand = NULL;
	char *type = NULL;
	unsigned char **lines = NULL;

	if (!get_string_var (MSGLOGFILE_VAR))
		return 0;

	t = time (NULL);
	timestr = update_clock (GET_TIME);

	switch (flag)
	{
	case 0:
		if (!(type = bits_to_lastlog_level (log_type)))
			type = "Unknown";
		if (!do_hook (MSGLOG_LIST, "%s %s %s %s", timestr, type, from, string ? string : ""))
			break;
		if (!logptr)
			return 0;
		if (msglog_level & log_type)
		{
			lines = split_up_line (stripansicodes (convert_output_format (get_fset_var (FORMAT_MSGLOG_FSET) ? get_fset_var (FORMAT_MSGLOG_FSET) : "[$[10]0] [$1] - $2-", "%s %s %s %s", type, timestr, from, string)));
			for (; *lines; lines++)
				fprintf (logptr, "%s\n", *lines);
			fflush (logptr);
		}
		break;
	case 1:
		malloc_sprintf (&filename, "%s", get_string_var (MSGLOGFILE_VAR));
		expand = expand_twiddle (filename);
		new_free (&filename);
		if (!do_hook (MSGLOG_LIST, "%s %s %s %s", timestr, "On", expand, ""))
		{
			new_free (&expand);
			return 1;
		}
		if (logptr)
		{
			new_free (&expand);
			return 1;
		}
		if (!(logptr = fopen (expand, get_int_var (APPEND_LOG_VAR) ? "at" : "wt")))
		{
			set_int_var (MSGLOG_VAR, 0);
			new_free (&expand);
			return 0;
		}

		fprintf (logptr, "MsgLog started [%s]\n", my_ctime (t));
		fflush (logptr);
		if (string)
		{
			int i;

			i = logmsg (LOG_CURRENT, from, string, 0);
			return i;
		}
		bitchsay ("Now logging messages to: %s", expand);
		new_free (&expand);
		break;
	case 2:
		if (!do_hook (MSGLOG_LIST, "%s %s %s %s", timestr, "Off", "", ""))
			return 1;
		if (!logptr)
			return 1;
		fprintf (logptr, "MsgLog ended [%s]\n", my_ctime (t));
		fclose (logptr);
		logptr = NULL;
		break;
	case 3:
		return logptr ? 1 : 0;
		break;
	case 4:
		if (!logptr)
			return 1;
		fprintf (logptr, "[TimeStamp %s]\n", my_ctime (t));
		fflush (logptr);
		break;
	default:
		bitchsay ("Bad Flag passed to logmsg");
		return 0;
	}
	return 1;
}

void 
set_beep_on_msg (Window * win, char *str, int unused)
{
	beep_on_level = parse_lastlog_level (str);
	set_string_var (BEEP_ON_MSG_VAR, bits_to_lastlog_level (beep_on_level));
}

void
cmd_awaylog (struct command *cmd, char *args)
{
	if (args && *args)
	{
		msglog_level = parse_lastlog_level (args);
		set_string_var (MSGLOG_LEVEL_VAR, bits_to_lastlog_level (msglog_level));
		put_it ("%s", convert_output_format ("$G Away logging set to: $0-", "%s", get_string_var (MSGLOG_LEVEL_VAR)));
	}
	else
		put_it ("%s", convert_output_format ("$G Away logging currently: $0-", "%s", bits_to_lastlog_level (msglog_level)));
}
