#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;
    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)));
}


syntax highlighted by Code2HTML, v. 0.9.1