/* Random access file viewer. PC specific */

#include "global.h"
#include "commands.h"
#include "session.h"
#ifdef MSDOS
#include <conio.h>
#else
#include "socket.h"
#endif

#if !defined(_lint)
static char rcsid[] OPTIONAL = "$Id: view.c,v 1.19 1997/08/19 01:19:22 root Exp $";
#endif


static long lineseek (FILE *fp,long offset,int nlines,int width);
static void view (int s, void *p1, void*p2);
extern char NoRead[];
extern char readmeFile[];
#ifdef UNIX
extern int Numrows, Numcols;
#endif


int
doview(argc,argv,p)
int argc OPTIONAL;
char *argv[];
void *p OPTIONAL;
{
char fname[256];
FILE *fp;

	/*allow only keyboard users to access the view command*/
	if (Curproc->input != Command->input) {
		tputs ("The 'view' command can only be executed from the command session.\nTry the 'more' command, instead!\n\n");
		return 0;
	}

	strncpy (fname, make_fname (Command->curdirs->dir,argv[1]), 256);
	if((fp = fopen (fname, READ_TEXT)) == NULLFILE)	{
		tprintf (NoRead, fname, SYS_ERRLIST(errno));
		return 1;
	}
	(void) newproc ("view", 512, view, 0, (void *)fp, (argv[1] == readmeFile) ? (void *) strdup("Reviewing the new features file") : (void *) strdup(argv[1]),(argv[1] == readmeFile) ? 0 : 2);
	return 0;	
}


/* Random-access file display program. Used both to read local
 * files with the "view" command, and by the FTP client to view
 * directory listings, temporary copies of read files, etc.
 */
static void
view (s, p1, p2)
int s;		/* If non-zero, poll interval for a changing file */
void *p1;	/* Open file pointer to read from */
void *p2;	/* If non-null, name to give to session. We free it */
{
struct session *sp;
FILE *fp;
char *name;
int c;
long offset = 0;
int row,col;
int cols;
int rows;
int32 polldelay = 0;
#ifndef UNIX
struct text_info text_info;

	gettextinfo (&text_info);
	cols = text_info.screenwidth;
	rows = text_info.screenheight-1;	/* Allow for status line */
#else
	cols = Numcols;
	rows = Numrows;
#endif

	fp = (FILE *)p1;
	name = (char *)p2;

	if ((sp = newsession (name, VIEW, 0)) == NULLSESSION)
		return;

	if (s != 0)
		polldelay = s;
	/* Put tty into raw mode so single-char responses will work */
	sp->ttystate.echo = sp->ttystate.edit = 0;
	for ( ; ; )	{
		fseek (fp, offset, SEEK_SET);
		clrscr ();
		/* Display a screen's worth of data, keeping track of
		 * cursor location so we know when the screen is full
		 */
		col = row = 0;
		while ((c = getc (fp)), c != EOF)	{
			switch (c)	{
				case '\n':	row++;
						col = 0;
						break;
				case '\t':	if (col < cols - 8)
							col = (col + 8) & ~7;
						break;
				default:	col++;
						break;
			}
			if (col >= cols)	{
				/* Virtual newline caused by wraparound */
				col = 0;
				row++;
			}
			if (row >= rows)
				break;	/* Screen now full */
			tputc ((unsigned char)c);
		}
		tflush();
		/* If we hit the end of the file and the file may be
		 * growing, then set an alarm to time out the getchar()
		 */
		do {
			if (feof(fp) && polldelay != 0)
				kalarm (polldelay);
			c = recvchar (Curproc->input);
			kalarm (0L);		/* Cancel alarm */
			if (c != EOF || errno != EALARM)
				break;		/* User hit key */
			/* Alarm timeout; see if more data arrived by
			 * clearing the EOF flag, trying to read
			 * another byte, and then testing EOF again
			 */
			clearerr (fp);
			(void)getc (fp);
			c = ' ';	/* Simulate a no-op keypress */
		} while (feof (fp));
		switch (c)	{
			case 'h':	/* Home */
			case 'H':
			case '<':	/* For emacs users */
				offset = 0;
				break;
			case 'e':	/* End */
			case '>':	/* For emacs users */
				fseek (fp, 0L, SEEK_END);
				offset = lineseek (fp, ftell (fp), -rows, cols);
				break;
			case CTLD:	/* Down one half screen (for VI users) */
				if (!feof (fp))
					offset = lineseek (fp, offset, rows / 2, cols);
				break;
			case CTLU:	/* Up one half screen (for VI users) */
				offset = lineseek (fp, offset, -rows / 2, cols);
				break;
			case 'd':	/* down line */
			case CTLN:	/* For emacs users */
			case 'j':	/* For vi users */
				if (!feof (fp))
					offset = lineseek (fp, offset, 1, cols);
				break;
			case 'D':	/* Down page */
			case CTLV:	/* For emacs users */
				if (!feof (fp))
					offset = lineseek (fp, offset, rows, cols);
				break;
			case 'u':	/* up line */
			case CTLP:	/* for emacs users */
			case 'k':	/* for vi users */
				offset = lineseek (fp, offset, -1, cols);
				break;
			case 'U':	/* Up page */
			case 'v':	/* for emacs users */
				offset = lineseek (fp, offset, -rows, cols);
				break;
			case CTLC:
			case 'q':
			case 'Q':
			case ESC:
				goto done;
			default:
				break;	/* Redisplay screen */
		}
	}
done:	(void) fclose (fp);
	freesession (sp);
}


/* Given a starting offset into an open file stream, scan forwards
 * or backwards the specified number of lines and return a pointer to the
 * new offset.
 */
static long
lineseek(fp,start,nlines,width)
FILE *fp;	/* Open file stream */
long start;	/* Offset to start searching backwards from */
int nlines;	/* Number of lines to move forward (+) or back (-) */
int width;	/* Screen width (max line size) */
{
long offset;
long *pointers;
int col = 0;
int c;
int newlines = 0;

	if (nlines == 0)
		return start;	/* Nothing to do */

	if (nlines > 0)	{	/* Look forward requested # of lines */
		fseek (fp, start, SEEK_SET);
		col = 0;
		while ((c = getc (fp)), c != EOF)	{
			switch (c)	{
				case '\n':	newlines++;
						col = 0;
						break;
				case '\t':	if (col < width - 8)
							col = (col + 8) & ~7;
						break;
				default:	col++;
						break;
			}
			if (col >= width)	{
				/* Virtual newline caused by wraparound */
				col = 0;
				newlines++;
			}
			if (newlines >= nlines)
				break;	/* Found requested count */
		}
		return (ftell (fp));	/* Could be EOF */
	}
	/* Backwards scan (the hardest)
	 * Start back up at most (width + 2) chars/line from the start.
	 * This handles full lines followed by expanded newline
	 * sequences
	 */
	nlines = -nlines;
	offset = (width + 2) * (nlines + 1);
	if (offset > start)
		offset = 0;	/* Go to the start of the file */
	else
		offset = start - offset;
	fseek (fp, offset, SEEK_SET);

	/* Keep a circular list of the last 'nlines' worth of offsets to
	 * each line, starting with the first
	 */
	if (nlines == 0)
		return offset;
	pointers = (int32 *) callocw (sizeof(long), (unsigned) nlines);
	if (pointers == (int32*) 0)
		return start;		/* shouldn't ever happen */
	pointers[newlines++ % nlines] = offset;

	/* Now count newlines up but not including the original
	 * starting point
	 */
	col = 0;
	for ( ; ; )	{
		c = getc (fp);
		switch (c)	{
			case EOF:	goto done;
			case '\n':	col = 0;
					offset = ftell (fp);
					if (offset >= start)
						goto done;
					pointers[newlines++ % nlines] = offset;
					break;
			case '\t':	if (col < width - 8)
						col = (col + 8) & ~7;
					break;
			default:	col++;
					break;
		}
		if (col >= width)	{
			/* Virtual newline caused by wraparound */
			col = 0;
			offset = ftell (fp);
			if (offset >= start)
				goto done;
			pointers[newlines++ % nlines] = offset;
		}
	}

done:
	if (newlines >= nlines)	/* Select offset pointer nlines back */
		offset = pointers[newlines % nlines];
	else 	/* The specified number of newlines wasn't seen, so go to the start of the file */
		offset = 0;

	free (pointers);
	return (offset);
}
