/* New for v2.0: readline support -- daw */

/* this is the main bunch of code for readline; lots of misc stuff here */

#ifdef HAVE_STRING_H
#include <string.h>
#else
#ifdef HAVE_STRINGS_H
#include <strings.h>
#endif
#endif

#include <sys/types.h>
#include <sys/time.h>
#include <stdio.h>
#include <errno.h>
#include <readline/readline.h>
#include "tintin.h"

/* this gets set when user hits control-z, so we don't worry about EINTR */
int			ignore_interrupt;

/* should we display the user's commands in the top window, too? */
int			am_purist = DEFAULT_PURIST_MODE;

/* the user requested this screen size -- kill autodetection code if set */
int			requested_scrsize = -1;

/* essentially the size of the screen; only for split mode */
static int	splitline, cols;
extern void	scrsize();

/* what column we were at in the top window; only for split mode */
static int		topwin_col = 0;

#define ISAPROMPT(x)	(strchr((x), '>'))

/* some readline stuff */
int			(*rl_event_hook)();
int			(*rl_completion_entry_function)();
extern char		E; /* @@@added -- perry */
extern int		_rl_last_c_pos;
extern int		_rl_eof_char;
extern int		readline_echoing_p;
extern int		history_expand();

extern int		errno;
extern struct session	*sessionlist, *activesession;
extern int		is_split;
extern int		redraw;
extern int		puts_echoing;
extern struct session	*parse_input();
extern struct session	*newactive_session();
extern char		*rltab_generate();
extern char		*rlhist_expand();
void			tintin_puts();
extern char		*get_arg_in_braces();

static void
ctrl_r_redraw(count, key)
	int	count;
	int	key;
{
	if (is_split) {
		goto_rowcol(splitline+1, 0);
		erase_toeol();
	} else
		printf("\n");
	fflush(stdout);
	_rl_last_c_pos = 0;
	rl_forced_update_display();
}

static void	readmud();

/* for use with rl_event_hook */
static void
bait(/* void */)
{
	fd_set		readfds;
	struct session	*s, *t;
	struct timeval	to;
	int		rv, maybe_redraw;

	FD_ZERO(&readfds);
	while (!FD_ISSET(0, &readfds)) {
		FD_ZERO(&readfds);
		FD_SET(0, &readfds);	/* stdin */
		for (s=sessionlist; s; s=s->next)
			FD_SET(s->socket, &readfds);
		to.tv_sec = checktick(); /* 100 */
		to.tv_usec = 0;

		ignore_interrupt = 0;
		rv = select(FD_SETSIZE, &readfds, NULL, NULL, &to);

		if (rv == 0)
			continue;	/* timeout */
		else if (rv < 0 && errno == EINTR && ignore_interrupt)
			continue;	/* don't worry, be happy */
		else if (rv < 0)
			syserr("select");

		maybe_redraw = 0;
		for (s=sessionlist; s; s=t) {
			t = s->next;	/* s may be free()d out from under us */
			if (FD_ISSET(s->socket, &readfds)) {
				readmud(s);
				maybe_redraw = 1;
			}
		}
		if (maybe_redraw && redraw && !is_split)
			ctrl_r_redraw();
	}
}

void
initrl(/* void */)
{
	rl_readline_name = "tintin++";
	rl_completion_entry_function = (int (*)()) rltab_generate;
	using_history();
	stifle_history(HISTORY_SIZE);
	rl_variable_bind("horizontal-scroll-mode", "on");
	rl_bind_key('\022', ctrl_r_redraw);	/* control-r */
	rl_bind_key_in_map('\022', ctrl_r_redraw, vi_movement_keymap);
	rl_bind_key_in_map('\022', ctrl_r_redraw, vi_insertion_keymap);
	rl_event_hook = (int (*)()) bait;

	/*
	 * a hack to let ^d work like it used to in old tt++:
	 * by default, readline won't let you rebind the eof
	 * character when it's at the start of the line, so
	 * let's pull the wool over readline.  yeeeeeehaw!
	 * bug: remember to undo the stty!  check portability!
	 */
	_rl_eof_char = '\001'; /* control-b : something innocuous */
	system("stty eof '^b'");
	rl_bind_key('\004', rl_get_previous_history); /* control-d */
	rl_bind_key_in_map('\004', rl_get_previous_history, vi_movement_keymap);
	rl_bind_key_in_map('\004', rl_get_previous_history,vi_insertion_keymap);
}

/* turn on split mode */
void
initsplit(arg)
	int arg; /* @@@added -- perry */
{
	char statusbar[256];
	char *p;
	int x = 0;

	/* notice the screen size and remember it */
	scrsize(&splitline, &cols);
	if (requested_scrsize > 0)
		splitline = requested_scrsize;
	splitline--;

	is_split = 1;
	topwin_col = 0;
	reset();
	erase_screen();
	scroll_region(1, splitline-1);
	goto_rowcol(splitline, 0);

	/* @@@changed -- perry */
	if (activesession && arg) {
		sprintf(statusbar, "%c[7;37m%-9s%%-%ds%20s%c[0m",
			E, "Session: ", cols - 29, "Tintin" VERSION_NUM, E);
		p = activesession->name;
		x = 1;
	}
	else {
		sprintf(statusbar, "%c[7;37m%%-%ds%20s%c[0m",
			E, cols - 20, "Tintin" VERSION_NUM, E);
		p = "No Session active";
	}
	printf(statusbar, p);
	goto_rowcol(splitline+1, 0);
	save_pos();
	fflush(stdout);
}

/*
 * ahh, gotta love coding around hardware bugs :-(
 * [this beauty does linewrapping for terminals which
 * are too dumb to do it themselves -- only needed
 * for split mode, i think.]
 * 
 * 's' better not contain any \n or \r's.
 * if 'isaprompt' is non-zero, then we won't tack
 * a \n to the end of 's', and try to remember that.
 * 
 * bug: this doesn't work right for muds that send ansi
 * escape sequences.  (yes, servers that do that are
 * arguably broken, but unfortunately, I don't get a
 * chance to argue).  perhaps the linewrapping should
 * be disabled. <sigh>
 */
/* @@@changed -- perry  (non-static) */
void
printline(s, isaprompt)
	char	*s;
{
	int	i, n = cols - topwin_col;

	if (is_split)
		for (i=strlen(s); i > n; i-=n, s+=n, n=cols) {
			printf("%.*s\n", n, s);
			topwin_col = 0;
		}
	if (isaprompt && is_split) {
		topwin_col += strlen(s) + 1;
		printf("%s", s); /* don't append \n */
	} else {
		topwin_col = 0;
		puts(s);
	}
}

void
mainloop(/* void */)
{
	char	*line;

	initrl();

	for (;;) {
		line = readline("");
		if (line == NULL)
			syserr("readline() == NULL");
		line = rlhist_expand(line);
		if (line == NULL)
			continue;

		if (is_split)
			goto_rowcol(splitline-1, topwin_col);

		/* commands will echo to the top screen, except for purists */
		if (is_split) {
			if (!am_purist)
				printline(line, 0);
			else {
				/* should I echo newlines after prompts? */
				/* printline("", 0); */
				;
			}
		}

		activesession = parse_input(line, activesession);

		if (is_split) {
			goto_rowcol(splitline+1, 0);
			erase_toeol();
			fflush(stdout);
		}

		free(line);
	}
}

/* an uncalled noop -- for now */
static void
see_prompt(b, l)
	char	*b;
	int	l;
{
	/* soon this will try to recognize prompts and process them */
	/* maybe use IACGA */
}

/* data waiting on this mud session; read & display it; do the dirty work */
static void
readmud(s)
	struct session	*s;
{
	char	thebuffer[2*BUFFER_SIZE+1], *buf, *p, *q;
	char	linebuf[BUFFER_SIZE], header[BUFFER_SIZE];
	int	rv, i, headerlen;

	buf = thebuffer + BUFFER_SIZE;
	rv = read_buffer_mud(buf, s);
	if (rv == 0) {
		cleanup_session(s);
		if (s == activesession)
			activesession = newactive_session();
		return;
	} else if (rv < 0)
		syserr("readmud: read");
	buf[++rv] = '\0';

	if (s == activesession)
		header[0] = '\0';
	else if (s->snoopstatus)
		sprintf(header, "%s%% ", s->name);
	else
		return;
	headerlen = strlen(header);

	if (s->old_more_coming) {
		p = s->last_line;
		buf -= strlen(p);
		while (*p)
			*buf++ = *p++;
		buf -= strlen(s->last_line);
		s->last_line[0] = '\0';
	}

	logit(s, buf);

	if (is_split) {
		save_pos();
		goto_rowcol(splitline-1, topwin_col);
	}

	/* separate into lines and print away */
	for (p=buf; p && *p; p=q) {
		if ((q = strchr(p, '\n')) == NULL && s->more_coming)
			break;
		if (q) {
			*q++ = '\0';
			if (*q == '\r')			/* ignore \r's */
				q++;
		}
		strcpy(linebuf, p);
		do_one_line(linebuf, s);		/* changes linebuf */
		if (strcmp(linebuf, ".") != 0) {
			strcat(header, linebuf);
			printline(header, (q == NULL) && ISAPROMPT(header));
			header[headerlen] = '\0';
		}
	}
	if (p && *p)
		strcpy(s->last_line, p);

	if (is_split)
		restore_pos();
	fflush(stdout);

	/* q: do we need to do some sort of redraw here?  i don't think so */
	/* a: no, i do it at the end of mainloop() [for now] */
}


/*
 * output to screen should go through this function
 * the output is NOT checked for actions or anything
 */
void
tintin_puts2(cptr, ses)
	char		*cptr;
	struct session	*ses;
{
	char	*p;

	if ((ses != activesession && ses != 0) || !puts_echoing)
		return;
	if (is_split) {
		save_pos();
		goto_rowcol(splitline-1, topwin_col);
	}
	printline(cptr, 0);
	if (is_split)
		restore_pos();
	fflush(stdout);

	/* q: do we need to do some sort of redraw here?  i don't think so */
	/* a: right now, i think so */
	if (redraw && !is_split)
		ctrl_r_redraw();
}

/*
 * output to screen should go through this function
 * the output IS treated as though it came from the mud
 */
void
tintin_puts(cptr, ses)
	char		*cptr;
	struct session	*ses;
{
	/* bug! doesn't do_one_line() sometimes send output to stdout? */
	/* bug: doesn't do_one_line() modify it's input?  are we ok here? */
	if (ses)
		do_one_line(cptr, ses);
	tintin_puts2(cptr, ses);  
}

/* nobody needs tintin_puts3 anymore*/

/* get a clean screen without the split crap; useful for ^Z, quitting, etc */
void
cleanscreen(/* void */)
{
	/* i think term_echo() was unportable.  try to avoid using echo.c */
	/* term_echo(); */
	system("stty echo");	/* a hack, admittedly */

	/* undo the "stty eof '^b'" from initrl() */
	system("stty eof '^d'");

	if (!is_split)
		return;
	scroll_region(1, splitline+1);
	erase_screen();
	reset();
	fflush(stdout);
}

/* undo cleanscreen(); useful after ^Z */
void
dirtyscreen(/* void */)
{
	if (is_split)
		initsplit(1); /* @@@changed -- perry */

	/* put the eof = ^b back; see cleanscreen() and initrl() */
	system("stty eof '^b'");
}

/* quit tintin++ and print a message */
void
quitmsg(m)
	char	*m;
{
	struct session	*s;

	for (s=sessionlist; s; s=s->next)
		cleanup_session(s);
	cleanscreen();
	if (m)
		printf("%s\n", m);
	printf("Goodbye from tintin++.\n");
	exit(0);
}

/* quit tintin++ fast!  for use with signal() */
void
myquitsig(/* void */)
{
	quitmsg(NULL);
}

/*
 * if the user requests a certain screen size, who are we
 * to autodetect things and ignore his request? :-)
 */
void
split_command(arg, ses)
	char	*arg;
	struct session	*ses;
{
	char	left[BUFFER_SIZE];

	if (is_split) {
		cleanscreen();
		is_split = 0;
	}

	arg = get_arg_in_braces(arg, left, 0);
	if (!*left)
		requested_scrsize = -1;
	else if ((requested_scrsize = atoi(left)) <= 0)
		requested_scrsize = -1;

	initsplit(1); /* @@@changed -- perry */
}

void
unsplit_command(/* void */)
{
	requested_scrsize = -1;
	cleanscreen();
	is_split = 0;
}

void
purist_command(/* void */)
{
	tintin_puts("#Ok, purist mode enabled.", 0);
	am_purist = 1;
}

void
unpurist_command(/* void */)
{
	tintin_puts("#Ok, purist mode disabled.", 0);
	am_purist = 0;
}