/* tail and extended head
 * !(c) 1992 D. Champion
 * Tail operates identically to the Unix tail.  Head operates identically
 * to the Unix head, but has the same options as tail.  This is the
 * extended feature of head; you can head relative to the beginning or end,
 * rather than just the beginning.  Unix tail's -r option is not yet
 * implemented, but we're still ahead of every other Amiga head/tail
 * I've seen.  Head and tail read any number of files in one command; if
 * more than one fail is headed or tailed, the output is prefixed with the
 * file name.  If no files are given, input is from standard input.
 *
 * Head and tail are the same program; to distinguish one function from
 * another, the filenames must end in "head" or "tail" as called from DOS.
 * The point here is code efficiency; if you use AmigaDOS 2.0's makelink
 * facility, you can cut down on total program size by 1/3 or more.  If you
 * don't have DOS 2.0, either deal with a slightly larger program sitting
 * on your disk twice, or go buy it.
 *
 * Usage: head [+|-num[l|b|c]] [file1 [file2 [...] ] ] : '-' is head-relative
 *        tail [+|-num[l|b|c]] [file1 [file2 [...] ] ] : '-' is tail-relative
 */

#ifdef AMIGA
# include <dos/dos.h>
#endif
#include <stdio.h>

#define shift		ac--; av++
#define LINELEN		255
#define BLOCKSIZE	512
#define HEAD		0
#define TAIL		1
#define HEADREL		HEAD
#define TAILREL		TAIL
#define CHARS		0
#define BLOCKS		1
#define LINES		2

char	version[]	= "$VER: Unified head/tail, v1.0. 16-Aug-92 D. Champion";
char	relflags[]	= "-+";
	/* standard offset table for each of 12 functions */
signed int	offstab[2][3][2]= {0, 1, 0, 1, 0, -1, 1, 0, 1, 0, 1, -2};

char	*myname;
char	buf[80];
char	mode	= HEAD;
int	numfiles;
#ifdef BEEF
int	OFFS	= 0;
#endif
#ifdef AMIGA
# ifdef LATTICE	/* This is still quite unhealthy.  Anyone have ideas?  It
                 * works on DICE.  This is a common problem, you know. */
   /* very nasty hack since Lattice can't figure out SEEK_END */
   /* WARNING! This only works with SEEK_END!  **NOT** SEEK_CUR
    * or SEEK_SET!  Modify this macro (extensively) or use another
    * for these offset relative bases. */
#  define fseek(p, d, r)	(p)->_rcnt -= (d); (p)->_flag &= ~_IOEOF
   /* Lattice knows no sleep(), as do many other environments:
    * use AmigaDOS function instead */
#  define sleep(a)		Delay((a))
# endif
#endif

main(ac, av)
int	ac;
char	**av;
{
int	offset	= 10;
FILE	*fp;
char	*names[]= {"head","tail"};
char	unit	= LINES;
char	rel;

	numfiles = ac - 1;
	if ( !ac ) exit(0);
	myname = av[0];
	if ( !strcmp(&myname[strlen(myname)-4], names[HEAD]) ) 		mode = HEAD;
	else if ( !strcmp(&myname[strlen(myname)-4], names[TAIL]) )	mode = TAIL;
	else report("unknown operation; assuming", names[mode]);

	rel = mode;
	shift;
	while ( ac ) {
		if ( (av[0][0] == '-') || (av[0][0] == '+') ) offset = parse_switch(av[0], &unit, offset, &rel);
#ifdef BEEF
		else if ( (av[0][0] == '=' ) ) OFFS=atoi(&av[0][1]);
#endif
		else {
			if ( offset < 0 ) report("bad range spec; skipping file", av[0]);
			else if ( !(fp = fopen(av[0], "r")) ) report("can't open", av[0]);
			else {
#ifdef BEEF
				fprintf(stderr, "%s, %d, %d, %s, %srel\n", names[mode], offset, unit, av[0], names[rel]);
#endif
				if ( numfiles > 1 ) report("file:", av[0]);
				do_seg(fp, unit, offset, rel, av[0]);
				fclose(fp);
			}
		}
		shift;
	}
	if ( numfiles == 0 ) do_seg(stdin, unit, offset, rel, NULL);
	clean(0);
}

int parse_switch(char *sw, char *unit, int offset, char *rel)
{
char	*p;

	numfiles--;

	if ( sw[0] == relflags[mode] ) *rel = HEADREL;
	if ( sw[0] == relflags[mode^1] ) *rel = TAILREL;

	p=&sw[1];
	while ( isdigit(*p) ) p++;
	switch ( *p ) {
	case	'c':
		*unit=CHARS;
		break;
	case	'b':
		*unit=BLOCKS;
		break;
	case	'l':
		*unit=LINES;
		break;
	case	'\0':
		break;
	default:
		report("undefined unit", p);
		return(-1);
	}
	*p='\0';
	return( atoi(sw+1) );
}

do_seg(FILE *fp, char unit, int offset, char rel, char *fn)
{
#ifdef GCC
register char	*line;
#else
register char	line[LINELEN];
#endif
register int	c;
register int	numlines	= 0;
register int	numchars	= 0;

#ifdef GCC
	line=(char *)malloc(LINELEN);
#endif

	/* offsets to offset */
	if ( unit == BLOCKS ) offset *= BLOCKSIZE;
	offset += offstab[mode][unit][rel];
#ifdef BEEF
	offset += OFFS;
#endif

	if ( rel == TAILREL ) {
		while ( (c=fgetc(fp)) != EOF ) {
			numchars++;
			if ( c == '\n' ) numlines++;
		}
		fseek(fp, -1, SEEK_END);
		if ( fgetc(fp) != '\n' ) numlines++;
		rewind(fp);
	}

	c = mode - 1;			/* always recycle */
	if ( unit == LINES ) {
		if ( rel == TAILREL) offset = numlines - offset - 1;
		while ( --offset > c ) strop(fgets(line, LINELEN, fp));
	}
	else if ( (unit == BLOCKS) || (unit == CHARS) ) {
		if ( rel == TAILREL) offset = numchars - offset + 1;
		while ( --offset > c ) chrop(fgetc(fp));
	}

	if ( mode == TAIL ) while ( (fgets(line, LINELEN, fp)) ) fputs(line, stdout);

#ifdef GCC
	free(line);
#endif
	return;
}

report(char *str, char *mod)
{
	fprintf(stderr, "\n%s: %s %s\n", myname, str, mod);
}

strop(char *str)
{
	if ( mode == HEAD ) fputs(str, stdout);
	return;
}

chrop(int c)
{
	if ( mode == HEAD ) fputc(c, stdout);
	return;
}

clean(int code)
{
	exit(code);
}
