/* upg, the micro-pager. */
/* brg Mon Jun  7 13:32:59 PDT 1999 */

#include <termios.h>
#include <stdio.h>
#include <signal.h>
#include <errno.h>
#include <sys/ioctl.h>

struct termios save;
char *myname;
int rows, cols;
FILE *ttyfp;

void handler (int sig) {
	tcsetattr(fileno(ttyfp), TCSANOW, &save);
	fprintf(stderr, "exiting on signal %d\n", sig);
	exit(1);
}

void chkdbl(int arg, long *size, long **ptr)
{
	if (arg > *size) {
		*size *= 2;
		*ptr = (long *) realloc(*ptr,
			*size * sizeof(long));
		if (!*ptr) {
			fprintf(stderr,
				"%s: virtual memory exhausted\n",
				myname);
			exit(1);
		}
	}
}

void see_line(int *arg, long *size, long **ptr, FILE *fp)
{
	*arg = *arg + 1;
	chkdbl(*arg, size, ptr);
	(*ptr)[*arg] = ftell(fp);
}

void clearln(void)
{
	printf("\r                                                                                \r");
}

int pagefp (FILE *fp)
{
	long *linestarts = NULL, linestartsize;
	int lineno = 0, rv = 0, linesshown = 0, linesseen = 0, inputdigits = 0;
	int eof_seen = 0, at_end = 0;
	char ch, *buf = NULL;

	buf = (char *) malloc(BUFSIZ * sizeof(char));
	if (!buf) {
		fprintf(stderr, "%s: virtual memory exhausted\n", myname);
		exit(1);
	}
	linestartsize = 200;
	linestarts = (long *) malloc(linestartsize * sizeof(long));
	if (!linestarts) {
		fprintf(stderr, "%s: virtual memory exhausted\n", myname);
		exit(1);
	}

	while (! feof(fp)) {
		if (linesseen < lineno)
			see_line(&linesseen, &linestartsize, &linestarts, fp);
		if (fgets(buf, BUFSIZ, fp) != NULL) {
			lineno++;
			fputs(buf, stdout);
			linesshown++;
		} else {
			continue;
		}
		at_end = feof(fp);
		if (linesshown == rows - 1 || at_end) {
			if (at_end)
				eof_seen++;
		userinput:
			switch(ch = fgetc(ttyfp)) {
				case '\n':
					if (at_end) {
						rv = 0; clearln(); 
						goto donewithfile;
					}
					linesshown--; inputdigits = 0;
					continue;
				case ' ':
					linesshown = 0; inputdigits = 0;
					continue;
				case '0': case '1': case '2': case '3':
				 case '4': case '5': case '6': case '7':
				 case '8': case '9':
					inputdigits *= 10;
					inputdigits += (ch - '0');
					clearln();
					printf("%d", inputdigits);
					goto userinput;
				case '\010':
					inputdigits /= 10;
					clearln();
					printf("%d", inputdigits);
					goto userinput;
				case 'q':
					rv = -1; clearln();
					goto donewithfile;
				case 'n':
					rv = 0; clearln();
					goto donewithfile;
				case 'G':
					if (fseek(fp, ftell(fp), SEEK_SET) < 0) { 
						clearln();
						printf("fseek: %s?",strerror(errno));
						inputdigits = 0;
						goto userinput;
					}
					if (inputdigits == 0) {
						fseek(fp, linestarts[linesseen], SEEK_SET);
						if (!eof_seen) {
							while (! feof(fp)) {
								see_line(&linesseen, &linestartsize, &linestarts, fp);
								fgets(buf, BUFSIZ, fp);
							}
							eof_seen++;
						}
						lineno = linesseen;
						lineno -= (rows - 1); if (lineno < 0) lineno = 1;
						fseek(fp, linestarts[lineno], SEEK_SET);
						linesshown = 0;
						continue;
					}
					if (inputdigits < 1) {
						printf("%d<1?\a", inputdigits);
						inputdigits = 0;
						goto userinput;
					}
					if (inputdigits > linesseen) {
						fseek(fp, linestarts[linesseen], SEEK_SET);
						while (linesseen <= inputdigits) {
							if (feof(fp)) {
								eof_seen++;
								clearln();
								printf("%d>%d?\a", inputdigits, linesseen);
								inputdigits = 0;
								goto userinput;
							}
							see_line(&linesseen, &linestartsize, &linestarts, fp);
							fgets(buf, BUFSIZ, fp);
						}
					}
					fseek(fp, linestarts[inputdigits], SEEK_SET);
					lineno = inputdigits - 1;
					inputdigits = 0;
					linesshown = 0;
					continue;
				case '\014':
					lineno -= (rows - 1); if (lineno < 0) lineno = 1;
					fseek(fp, linestarts[lineno], SEEK_SET);
					linesshown = 0;
					continue;
				case 'b':
					lineno -= (2 * rows - 1); if (lineno < 0) lineno = 1;
					fseek(fp, linestarts[lineno], SEEK_SET);
					linesshown = 0;
					continue;
				case '\007':
					clearln(); printf("%d", lineno);
					if (eof_seen) {
						printf("/%d, %.2f%%", linesseen,
							((double) ((double)lineno) /
								((double)linesseen) * 100.0));
					}
					goto userinput;
				default:
					clearln();
					if (inputdigits) printf("%d", inputdigits);
					printf("?\a");
					goto userinput;
			}
		}
	}
donewithfile:
	free(buf);
	free(linestarts);
	return rv;
}

int pagefile (char *filename)
{
	FILE *fp; 
	int rc;

	if (strcmp(filename, "-") == 0) {
		rc = pagefp(stdin);
	} else {
		fp = fopen(filename, "r");
		if (!fp) {
			fprintf(stderr, "%s: could not open %s: %s (hit a key...)\n",
				myname, filename, strerror(errno));
			(void) fgetc(ttyfp);
			return 0;
		}
		rc = pagefp(fp);
		fclose(fp);
	}
	return rc;
}

void get_wsize (int *rows, int *cols)
{
	struct winsize cons_size;

	if (ioctl(0, TIOCGWINSZ, &cons_size) < 0) { 
		cons_size.ws_col = 80;
		cons_size.ws_row = 25;
	}
	*rows = cons_size.ws_row;
	*cols = cons_size.ws_row;
}

int main (int argc, char **argv)
{
	struct termios attrs;
	int i;

	myname = argv[0];

	ttyfp = fopen("/dev/tty", "r");
	if (!ttyfp) {
		ttyfp = stdout;
	}

	get_wsize(&rows, &cols);
	tcgetattr(fileno(ttyfp), &save);
	signal(SIGHUP, handler);
	signal(SIGINT, handler);
	signal(SIGQUIT, handler);
	tcgetattr(fileno(ttyfp), &attrs);
	attrs.c_lflag &= ~(ICANON|ECHO);
	tcsetattr(fileno(ttyfp), TCSANOW, &attrs);

	for (i = 1; i < argc; i++) {
		if (pagefile(argv[i]) < 0) {
			goto done;
		}
	}
	if (argc == 1) {
		if (isatty(fileno(stdin))) {
			fprintf(stderr, "Missing filename argument\n");
			exit(1);
		} else {
			pagefp(stdin);
		}
	}

done:
	tcsetattr(fileno(ttyfp), TCSANOW, &save);
	exit(0);
}

