/*
 * Copyright (c) 1980 Regents of the University of California.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *	This product includes software developed by the University of
 *	California, Berkeley and its contributors.
 * 4. Neither the name of the University nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

/*
 * 1999-02-22 Arkadiusz Mi¶kiewicz <misiek@pld.ORG.PL>
 * - added Native Language Support
 *
 * 2000-07-30 Per Andreas Buer <per@linpro.no> - added "q"-option
 *
 * 2007-03-16 Andrew McGill <andrew lunch za net> added screensaver, timeout
 * and usage options.  Changed from two forked processes to one select()
 * process (that was a big deal).  Cleaned up (and/or mangled) variable names,
 * now that there are more of them, it matters more.   Added the script > foo
 * (record with timing escape codes) and script < foo (replay with timing)
 * usages.  Also keystroke logging ... because it could be handy.  While I was
 * at it, I removed the TCSAFLUSH which discards buffered input before we get
 * going.  It's just irritating. Typeahead is a good thing (tm).
 *
 * TODO: 
 *   a filter to strip vt100 escape characters, and render the text almost readable
 *   a browseable screenshot replay (forward, backwards, skip, seek, etc)
 *   browser-based replay -> convert typescript to html and javascript ...
 *
 */

/*
 * script
 */
#include <stdio.h>
#include <stdlib.h>
#include <paths.h>
#include <time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <termios.h>
#include <sys/ioctl.h>
#include <sys/time.h>
#include <sys/file.h>
#include <sys/signal.h>
#include "nls.h"
#include <ctype.h>
#include <errno.h>
/* not sure which systems provide/require this ... */
#include <sys/select.h>

#ifdef __linux__
#include <unistd.h>
#include <string.h>
#endif

#include "../defines.h"
#undef HAVE_openpty
#ifdef HAVE_openpty
#include <pty.h>
#endif

void finish(int);
void hangup(int);
void done(void);
void fail(void);
void resize(int);
void fixtty(void);
void getmaster(void);
void getslave(void);
void doinputoutput(void);
void doshell(void);
void usage(void);
int scriptreplay(void);
void replay_char(int c);

char	*shell;
FILE	*fscript = NULL ;       /* typescript file */
int	master;                 /* master side of child terminal */
int	slave;                  /* slave side of child terminal */
int	child;
char	*scriptfilename = NULL; /* typescript file name */
FILE    *outputstream;          /* where we send the output -- stdout by default */
FILE    *keylogfile = NULL;     /* capture keystrokes - by default, don't */

struct	termios termios_orig;
struct	winsize windowsize_orig;
#ifndef HAVE_openpty
char	line[] = "/dev/ptyXX";
#endif
int     replaymode = 0;         /* Set to 1 if we are replaying an existing typescript */
int     writetimingescapes = 0; /* Set to 1 if we are writing timing together with data */
int     opentypescript = 1;   /* If we send data to stdout, we don't create a separate typescript file */
char    *typescriptopenmode = "w"; /* fopen options for typescript (and keylog file) */
char  	*keylogfilename = NULL; /* Log keystrokes to here */
char  	*shellcommand = NULL;   /* Run this command instead of a shell */
char	*screensaver = NULL;    /* Screensaver command */
int	idleinuse = 0;          /* Whether we have a -i option for idle time */
struct timeval idle_timeout;         /* Idle time in timeval */
double	idleseconds = 0.0;      /* How long before screensaver runs */
double	speed = 1.0;            /* How fast do we replay */
int	flushflag = 0;          /* Flush typescript file writes */
int	quietflag = 0;          /* Un-verbose */
FILE	*timingfile = NULL;     /* Where are we sending timing info */
char    exitmessage[120];

static char *progname;

void
usage(void) {
        fprintf(stderr, 
                "script: Record and replay terminal transcripts:\n"
                "\n"
                "record:\n"
                "  script [options] > typescript++\n"
                "  script [options] [typescript] [-t 2>timing]\n"
                "record options:\n"
                "        -f  flush buffers after each write\n"
                "        -a  append to typescript file\n"
                "        -t  write timing to stderr\n"
                "        -q  quiet -- no messages\n"
                "    -c cmd  Run given shell command instead of a shell\n"
                "    -s cmd  Run screensaver when idle (default is to exit)\n"
                "      -i n  Idle time in seconds. Exit if no screensaver\n"
                "   -k file  Log keystrokes to file\n"
                "\n"
                "replay\n"
                "  script [options] < typescript++\n"
                "  script [options] -t [typescript] < timing\n"
                "  script [options] timingfile [typescript [speed] ]\n"
                "replay options\n"
                "      -i n  Pause for a maximum of n seconds during replay\n"
                "        -t  Read timing from stdin\n"
                "  -x speed  Replay at speed times n\n"
                "file:\n"
                "      file  write to this file, default ./typescript\n"
                );
}

static void
die_if_link(char *fn) {
	struct stat s;

	if (lstat(fn, &s) == 0 && (S_ISLNK(s.st_mode) || s.st_nlink > 1)) {
		fprintf(stderr,
			_("Warning: `%s' is a link.\n"
			  "Use `%s [options] %s' if you really "
			  "want to use it.\n"
			  "Script not started.\n"),
			fn, progname, fn);
		exit(1);
	}
}


/* isn't there a builtin?? */
char mkhexchar(char hi,char lo);

char
mkhexchar(char hi,char lo) {
        char *hexstring="0123456789ABCDEF";
        char *h, *l;
        /* hope it's safe to assume ascii ... */
        if (hi>='a') hi-='a'-'A';
        if (lo>='a') lo-='a'-'A';
        h = strchr(hexstring,hi);
        l = strchr(hexstring,lo);
        if (h && l) {
                return (h-hexstring)<<4 | (l-hexstring);
        }
        else return '?';
}

/*
 * script -t prints time delays as floating point numbers
 * The example program (scriptreplay) that we provide to handle this
 * timing output is a perl script, and does not handle numbers in
 * locale format (not even when "use locale;" is added).
 * So, since these numbers are not for human consumption, it seems
 * easiest to set LC_NUMERIC here.
 */

int
main(int argc, char **argv) {
	extern int optind;
	char *p;
	int ch;

        outputstream=stdout; /* generally this is 1 */

        /* init? */
        strcpy(exitmessage,"");

	progname = argv[0];
	if ((p = strrchr(progname, '/')) != NULL)
		progname = p+1;


	setlocale(LC_ALL, "");
	setlocale(LC_NUMERIC, "C");	/* see comment above */
	bindtextdomain(PACKAGE, LOCALEDIR);
	textdomain(PACKAGE);
	
	if (argc == 2) {
		if (!strcmp(argv[1], "-V") || !strcmp(argv[1], "--version")) {
			printf(_("%s from %s\n"),
			       progname, util_linux_version);
			return 0;
		}
	}

        /* Examine our file descriptors, and decide what mode we are in */
        /* If we have standard input from a stream, we'll do replay mode */
        if (!isatty(fileno(stdin))) {
                replaymode=1;
                timingfile=stdin;
                typescriptopenmode="r";
                opentypescript = 0; /* don't use 'typescript' unless we're asked */
        }
        /* stderr is automatically for timing if it's not a tty */
        else if (! isatty(fileno(stderr))) {
                timingfile=stderr;
        }
        /* Usage: script > foo.newtimingformat */
        else if (! isatty(fileno(stdout))) {
                outputstream=stderr;
                fscript=stdout;
                writetimingescapes = 1;
                opentypescript = 0; /* we'll just stdout, thanks */
        }

        /* Command line arguments */
	while ((ch = getopt(argc, argv, "k:ac:fqs:i:?tT:x:")) != -1)
		switch((char)ch) {
		case 'a':
			typescriptopenmode = "a";
			break;
		case 'c':
			shellcommand = optarg;
			break;
		case 's':
			screensaver = optarg;
                        if (idleseconds == 0.0) {
                                idleseconds = 600.0;
                                idleinuse++;
                        }
			break;
		case 'x':
                        speed = atof(optarg);
                        if (speed < 0.01) {
                                fprintf(stderr,"script: Speed parameter %s invalid\n", optarg);
                                return 1;
                        }
                        replaymode=1;
                        typescriptopenmode="r";
			break;
		case 'i':
                        idleseconds = atof(optarg);
                        idleinuse++;
			break;
		case 'f':
			flushflag++;
			break;
		case 'q':
			quietflag++;
			break;
		case 't':
                        if (isatty(fileno(stdin))) {
                                timingfile=stderr;
                        }
                        else {
                                replaymode=1;
                                timingfile=stdin;
                                typescriptopenmode="r";
                        }
                        break;
		case 'k':
                        keylogfilename = optarg;
			break;
		case '?':
		default:
                        usage();
			exit(1);
		}
	argc -= optind;
	argv += optind;

	if (argc > 1) {
                /* Usage: -- like scriptreplay timingfile typescript speed - */
                replaymode = 1;
		timingfile = fopen(argv[0], "r");
                if (timingfile == NULL ) {
                        perror(argv[0]);
                        fail();
                }
		scriptfilename = argv[1];
                opentypescript = 1;
                typescriptopenmode="r";
                if (argc>2) {
                        speed = atof(argv[2]);
                        if (speed < 0.01) {
                                fprintf(stderr,"script: Speed parameter %s invalid\n", argv[2]);
                                return 1;
                        }
                }
                /* TODO: it would be nice to play back key recordings in a new session ... but we don't ... yet */
                keylogfilename = NULL;
        }
	else if (argc > 0) {
		scriptfilename = argv[0];
                opentypescript = 1;
        }
	else if (opentypescript) {
		scriptfilename = "typescript";
		die_if_link(scriptfilename);
	}
        if (opentypescript)  {
                if ((fscript = fopen(scriptfilename, typescriptopenmode)) == NULL) {
                        perror(scriptfilename);
                        fail();
                }
        }
        if (keylogfilename) {
                if ( (keylogfile = fopen(keylogfilename, typescriptopenmode)) == NULL) {
                        perror(scriptfilename);
                        fail();
                }
        }

        /* Convert idle for usage */
        if (idleinuse) {
                idle_timeout.tv_sec = idleseconds;
                idle_timeout.tv_usec = (idleseconds-idle_timeout.tv_sec)*1000000;
        }

        /* and, ACTION: */
        if (replaymode) {
                return scriptreplay();
        }

	shell = getenv("SHELL");
	if (shell == NULL)
		shell = _PATH_BSHELL; /* any bourne shell by default */

	getmaster();
	if (!quietflag && scriptfilename)
		fprintf(outputstream,_("Script started, file is %s\n"), scriptfilename);
	fixtty();

	(void) signal(SIGCHLD, finish);
	child = fork();
	if (child < 0) {
		perror("fork");
		fail();
	}
	if (child == 0) {
                doshell();
	} else {
                /* Install SIGWINCH handler to relay window size changes to child */
		(void) signal(SIGWINCH, resize);
		(void) signal(SIGHUP, hangup);
                doinputoutput();
        }

	return 0;
}

#include <sys/wait.h>

/* We're dead! Kill the shell! */
void
hangup(int dummy) {
	(void) kill(child, SIGHUP);
        finish(dummy);
}

void
finish(int dummy) {
	int status;
	register int pid;
	register int die = 0;

	while ((pid = wait3(&status, WNOHANG, 0)) > 0)
		if (pid == child)
			die = 1;

	if (die)
		done();
}

void
resize(int dummy) {
	/* transmit window change information to the child */
	(void) ioctl(0, TIOCGWINSZ, (char *)&windowsize_orig);
	(void) ioctl(master, TIOCSWINSZ, (char *)&windowsize_orig);
	kill(child, SIGWINCH);
}

/*
 * Stop extremely silly gcc complaint on %c:
 *  warning: `%c' yields only last 2 digits of year in some locales
 */
static void
my_strftime(char *buf, size_t len, const char *fmt, const struct tm *tm) {
	strftime(buf, len, fmt, tm);
}

void
doinputoutput() {
	register int cc;
	time_t tvec;
	char obuf[BUFSIZ];
	char ibuf[BUFSIZ];

	struct timeval tv = {0,0} , last_tv = {0,0} , dt;
	double oldtime=0.0, newtime;

        fd_set rfds, wfds, erfds;
        struct timeval timeout, *use_timeout;
        int selectrc, endoffile=0;
        int timing_extra_chars = 0;
        char timing_escape[32];

#ifdef HAVE_openpty
	(void) close(slave);
#endif
	tvec = time((time_t *)NULL);
	my_strftime(obuf, sizeof obuf, "%c\n", localtime(&tvec));
        if (quietflag) {
                /* If we're being quiet, add this line just for compatibility with replay */
                if (fscript) fprintf(fscript,"\n");
        }
        else {
                if (fscript) {
                        fprintf(fscript, _("Script started on %s"), obuf);
                }
        }
        if (timingfile || writetimingescapes) {
                gettimeofday(&last_tv, NULL);
                tv = last_tv;
        }
        if (timingfile) {
                oldtime = last_tv.tv_sec + (double) last_tv.tv_usec / 1000000;
        }

        use_timeout = NULL;
        if (idleinuse && idleseconds != 0.0) {
                use_timeout = &timeout;
        }
	while (!endoffile) {
                /* Watch stdin (fd 0) to see when it has input. */
                FD_ZERO(&rfds);  FD_SET(0, &rfds); FD_SET(master, &rfds);
                FD_ZERO(&wfds);
                FD_ZERO(&erfds);
                FD_SET(0, &erfds);
                FD_SET(master, &erfds);
                /* Wait up to five seconds. */
                if (use_timeout) timeout = idle_timeout;
                do {
                        selectrc = select(master+1, &rfds, NULL, NULL, use_timeout);
                } while (selectrc < 0 && errno == EINTR );
                /* Error condition */
                if (selectrc < 0) {
                        if (!quietflag) {
                                snprintf(exitmessage,sizeof(exitmessage)-1,
                                        _("select: %s\n"),strerror(errno));
                        }
                        fail();
                }
                /* Timeout condition */
                if (selectrc ==0) {
                        if (screensaver) {
                                /* Stop everything and run the screensaver
                                 * program ... in the original pty .. mostly */
                                system(screensaver);
                        }
                        else {
                                /* No screensaver means terminate on idle. Save the message, and we're outta here */
                                if (!quietflag) {
                                        if (idleseconds == 1.0) 
                                                snprintf(exitmessage,sizeof(exitmessage)-1,
                                                         _("\nScript saw no activity for a whole second"));
                                        else
                                                snprintf(exitmessage,sizeof(exitmessage)-1,
                                                        _("\nScript saw no activity for %g seconds"), idleseconds);
                                }
                                endoffile=1;
                        }
                }

                /* Read from stdin, write to master */
                if (!endoffile && FD_ISSET(0, &rfds)) {
                        cc = read(0, ibuf, sizeof(ibuf));
                        if (cc==0) {
                                endoffile=1;
                        }
                        else if (cc<0 && errno == EINTR) { cc=0; } 
                        else if (cc<0) fail();
                        if (cc) {
                                (void) write(master, ibuf, cc); /* and if it blocks, we wait */
                                if (keylogfile) {
                                        fwrite(ibuf,1,cc,keylogfile);
                                        if (flushflag) (void) fflush(keylogfile);
                                }
                        }
                }
                /* Read from master, write to stdout */
                if (endoffile || FD_ISSET(master, &rfds)) {
                        if (endoffile) {
                                /* We "read" zero bytes, because it's EOF on input or something */
                                cc=0;
                        }
                        else {
                                cc = read(master, obuf, sizeof (obuf));
                                if (cc == 0) {
                                        endoffile=1;
                                }
                                else if (cc < 0 && errno==EINTR) cc=0;  /* ignore INTR */
                                else if (cc < 0) fail();
                        }
                        if (writetimingescapes) {
                                /* All this complexity just to avoid floating point maths */
                                gettimeofday(&tv, NULL);
                                if (tv.tv_sec < last_tv.tv_sec || 
                                   ( tv.tv_sec == last_tv.tv_sec && tv.tv_usec < last_tv.tv_usec )) {
                                        dt.tv_sec = 1;  /* time like a rolling stream ever forward */
                                        dt.tv_usec = 0;
                                }
                                else {
                                        if (tv.tv_usec >= last_tv.tv_usec) {
                                                dt.tv_sec  = tv.tv_sec  - last_tv.tv_sec;
                                                dt.tv_usec = tv.tv_usec - last_tv.tv_usec;
                                        }
                                        else {
                                                dt.tv_sec  = tv.tv_sec  - last_tv.tv_sec - 1;
                                                dt.tv_usec = tv.tv_usec - last_tv.tv_usec + 1000000;
                                        }
                                }

                                last_tv = tv;
                                if (dt.tv_sec || dt.tv_usec) {
                                        /* This is the format for fake delays.  It's vaguely compatible with the Linux
                                         * terminal type of escape, and it has an fair chance of being ignored if you
                                         * simply cat the recorded stuff to a terminal.  It is utterly incompatible
                                         * with ECMA-48's SDS code, which is hopefully not a problem.
                                         * If we happen to receive this in the input stream, sorry.  You can't tell the
                                         * difference between the one we put there and the one that the application
                                         * emitted. You can strip these timing escapes with sed
                                         * s/.\[42;[0-9;]*\]//g -- where "." is a real escape character for
                                         * proper correctness  */

                                        char *timing_escape_format = "\e[42;%lu;%06lu]";
                                        snprintf(timing_escape, sizeof(timing_escape), timing_escape_format, dt.tv_sec, dt.tv_usec);
                                        timing_extra_chars = strlen(timing_escape);
                                }
                                else {
                                        timing_extra_chars = 0;
                                }
                        }
                        if (timingfile) {
                                /* this is a bizarre buglet to be compatible
                                 * with the original version: the interval
                                 * matches to the *next* timing line ...
                                 */
                                
                                newtime = tv.tv_sec + (double) tv.tv_usec / 1000000;
                                fprintf(timingfile, "%f %i\n", 
                                        newtime>oldtime ? newtime - oldtime : 1.0, /* 1.0 seconds for clock-goes-back */
                                        cc + timing_extra_chars);
                                oldtime = newtime;
                                gettimeofday(&tv, NULL);  
                        }
                        /* Send to terminal */
                        if (cc) {
                                write(fileno(outputstream), obuf, cc);
                        }
                        /* Send to log file */
                        if (fscript) {
                                if (timing_extra_chars) {
                                        (void) fwrite(timing_escape, 1, timing_extra_chars, fscript);
                                }
                                if (cc)  {
                                        (void) fwrite(obuf, 1, cc, fscript);
                                        if (flushflag) (void) fflush(fscript);
                                }
                        }
                }
	}
	done();
}

void
doshell() {
	char *shname;

#if 0
	int t;

	t = open(_PATH_TTY, O_RDWR);
	if (t >= 0) {
		(void) ioctl(t, TIOCNOTTY, (char *)0);
		(void) close(t);
	}
#endif

	getslave();
	(void) close(master);
        if (fscript)
                (void) fclose(fscript);
	(void) dup2(slave, 0);
	(void) dup2(slave, 1);
	(void) dup2(slave, 2);
	(void) close(slave);

	shname = strrchr(shell, '/');
	if (shname)
		shname++;
	else
		shname = shell;

	if (shellcommand)
		execl(shell, shname, "-c", shellcommand, (char *) NULL);
	else
		execl(shell, shname, "-i", (char *) NULL);

	perror(shell);
	fail();
}

/* Set terminal to raw, and turn off echo */
void
fixtty() {
	struct termios rtt;

	rtt = termios_orig;
	cfmakeraw(&rtt);
	rtt.c_lflag &= ~ECHO;
   /* If you do this, script discards input that you typed while you were
    * waiting for it to start up.  It's an un-feature */
	/* (void) tcsetattr(0, TCSAFLUSH, &rtt); */
	(void) tcsetattr(0, TCSANOW, &rtt);
}

void
fail() {
        /* Your terminal has died, your phone has hung up - that's SIGHUP to
         * the kids */
	(void) kill(child, SIGHUP);
	done();
}

void
done() {
	time_t tvec;

        if (!quietflag) {
                char buf[BUFSIZ];
                tvec = time((time_t *)NULL);
                my_strftime(buf, sizeof buf, "%c\n", localtime(&tvec));
                /* If there's a reason, say it here */
                if (fscript) {
                        fprintf(fscript, "%s", exitmessage);
                        fprintf(fscript, _("\nScript done on %s"), buf);
                }
        }
        if (fscript)
                (void) fclose(fscript);
        (void) close(master);
        (void) tcsetattr(0, TCSAFLUSH, &termios_orig);
        if (!quietflag) {
                fprintf(outputstream,"%s", exitmessage);
                fprintf(outputstream,"\n");
                if (scriptfilename) {
                        fprintf(outputstream,_("Script done, file is %s\n"), scriptfilename);
                }
        }
        else {
                fprintf(outputstream,"\n");  /* This is prettier, since the command
                                            easily leave without a linefeed */
        }
	exit(0);
}

void
getmaster(void) {
#ifdef HAVE_openpty
	(void) tcgetattr(0, &termios_orig); /* fileno(stdin) */
	(void) ioctl(0, TIOCGWINSZ, (char *)&windowsize_orig);
	if (openpty(&master, &slave, NULL, &termios_orig, &windowsize_orig) < 0) {
		fprintf(stderr, _("openpty failed\n"));
		fail();
	}
#else
	char *pty, *bank, *cp;
	struct stat stb;

	pty = &line[strlen("/dev/ptyp")];
	for (bank = "pqrs"; *bank; bank++) {
		line[strlen("/dev/pty")] = *bank;
		*pty = '0';
		if (stat(line, &stb) < 0)
			break;
		for (cp = "0123456789abcdef"; *cp; cp++) {
			*pty = *cp;
			master = open(line, O_RDWR);
			if (master >= 0) {
				char *tp = &line[strlen("/dev/")];
				int ok;

				/* verify slave side is usable */
				*tp = 't'; /* line="/dev/tty.." */
				ok = access(line, R_OK|W_OK) == 0;
				*tp = 'p'; /* line="/dev/pty.." */
				if (ok) {
					(void) tcgetattr(0, &termios_orig);
				    	(void) ioctl(0, TIOCGWINSZ, 
						(char *)&windowsize_orig);
					return;
				}
				(void) close(master);
			}
		}
	}
	fprintf(stderr, _("Out of pty's\n"));
	fail();
#endif /* not HAVE_openpty */
}

void
getslave() {
#ifndef HAVE_openpty
	line[strlen("/dev/")] = 't';
	slave = open(line, O_RDWR);
	if (slave < 0) {
		perror(line);
		fail();
	}
	(void) tcsetattr(slave, TCSAFLUSH, &termios_orig);
	(void) ioctl(slave, TIOCSWINSZ, (char *)&windowsize_orig);
#endif
	(void) setsid();
	(void) ioctl(slave, TIOCSCTTY, 0);
}

/*
 * scriptreplay(): replay the typescript file to stdout with timing from stdin
 * Very much like scriptreplay.pl
 *
 * Here's how the timing script works:
 *
 * interval-1 bytes-2
 * interval-2 bytes-3
 * interval-3 bytes-4
 *
 * So, to replay, we read interval-1, sleep interval 1, display 0 bytes
 * read interval-2, sleep interval-2, display bytes-2 bytes
 * read interval-3, sleep interval-3, display bytes-3 bytes
 */
int
scriptreplay(void)
{
        int line_is_terminated;
        char buffer[64], *p;
        double time;
        int cc;
        int delayedbytes;
        int rc;
        int c;

        buffer[sizeof(buffer)-1]=0;

        if (fscript == NULL) {
                fscript = timingfile;
                timingfile = NULL;
        }
        if (timingfile) {
                /* Discard header line of typescript */
                fgets(buffer,sizeof(buffer)-1, fscript);
        }
        delayedbytes=0;
        if (timingfile==NULL)
        {
                for (;;) {
                        c = fgetc(fscript);
                        if (c==EOF) 
                                break;
                        replay_char(c);
                }
                replay_char(-1);
        }
        else 
        /* Read from timing file, and copy to output */
        for (;;) {
                /* First form: separate typescript (data) and timing streams */
                /* Line format is: floating point time interval, number of characters 
                 * Second form: This may be followed by the characters themselves ...
                 * */

                if ( fgets(buffer,sizeof(buffer)-1,timingfile) == NULL) {
                        break;
                }
                cc=0;
                time=0.0;
                rc = ( sscanf(buffer,"%lf %i\n",&time, &cc) == 2);
                if (!rc) {
                        /* fprintf(stderr, "Bad timing: %s\n",buffer); */
                        continue;
                }
                if (idleinuse && time > idleseconds) {
                        time = idleseconds;
                }
                /* Wait as long as we waited for this input */
                if (time > 0.001) {
                        fflush(stdout);
                        usleep( (unsigned int)(time*1000000) );
                }
                /* In replay mode from old timing format we use this */
                if (delayedbytes) {
                        while (delayedbytes > 0) {
                                c = fread(buffer,1, 
                                                delayedbytes > sizeof(buffer)-1 ? sizeof(buffer)-1 : delayedbytes ,
                                                fscript);
                                if (c<=0) {
                                        /* FIXME ... report error? */
                                        if (ferror(fscript)) {
                                                perror("read");
                                        }
                                        return 1;
                                }
                                delayedbytes -= c;   /* ! not delayedbytes =- c; */
                                if (fwrite(buffer,1,c,stdout) == 0 ) {
                                        if (ferror(fscript)) {
                                                perror("write");
                                        }
                                        return 2;
                                }
                        }
                }
                /* Embedded timing and text */
                if (fscript) {
                        delayedbytes = cc;
                }
                else {
                        /* print out the rest of the line from the timing file */
                        p=strchr(buffer,' ');
                        if (!p) { /* bad input ... */ continue; }
                        p++; /* move to after ' ' */
                        line_is_terminated = ( buffer[strlen(buffer)-1]=='\n'); 
                        while (*p!='\n') {
                                /* We stop reading a bit before the end to
                                 * avoid escaped control characters wrapping
                                 * over buffer boundaries "\\xAA\0" is 5
                                 * characters */
                                if (! line_is_terminated && p >= buffer + sizeof(buffer) - 8 ) { 
                                        /* Close to end of a partial line? get another! */
                                        int len = strlen(p);
                                        memmove(buffer,p,len+1);
                                        if (fgets(buffer+len,sizeof(buffer)-1-len,timingfile) == NULL) {
                                                if (len == 0) break;
                                        }
                                        p=buffer;
                                        if (*p ==0) break;
                                        line_is_terminated = ( buffer[strlen(buffer)-1]=='\n'); 
                                }
                                /* escape code handling */
                                if (p[0] == '\\') {
                                        if (p[1]=='n') { fputc('\n',stdout); p+=2; }
                                        else if (p[1]=='r') { fputc('\r',stdout); p+=2; }
                                        else if (p[1]=='\\') { fputc('\\',stdout); p+=2; }
                                        else if (p[1]=='x' && isxdigit(p[2]) && isxdigit(p[3]) ) {
                                                fputc(mkhexchar(p[2],p[3]),stdout);
                                                p+=4;
                                        }
                                        else {
                                                fputc('\\',stdout); p++; 
                                        }
                                }
                                else {
                                        fputc(*p,stdout);
                                        p++;
                                }
                        }
                }
        }
        fflush(stdout);
        return 0;
}

void replay_char(int c)
{
        static char escape[32];
        static char escape_pattern[32] = "\e[42;";
        int pattern_length = 5;
        static int match_length = 0;
        struct timeval t;
        char *p;
        
        if (c == -1) { /* flush */
        }
        else if (match_length < pattern_length) {
                if (c == escape_pattern[match_length]) {
                        escape[match_length] = c;
                        match_length++;
                        return;
                }
        }
        else {
                if (c == ']') {
                        /* We have a sleep escape in our buffer, so let's get the parameters from it, and sleep that long */
                        t.tv_sec = atol(escape+pattern_length);
                        p = strpbrk(escape+pattern_length,";");

                        if (p) {
                                t.tv_usec = atol(p+1);
                        }
                        else {
                                t.tv_usec = 0;
                        }
                        if (speed != 1.0) {
                                t.tv_usec /= speed;  /* Hey! Floating point maths! */
                                t.tv_sec /= speed;
                        }
                        if (idleinuse && ( (t.tv_sec > idle_timeout.tv_sec) ||
                                           (t.tv_sec == idle_timeout.tv_sec && t.tv_usec > idle_timeout.tv_usec))) {
                                t = idle_timeout;
                        }
                        if (t.tv_sec || t.tv_usec > 1000) {
                                /* Send output, sleep, and we're done with this byte */
                                fflush(stdout);
                                select(0,NULL,NULL,NULL,&t);
                        }
                        match_length = 0;
                        return;
                }
                else if ( (c>='0' && c<='9') || (c ==';') )  {
                        if (match_length < sizeof(escape)) {
                                escape[match_length] = c;
                                match_length++;
                                return;
                        }
                }
        }
        if (match_length) {
                fwrite(escape,match_length,1,stdout);
                match_length = 0;
        }
        if (c!=-1) {
                fputc(c,stdout);
        }
}


