/*
** tail -- Shows the last n lines of a file.
**
**  Copyright (C) 1987 By Robert G. Arsenault
**
**  This software is provided "as-is" and carries with it no explicit or
**  implicit promise of support by the author. Nor will the author be 
**  held liable for any damages, real or imagined, which may result 
**  from the use or abuse of this software.
**
**  The author grants permission for the non-commercial distribution of 
**  this software provided that this and other identifying information 
**  remains intact.
**
** Useage: tail -n file, where
**			n is the number of lines to be shown (default is 15);
**			file is the input file to be used.
**
** About the program:
**  This program first opens the current window for error message
**  outputting.  Then, the program useage is tested, and if
**  called incorrectly, appropiate error messages are printed.
**
**  Then, the input file is briefly tested: The file is opened,
**  seeked, and the last byte is read and compared to a new-line
**  character.  If they are not equal, the file is assumed to NOT 
**  be a standard ascii text file, and the program exits with 
**  an appropiate message.
**
**  The program next allocates a buffer for file reading.  A buffer
**  of 80 bytes per line is attempted to be allocated, and if there
**  isn't enough memory, progressive attempts are made with sizes
**  64 bytes less than before, stopping if no buffer is allocated before
**  decreasing to a size of 64 bytes.  Upon no buffer being able to 
**  be allocated, the program exits with an error message.
**  The finally allocated buffer size shall now be referred to as bsize.
**
**  Now, a buffer of size bsize is read from the tail end of the file,
**  with further reads coming before the previous read, until the
**  head of the file is reached.  With each buffer read, a search takes place
**  for the desired number of new-lines, plus one more.  Once the required
**  number is found, no more buffers will be read in this searching manner.
**  While looking for new-lines, the intermediate characters incountered 
**  between new-lines are tabulated, and if the number exceeds 256, the 
**  program aborts, declaring the file to be non-ascii.
**  
**  The last part is printing out what is required:  The lines that 
**  are currently sitting in the buffer, after the last new-line found,
**  and all that is in the file from this buffer read onward to the 
**  end of the file is output to the Amiga Dos Understanding of what
**  the output should go to.
**
*/
#include <exec/types.h>
#include <libraries/dosextens.h>

#define LSIZE 256	/* longest allowable line size (before abort occurs) */
#define FSIZE 60	/* path + file size in characters                    */
#define LINES 15	/* number of lines from end to display  (default)    */
#define NL  '\n'

char	*buffer = 0;			/* file input buffer pointer         */
int		bsize = 0; 				/* dynamically allocated buffer size */
struct FileHandle *f_err = 0;	/* file handle to current window     */

main(argc,argv)
int argc;
char *argv[];
{
	struct FileHandle *Open();		/* Amiga Dos Function, Opens file   */
	char *AllocMem();				/* Exec function to allocate memory */

	LONG	Seek();		/* Amiga Dos Function, returns prior file position */
	LONG	Read();     /* Amiga Dos Function, returns number bytes read   */

	struct FileHandle *f_hand = 0;	/* file handle to opened input file */

	SHORT	lines = LINES; 	/* default is the last 15 lines             */
	char	file_nm[FSIZE];	/* path+file name of input (see FSIZE above */
	char	*c;				/* character pointer when scanning buffer   */
	LONG	n;				/* read function return save location       */
	LONG	prior_pos;		/* prior position is file                   */


	/* Open the file handle that will be used if an error message */
	/* must ever be displayed.									  */
	if (!(f_err = Open ("*", MODE_OLDFILE))) {
		/* cannot open the current window for output -- give up */
		exit (10);
	}

	/* Test the users calling format...                           */
	/* There are four allowable calling formats                   */
	/* 0 arguments , display useage                               */
	/* 1 argument == file name                                    */
	/* 1 argument == ?  (stupid amiga standard)                   */
	/* 2 arguments, first is number of lines, second is file name */

	if ((argc == 1) || (argc > 3)) {
HELP:
		Write (f_err, "Useage: tail [-[3m[1mn[0m] [3m[1mfile[0m, where\n [3m[1mn[0m     is the number of lines to be shown,\n [3m[1mfile[0m  is the file to be read.\n", 152);
		goto CLOSE_UP;
	} else if (argc == 3) {
		if (argv[1][0] != '-') {
			/* three arguments, and first is not a line number */
			goto HELP;
		} else if (!(lines = (SHORT) atoi (argv[1] + 1))) {
			/* the specified line number cannot be zero */
			Write (f_err, "tail: The input line number is either zero or an\n      invalid number entry.\n", 77);
			goto CLOSE_UP;
		}
		strncpy (file_nm, argv[2], FSIZE);
	} else if (argv[1][0] == '?') {
		goto HELP;
	} else {
		strncpy (file_nm, argv[1], FSIZE);
	}

	if (!(f_hand = Open (file_nm, MODE_OLDFILE))) {
		Write (f_err, "tail: cannot open \"",19);
		Write (f_err, file_nm, strlen(file_nm));
		Write (f_err, "\" for reading.\n", 15);
		goto CLOSE_UP;
	}

	/* simple checks before doing a whole lot of processing */

	/* test seek to end of file */
	prior_pos = Seek (f_hand, -1L, OFFSET_END);
	if (prior_pos < 0) {
		Write (f_err, "tail: file operation error.\n      Could not seek to the last byte in the file.\n", 79);
		goto CLOSE_UP;
	}

	prior_pos = Seek (f_hand, 0L, OFFSET_CURRENT);

	/* increment size in case we can't fill even one buffer */
	/* and this is used as previous fill point in loop.     */
	++prior_pos;

	/* test read of one byte */
	n = Read (f_hand, file_nm, 1L);
	if (n != 1 ) {
		Write (f_err, "tail: file operation error.\n      Could not read a last byte in the file.\n      Check the input file size (must be greater than zero).\n", 136);
		goto CLOSE_UP;
	}

	if (*file_nm != NL) {
		Write (f_err, "tail: file type error.\n      The last byte in the file is not a new-line character.\n      The input file must be of standard ascii text.\n", 137);
		goto CLOSE_UP;
	}

	/* dynamically allocate a buffer as big as needed and possible */
	for (bsize = lines * 128; (bsize > 64) ; bsize -= 64) {
		if (buffer = AllocMem (bsize, 0)) {
			/* Allocated my buffer */
			break;
		}
	}
	if (!buffer) {
		/* Never got a buffer allocated */
		Write (f_err, "tail: Cannot not enough memory to allocate a buffer.\n", 53);
		goto CLOSE_UP;
	}


	/* start a new block to declare some local variables */
	{
		register LONG between = 0, line_size = LSIZE;
		register SHORT aborted = 0, n;
		register SHORT found = 0, l = lines+1;
		SHORT	buf_n=0;	/* buffers read in so far in search         */ 

		while (!aborted && (found < l)) {

			/* go to the end of the file - minus buf_n buffer sizes */
			n = Seek (f_hand, (LONG) -((++buf_n) * bsize), OFFSET_END);

			if (n < 0) {

				/* we tried to go beyond the start of the file */
				Seek (f_hand, 0L, OFFSET_BEGINNING);

				/* get a full buffer from beginning to prior position */
				n = Read (f_hand, buffer, (LONG)prior_pos);

			} else {

				prior_pos = Seek (f_hand, 0L, OFFSET_CURRENT);

				/* try to get a full buffer */
				n = Read (f_hand, buffer, (LONG)bsize);
			}

			/* now, count back the required number of lines, */
			/* denoted by new-lines.                         */
			for (c = (buffer + n - 1) ; (c >= buffer) && (found < l) ; --c) {
				if (*c == NL) {
					++found;
					between = 0;
				} else {
					if ((++between) > line_size) {
						Write (f_err, "tail: The file does not seem to be in a standard ascii format.\n      A line has been found that is at least or more than 132 characters.\n", 138);
						aborted = 1;
						break;
					}
				}
			}
			if (n < bsize)
				break;
		}

		if (!aborted) {

			/* check for file exhaustion */
			if ((n < bsize) && (l > found)) {
				/* input file is exhausted    */
				/* and we never found enough. */
				/* show everything (entire buffer and the rest of the file */
				show (buffer, n, buf_n, f_hand);
			} else {
				/* we found the last required new_line plus one more.  */
				/* show the entire buffer, except this last new_line,  */
				/* plus the entire rest of the file.                   */
				/* If the new-line was the very last character */
				/* in the buffer, it is okay, because we pass  */
				/* a count of zero.                            */

				show (c+2, n - (c - buffer) - 2, buf_n, f_hand);
			}
		}
	}
	/* close up everything */
CLOSE_UP:
	if (f_err)
		Close (f_err);
	if (f_hand)
		Close (f_hand);
	if (buffer)
		FreeMem (buffer, bsize);
}

show (b, c, n, f)
char *b;
SHORT c;
SHORT n;
struct FileHandle *f;
{
	struct FileHandle *out_of_hand, *Output();

	if (!(out_of_hand = Output())) {
		Write (f_err, "tail: Cannot access the standard Amiga Dos Output() file handle.\n      Aborting...\n", 83);
	} else {

		/* write everything needed that is in the buffer */
		if (c)
			Write (out_of_hand, b, c);

		/* read and write the rest of the file */
		while (--n) {
			if (c = Read (f, buffer, bsize))
				Write (out_of_hand, buffer, c);
		}
	}
}
