/* userson.c - "Users On" display functions
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <pwd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <dirent.h>
#include <unistd.h>
#include "viewmenu.h"
#include "terminal.h"
#include "mstring.h"
#include "lock.h"
#include "hook.h"
#include "u2u.h"
#include "bbs.h"

#define MCONV_UO_WIDETERM	115	    /* "wide terminal" width */


uo_info_t *uo_list = 0;			 /* list of users logged in */
int *uo_order = 0;			 /* order to display users in */
int uo_list_len = 0;			 /* number of items in list */
int uo_pos;				 /* position of top of screen */

char **uo_friends = 0;			 /* array of "friends" */
int uo_num_friends = 0;			 /* length of array */

int uo_idle_on = 0;			 /* 1=idle on, 0=users on */


extern int mview_edit_file(char *, long, int, char *, char *);
extern void examine_user(char *);


/* Return a pointer to a static buffer containing a short string describing
 * the time span "t".
 */
char *uo__timespan(long t)
{
	static char buf[64];

	if (t < (60 * 60)) {		    /* < 1 hour */

		sprintf(buf, "%ld:%02lds", t / 60, t % 60);

	} else if (t < (60 * 60 * 24)) {    /* < 1 day */

		t = t / 60;
		sprintf(buf, "%ldh%2ldm", t / 60, t % 60);

	} else if (t < (60 * 60 * 24 * 10)) {	/* < 10 days */

		t = t / (60 * 60);

		sprintf(buf, "%ldd %ldh", t / 24, t % 24);

	} else if (t < (60 * 60 * 24 * 1000)) {	/* < 1000 days */

		t = t / (60 * 60 * 24);
		sprintf(buf, "%lddy", t);

	} else {			    /* >= 1000 days */

		sprintf(buf, "999d+");

	}

	buf[6] = 0;

	return (buf);
}


/* Look up "user" in the friends list, returning 1 if they're in it.
 */
int uo__friend(char *user)
{
	int i;

	if (!uo_friends)
		return (0);

	for (i = 0; i < uo_num_friends; i++) {
		if (!strcmp(uo_friends[i], user))
			return (1);
	}

	return (0);
}


/* Free the friends list from memory.
 */
void uo__friends_free(void)
{
	int i;

	if (!uo_friends)
		return;

	for (i = 0; i < uo_num_friends; i++)
		free(uo_friends[i]);
	free(uo_friends);

	uo_friends = 0;
	uo_num_friends = 0;
}


/* Load the friends list into memory.
 */
void uo__friends_load(void)
{
	char buf[1024];
	FILE *fptr;
	char **b;
	char *a;

	uo__friends_free();

	fptr = fopen(cf_str("friends"), "r");
	if (!fptr)
		return;

	while ((!feof(fptr)) && (!ferror(fptr))) {
		buf[0] = 0;
		fgets(buf, sizeof(buf) - 1, fptr);	/* read line */
		buf[sizeof(buf) - 1] = 0;
		a = strchr(buf, '\n');	    /* chop \n */
		if (a)
			*a = 0;
		if (buf[0] == 0)
			continue;	    /* skip blank lines */
		a = strtok(buf, " \011");
		while (a) {		    /* many names per line, so split */
			if (a[0] == '#') {  /* stop at a comment */
				a = 0;
				memset(buf, 0, sizeof(buf));	/* KLUDGE ALERT */
				continue;
			}
			b = realloc(uo_friends,
				    (uo_num_friends + 1) * sizeof(char *));
			if (b) {
				uo_friends = b;
				uo_friends[uo_num_friends] = strdup(a);
				uo_num_friends++;
			}
			a = strtok(0, " \011");
		}
	}

	fclose(fptr);
}


/* Edit the friends list.
 */
void uo__friends_edit(void)
{
	FILE *fptr;
	int r;

	fptr = fopen(cf_str("friends"), "r");
	if (!fptr) {			    /* create file */
		fptr = fopen(cf_str("friends"), "w");
		if (!fptr)
			return;
		fchmod(fileno(fptr),
		       S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP);
	}
	fclose(fptr);

	r = mview_edit_file(cf_str("friends"), 0, 0, "Friends List",
			    "The users named in this file will be highlighted in the Users On listing.\n"
			    "\n"
			    "Note that anything following a \035B#\035b on a line will be ignored.\n");
	if (r == 0)
		uo__friends_load();

	rf_redraw = 1;
}


/* Output a single line. <0 indicates a header line.
 */
void uo__lineout(int n)
{
	char buf[1024];
	char tmp[64];
	time_t now;
	struct tm *t;
	int b;

	if (n == -1) {			    /* headers */

		if (t_cols > MCONV_UO_WIDETERM) {	/* wide terminal */

			snprintf(buf, sizeof(buf) - 16,
				 " \035U%-8s\035u \035U%6s\035u \035U%7s\035u"
				 " \035U%-40s\035u \035U%-30s\035u \035U%s\035u",
				 "Account", "Idle", "Connect",
				 "Name", "Location", "Current Action");

		} else {

			if (uo_idle_on) {   /* idle on */

				snprintf(buf, sizeof(buf) - 16,
					 " \035U%-8s\035u \035U%6s\035u \035U%7s\035u"
					 " \035U%-30s\035u \035U%s\035u",
					 "Account", "Idle", "Connect",
					 "Location", "Current Action");

			} else {	    /* users on */

				snprintf(buf, sizeof(buf) - 16,
					 " \035U%-8s\035u \035U%-40s\035u \035U%s\035u",
					 "Account", "Name",
					 "Current Action");

			}

		}

	} else if (n == -2) {		    /* line of ~~~~s */

		strcpy(buf, "\035CB");
		for (b = 0; (b < t_cols) && (b < (sizeof(buf) - 16)); b++)
			buf[b + 3] = '~';
		buf[b + 3] = 0;
		strcat(buf, "\035CA");

	} else if (n == -3) {		    /* top header line */

		now = time(0);
		t = localtime(&now);

		if (t) {
			sprintf(buf,
				"\035CC(%d) User%s On at %d:%02d\035CA",
				uo_list_len, (uo_list_len == 1) ? "" : "s",
				t->tm_hour, t->tm_min);
		} else {
			sprintf(buf, "\035CC(%d) User%s On\035CA",
				uo_list_len,
				(uo_list_len == 1) ? "" : "s");
		}

		t_centre(buf);
		t_write("\n");
		return;

	} else if ((n < 0) || (n >= uo_list_len)) {	/* line out of range */

		buf[0] = 0;

	} else {			    /* normal user line */

		if (t_cols > MCONV_UO_WIDETERM) {	/* wide terminal */

			strcpy(tmp,
			       uo__timespan(time(0) -
					    uo_list[uo_order[n]].idle));

			uo_list[uo_order[n]].location[30] = 0;

			snprintf(buf, sizeof(buf) - 16,
				 "%s%s%-8s\035a\035CA %6s %7s %-40s %-30s %s%s%s%s",
				 (uo_list[uo_order[n]].bold) ? ">\035B" :
				 (uo_list[uo_order[n]].admin) ? "\035CC*" :
				 (uo_list[uo_order[n]].internal) ? "-" :
				 " ",
				 (uo_list[uo_order[n]].me) ? "\035CG" : "",
				 uo_list[uo_order[n]].username,
				 tmp,
				 uo__timespan(time(0) -
					      uo_list[uo_order[n]].login),
				 uo_list[uo_order[n]].nameline,
				 uo_list[uo_order[n]].location,
				 (strlen(uo_list[uo_order[n]].keypath) >
				  0) ? "<" : "",
				 uo_list[uo_order[n]].keypath,
				 (strlen(uo_list[uo_order[n]].keypath) >
				  0) ? "> " : "",
				 uo_list[uo_order[n]].action);

		} else {

			if (uo_idle_on) {   /* idle on */

				strcpy(tmp,
				       uo__timespan(time(0) -
						    uo_list[uo_order[n]].
						    idle));

				uo_list[uo_order[n]].location[30] = 0;

				snprintf(buf, sizeof(buf) - 16,
					 "%s%s%-8s\035a\035CA %6s %7s %-30s %s%s%s%s",
					 (uo_list[uo_order[n]].
					  bold) ? ">\035B"
					 : (uo_list[uo_order[n]].
					    admin) ? "\035CC*"
					 : (uo_list[uo_order[n]].
					    internal) ? "-" : " ",
					 (uo_list[uo_order[n]].
					  me) ? "\035CG" : "",
					 uo_list[uo_order[n]].username,
					 tmp,
					 uo__timespan(time(0) -
						      uo_list[uo_order[n]].
						      login),
					 uo_list[uo_order[n]].location,
					 (strlen
					  (uo_list[uo_order[n]].keypath) >
					  0) ? "<" : "",
					 uo_list[uo_order[n]].keypath,
					 (strlen
					  (uo_list[uo_order[n]].keypath) >
					  0) ? "> " : "",
					 uo_list[uo_order[n]].action);

			} else {	    /* users on */

				snprintf(buf, sizeof(buf) - 16,
					 "%s%s%-8s\035a\035CA %-40s %s%s%s%s",
					 (uo_list[uo_order[n]].
					  bold) ? ">\035B"
					 : (uo_list[uo_order[n]].
					    admin) ? "\035CC*"
					 : (uo_list[uo_order[n]].
					    internal) ? "-" : " ",
					 (uo_list[uo_order[n]].
					  me) ? "\035CG" : "",
					 uo_list[uo_order[n]].username,
					 uo_list[uo_order[n]].nameline,
					 (strlen
					  (uo_list[uo_order[n]].keypath) >
					  0) ? "<" : "",
					 uo_list[uo_order[n]].keypath,
					 (strlen
					  (uo_list[uo_order[n]].keypath) >
					  0) ? "> " : "",
					 uo_list[uo_order[n]].action);

			}

		}

	}

	buf[sizeof(buf) - 16] = 0;

	if (n >= 0) {
		if (mstrlen(buf) >= t_cols) {	/* chop long lines */
			b = mstrindex(buf, t_cols - 1);
			strcpy(buf + b, "\035a\035CA\035Ca\035R>\035r");
		}
	}

	strcat(buf, "\035a\035CA\035Ca\n");

	t_write(buf);
}


/* Read a line from "fptr" and store it in "buf", removing the trailing \n.
 * If the line is too long to fit, it is truncated.
 */
void uo__getline(FILE * fptr, char *buf, int len)
{
	char lbuf[1024];
	char *a;

	buf[0] = 0;
	fgets(lbuf, sizeof(lbuf), fptr);

	a = strchr(lbuf, '\n');
	if (a)
		a[0] = 0;

	strncpy(buf, lbuf, len);
	buf[len - 1] = 0;		    /* always null terminate */
}


/* Regenerate the list of connected users.
 */
void uo_genlist(void)
{
	struct dirent *d;
	struct stat sb;
	char file[1024];
	char tmp[64];
	FILE *fptr;
	DIR *dptr;
	char *dir;
	char *a;
	int i, changed, n;

	if (uo_list)
		free(uo_list);
	if (uo_order)
		free(uo_order);
	uo_list = 0;
	uo_order = 0;
	uo_list_len = 0;

	dir = cf_str("status");

	dptr = opendir(dir);
	if (!dptr)
		return;
	rewinddir(dptr);

	while ((d = readdir(dptr))) {	    /* scan for status files */

		snprintf(file, sizeof(file), "%s/%s", dir, d->d_name);

		fptr = fopen(file, "r+");   /* open status file */
		if (!fptr)
			continue;

		if ((fstat(fileno(fptr), &sb) != 0) ||	/* skip if it's old */
		    (sb.st_mtime < (time(0) - 10800)) ||
		    (!S_ISREG(sb.st_mode))) {
			fclose(fptr);
			remove(file);	    /* ...and delete it */
			continue;
		}

		my_flock(file, fileno(fptr), LOCK_EX);

		uo_list =
		    realloc(uo_list,
			    sizeof(uo_info_t) * (uo_list_len + 2));
		uo_order =
		    realloc(uo_order, sizeof(int) * (uo_list_len + 2));
		if (!uo_list)
			continue;
		if (!uo_order)
			continue;

		uo__getline(fptr, uo_list[uo_list_len].username,
			    sizeof(uo_list[uo_list_len].username));
		uo__getline(fptr, uo_list[uo_list_len].nameline,
			    sizeof(uo_list[uo_list_len].nameline));
		uo__getline(fptr, uo_list[uo_list_len].keypath,
			    sizeof(uo_list[uo_list_len].keypath));
		uo__getline(fptr, uo_list[uo_list_len].action,
			    sizeof(uo_list[uo_list_len].action));
		uo__getline(fptr, tmp, sizeof(tmp));
		uo_list[uo_list_len].idle = atol(tmp);
		uo__getline(fptr, uo_list[uo_list_len].location,
			    sizeof(uo_list[uo_list_len].location));
		uo__getline(fptr, tmp, sizeof(tmp));
		uo_list[uo_list_len].login = atol(tmp);

		my_flock(file, fileno(fptr), LOCK_UN);
		fclose(fptr);

		uo_list[uo_list_len].me = (strcmp(current_user,
						  uo_list[uo_list_len].
						  username) == 0) ? 1 : 0;

		uo_list[uo_list_len].bold =
		    uo__friend(uo_list[uo_list_len].username);

		uo_list[uo_list_len].admin = 0;
		uo_list[uo_list_len].internal = 0;

		a = current_user;
		current_user = uo_list[uo_list_len].username;
		if (bbs_hook(HOOK_USERCHECK, "admin/", current_user))
			uo_list[uo_list_len].admin = 1;
		if (getpwnam(current_user))
			uo_list[uo_list_len].internal = 1;
		current_user = a;

		uo_list_len++;
	}

	closedir(dptr);

	for (i = 0; i < uo_list_len; i++)
		uo_order[i] = i;

	do {				    /* sort by username (bubble sort) */
		changed = 0;
		for (i = 1; i < uo_list_len; i++) {
			if (strcmp(uo_list[uo_order[i - 1]].username,
				   uo_list[uo_order[i]].username) > 0) {
				n = uo_order[i];
				uo_order[i] = uo_order[i - 1];
				uo_order[i - 1] = n;
				changed = 1;
			}
		}
	} while (changed);
}


/* Regenerate the user listing and redraw the screen.
 */
void uo_redraw(void)
{
	char buf[1024];
	time_t now;
	struct tm *t;
	int i;

	now = time(0);
	t = localtime(&now);

	if (t) {
		sprintf(buf, "Users On at %d:%02d", t->tm_hour, t->tm_min);
	} else
		strcpy(buf, "Users On");

	bbs_hook(HOOK_SET_ACTION, 0, buf);
	bbs_hook(HOOK_SET_KEYPATH, "", 0);

	uo_genlist();
	t_clear();
	for (i = 0; i < (t_rows - 1); i++)
		uo__lineout(uo_pos + i);

	strcpy(buf, "[\035BQ\035b]:Exit");
	if (uo_pos > -3)
		strcat(buf, "  [\035B-\035b]:Up");
	if (uo_pos <= (uo_list_len - t_rows))
		strcat(buf, "  [\035B+\035b]:More");
	strcat(buf, "  [\035BESC\035b]:Utils");
	strcat(buf, "  [\035B.\035b]:Friends");
	strcat(buf, "  [\035BE\035b]:Examine");
	strcat(buf, "  [\035BS\035b]:Send");

	t_centre(buf);
}


/* Scroll forward "n" rows.
 */
void uo_scroll_forward(int n)
{
	uo_pos += n;
	if (uo_pos > (uo_list_len - t_rows + 1))
		uo_pos = uo_list_len - t_rows + 1;
	if (uo_pos < -3)
		uo_pos = -3;
	rf_redraw = 1;
}


/* Scroll back "n" rows.
 */
void uo_scroll_back(int n)
{
	uo_pos -= n;
	if (uo_pos < -3)
		uo_pos = -3;
	rf_redraw = 1;
}


/* Display a list of the current users on the system.
 */
void bbs_users_on(menudata_t data)
{
	time_t next_update;
	char *a;
	int interval;
	int c;

	bbs_hook(HOOK_SET_ACTION, 0, "Users On");
	rf_redraw = 1;
	uo_pos = -3;

	a = getenv("BBS_USERS_ON_TIME");
	interval = 30;
	if (a)
		interval = atoi(a);
	if (interval < 5)
		interval = 30;
	next_update = time(0) + interval;

	uo__friends_load();

	do {

		if (time(0) >= next_update) {
			rf_redraw = 1;
			uo_idle_on = 1 - uo_idle_on;
		}

		if (rf_redraw) {
			uo_redraw();
			rf_redraw = 0;
			a = getenv("BBS_USERS_ON_TIME");
			interval = 30;
			if (a)
				interval = atoi(a);
			if (interval < 5)
				interval = 30;
			next_update = time(0) + interval;
		}

		c = t_getchar(1);	    /* read a keypress */

		t_checksize();		    /* check for terminal resize */

		bbs_hook(HOOK_KEY_PRESSED, 0, &c);

		switch (c) {

		case 12:
			uo_idle_on = 1 - uo_idle_on;
			rf_redraw = 1;
			break;

		case 'u':
		case 'U':
			uo_idle_on = 0;
			rf_redraw = 1;
			break;
		case 'i':
		case 'I':
			uo_idle_on = 1;
			rf_redraw = 1;
			break;

		case 'q':
		case 'Q':
			data->quit = MENUQUIT_QUIT;
			uo__friends_free();
			return;

		case KEY_NPAGE:
		case KEY_RIGHT:
		case '+':
		case 'c':
		case 'C':
		case ' ':
			uo_scroll_forward(t_rows - 2);
			break;

		case KEY_DOWN:
			uo_scroll_forward(1);
			break;

		case 10:
		case 13:
			if (uo_pos <= (uo_list_len - t_rows)) {
				uo_scroll_forward(1);
			} else {
				data->quit = MENUQUIT_QUIT;
				uo__friends_free();
				return;
			}
			break;

		case KEY_UP:
			uo_scroll_back(1);
			break;

		case KEY_PPAGE:
		case KEY_LEFT:
		case '-':
		case 'b':
		case 'B':
			uo_scroll_back(t_rows - 2);
			break;

		case '.':
			uo__friends_edit();
			break;

		case 'e':
		case 'E':
			examine_user(0);
			rf_redraw = 1;
			break;

		case 's':
		case 'S':
			a = u2u_get_recipients();
			if (a) {
				u2u_send_to(a);
				free(a);
			}
			rf_redraw = 1;
			break;

		case -1:
		case 0:
			bbs_hook(HOOK_CHECK_MSGS, "yes", 0);
			break;

		default:
			bbs_hook(HOOK_KEY_MENU, data, &c);
			break;
		}

	} while (menuview_exitup == 0);

	uo__friends_free();
}

/* EOF */
