/* 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();
int color_strlen();
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 (isaprompt) {
		if (is_split)
			topwin_col += color_strlen(s) + 1;
		printf("%s", s);	/* don't append \n */
	}
	else {
		topwin_col = 0;
		puts(s);
	}
}

int color_strlen(const char *s)
{
	int len = 0;

	while (*s) {
		if (*s == 033) {
			while (!isalpha(*s))
				s++;
			s++;
			continue;
		}
		len++, s++;
	}

	return len;
}

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, 1);
			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);
		if (strcmp(linebuf, ".") != 0) {
			strcat(header, linebuf);
/* @@@changed -- perry  (prints newlines halfway through line if its not */
/*						 a prompt) */
/*			printline(header, (q == NULL) && ISAPROMPT(header)); */
			printline(header, (q == NULL));
			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;
}