/* pr - print files			Author: Michiel Huisjes */

/* Pr - print files
 *
 * Author: Michiel Huisjes.
 * Modified: Jacob P. Bunschoten.	(30 nov 87)
 *	When "columns" is not given and numbering is on:
 *		line numbers are correlated with input lines.
 *	(try pr [-1] -n file )
 *	tabs are accounted for.
 *	When numbering is turned on, width know this.
 *	automatic line-folding. -f to get the original program.
 *	backspaces are accounted for. -b to disable this.
 *	multi-column mode changed.
 *	header can be given and used.
 *	format changed may occur between printing of several files:
 *		pr -l30 file1 -w75 file2
 *
 * Modified: Rick Thomas.		(Sept 12, 1988)
 *	added "-M" option to cover functionality of old "-n" option,
 *	and made "-n" option behavior compatible with system V.
 *
 * Usage: pr [+page] [-columns] [-h header] [-wwidth] [-llength] [-ntm] [files]
 *        -t : Do not print the 5 line header and trailer at the page.
 *        -n : Turn on line numbering.
 *        -M : Use "Minix" style line numbering -- Each page begins at
 *             a line number that is an even multiple of the page length.
 *             Like the listings in Appendix E of the book.
 *        +page    : Start printing at page n.
 *        -columns : Print files in n-columns.
 *        -l length: Take the length of the page to be n instead of 66
 *        -h header: Take next argument as page header.
 *        -w width  : Take the width of the page to be n instead of default 79
 *	  -f : do not fold lines.
 *
 * Modified: Lars Fredriksen		(Jan 19, 1990)
 *	fixed the program so that
 *		pr -n *.c
 *	would work. The clobal variable 'width' was decremented
 *	by NUM_WIDTH, for each file, resulting in width finally
 *	being so small that nothing was printed. Used the local
 *	variable 'w' for the width adjustment (in print())
 *
 * Modified: Kenneth J. Hendrickson	(10 April 1991)
 *	date in header should be last modification date for files,
 *	and the current time for stdin.
 *
 * Modified: Kees J. Bot		(5 October 1992)
 *	Use localtime(3) to get the date, it knows TZ.
 */

#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <time.h>
#include <stdlib.h>
#include <stdio.h>


#define DEF_LENGTH	66
#define DEF_WIDTH	79
#define NUM_WIDTH	8
#define TAB_WIDTH	8	/* fixed tab_width */

/* Used to compute next (fixed) tabstop */
#define TO_TAB(x)	(( (x) + TAB_WIDTH ) & ~07 )

typedef char BOOL;

#define FALSE		0
#define TRUE		1

/* EAT:	eat rest of input line */
#define EAT(fp)		while((c=getc(fp))!='\n' && c!=EOF)

/* L_BUF: calculate address of pointer to char (string) used in format */
#define L_BUF(i,j)	* (char **) (line_buf + (i + j*length)*sizeof(char *))

char *header;
BOOL no_header;
BOOL number = FALSE;
BOOL minix_number = FALSE;
BOOL ext_header_set = FALSE;	/* external header given */
BOOL back_space = TRUE;		/* back space correction in line width */
BOOL dont_fold = FALSE;		/* original. If the line does not fit eat it. */
short columns;
short cwidth;
short start_page = 1;
short width = DEF_WIDTH;
short length = DEF_LENGTH;
short linenr;
char *line_buf;			/* used in format for multi-column output */

char output[1024];

_PROTOTYPE(int main, (int argc, char **argv));
_PROTOTYPE(static char *myalloc, (size_t size));
_PROTOTYPE(char skip_page, (int lines, int width, FILE * filep));
_PROTOTYPE(void format, (FILE * filep));
_PROTOTYPE(void print_page, (int pagenr, int maxcol));
_PROTOTYPE(void print, (FILE * filep));
_PROTOTYPE(void out_header, (int page));
_PROTOTYPE(void print_time, (time_t t));

int main(argc, argv)
int argc;
char *argv[];
{
  FILE *file;
  char *ptr;
  int index = 1;		/* index is one ahead of argc */
  int line, col;

  setbuf(stdout, output);
  do {
	if (argc == index)	/* No arguments (left) */
		goto pr_files;

	ptr = argv[index++];
	if (*ptr == '+') {
		start_page = atoi(++ptr);
		continue;
	}
	if (*ptr != '-') {	/* no flags */
		index--;
		goto pr_files;
	}
	if (*++ptr >= '0' && *ptr <= '9') {
		columns = atoi(ptr);
		if (columns <= 0) columns = 1;
		continue;	/* Fetch next flag */
	}
	while (*ptr) switch (*ptr++) {
		    case 't':	no_header = TRUE;	break;
		    case 'n':
			number = TRUE;
			minix_number = FALSE;
			break;
		    case 'M':
			number = TRUE;
			minix_number = TRUE;
			break;
		    case 'h':
			header = argv[index++];
			ext_header_set = TRUE;
			break;
		    case 'w':
			if ((width = atoi(ptr)) <= 0) width = DEF_WIDTH;
			*ptr = '\0';
			break;
		    case 'l':
			if ((length = atoi(ptr)) <= 0) length = DEF_LENGTH;
			*ptr = '\0';
			break;
		    case 'b':	/* back_space correction off */
			back_space = FALSE;
			break;
		    case 'f':	/* do not fold lines */
			dont_fold = TRUE;
			break;
		    default:
			fprintf(stderr, "Usage: %s [+page] [-columns] [-h header] [-w<width>] [-l<length>] [-nMt] [files]\n", argv[0]);
			exit(1);
		}
	continue;		/* Scan for next flags */


	/* ==============  flags are read. Print the file(s) ========= */

pr_files:

	if (!no_header) length -= 10;

	if (columns) {
		cwidth = width / columns + 1;
		if (columns > width) {
			fprintf(stderr, "Too many columns for page width.\n");
			exit(1);
		}

		/* Allocate piece of mem to hold some pointers */
		line_buf = myalloc(length * columns * sizeof(char *));
	}
	for (line = 0; line < length; line++)
		for (col = 0; col < columns; col++)
			L_BUF(line, col) = NULL;

	if (length <= 0) {
		fprintf(stderr, "Minimal length should be %d\n", no_header ?
			1 : 11);
		exit(1);
	}
	while (index <= argc) {	/* print all files, including stdin */
		if (index < argc && (*argv[index] == '-' || *argv[index] == '+'))
			break;	/* Format change */

		if (argc == index) {	/* no file specified, so stdin */
			if (!ext_header_set) header = "";
			file = stdin;
		} else {
			if ((file = fopen(argv[index], "r")) == (FILE *) 0) {
				fprintf(stderr, "Cannot open %s\n", argv[index++]);
				continue;
			}
			if (!ext_header_set) header = argv[index];
		}
		if (columns)
			format(file);
		else
			print(file);
		fclose(file);
		if (++index >= argc)
			break;	/* all files (including stdin) done */
	}
	if (index >= argc) break;
	/* When control comes here. format changes are to be done.
	 * reinitialize some variables */
	if (!no_header) length += 10;

	start_page = 1;
	ext_header_set = FALSE;
	if (columns) free(line_buf);
  } while (index <= argc);	/* "pr -l60" should work too */

  (void) fflush(stdout);
  return(0);
}

char skip_page(lines, width, filep)
int lines, width;
FILE *filep;
{
  short c;
  int char_cnt;
  int w;

  do {
	w = width;
	if (number)		/* first lines are shorter */
		if (!columns ||	/* called from print(file)  */
		    !(lines % columns))	/* called from format(file) */
			w -= NUM_WIDTH;

	char_cnt = 0;
	while ((c = getc(filep)) != '\n' && c != EOF && char_cnt < w) {
		/* Calculate if this line is longer than "width (w)"
		 * characters */
		if (c == '\b' && back_space) {
			if (--char_cnt < 0) char_cnt = 0;
		} else if (c == '\t')
			char_cnt = TO_TAB(char_cnt);
		else
			char_cnt++;
	}
	if (dont_fold && c != '\n' && c != EOF) EAT(filep);
	lines--;
	if (c == '\n') linenr++;
  } while (lines > 0 && c != EOF);

  return c;			/* last char read */
}

void format(filep)
FILE *filep;
{
  char buf[512];
  short c = '\0';
  short index, lines, i;
  short page_number = 0;
  short maxcol = columns;
  short wdth;
  short line, col;

  do {
	/* Check printing of page */
	page_number++;

	if (page_number < start_page && c != EOF) {
		c = (char) skip_page(columns * length, cwidth, filep);
		continue;
	}
	if (c == EOF) return;

	lines = columns * length;
	for (line = 0; line < length; line++)
		for (col = 0; col < columns; col++) {
			if (L_BUF(line, col) != NULL)
				free(L_BUF(line, col));
			L_BUF(line, col) = (char *) NULL;
		}
	line = 0;
	col = 0;
	do {
		index = 0;
		wdth = cwidth - 1;
		if (number && !col)	/* need room for numbers */
			wdth -= NUM_WIDTH;

		/* Intermidiate colums are shortened by 1 char */
		/* Last column not */
		if (col + 1 == columns) wdth++;
		for (i = 0; i < wdth - 1; i++) {
			c = getc(filep);
			if (c == '\n' || c == EOF) break;

			if (c == '\b' && back_space) {
				buf[index++] = '\b';
				if (--i < 0) {	/* just in case ... */
					i = 0;
					index = 0;
				}
			} else if (c == '\t') {
				int cnt, max;

				max = TO_TAB(i);
				for (cnt = i; cnt < max; cnt++)
					buf[index++] = ' ';
				i = max - 1;
			} else
				buf[index++] = (char) c;
		}
		buf[index++] = '\0';
		/* Collected enough chars (or eoln, or EOF) */

		/* First char is EOF */
		if (i == 0 && lines == columns * length && c == EOF) return;

		/* Alloc mem to hold this (sub) string */
		L_BUF(line, col) = myalloc(index * sizeof(char));
		strcpy(L_BUF(line, col), buf);

		line++;
		line %= length;
		if (line == 0) {
			col++;
			col %= columns;
		}
		if (dont_fold && c != '\n' && c != EOF) EAT(filep);
		lines--;	/* line ready for output */
		if (c == EOF) {
			maxcol = columns - lines / length;
		}
	} while (c != EOF && lines);
	print_page(page_number, maxcol);
  } while (c != EOF);
}

void print_page(pagenr, maxcol)
short pagenr, maxcol;
{
  short pad, i, j;
  short width;
  char *p;

  if (minix_number)
	linenr = (pagenr - 1) * length + 1;
  else
	linenr = 1;

  if (!no_header) out_header(pagenr);

  for (i = 0; i < length; i++) {
	for (j = 0; j < maxcol; j++) {
		width = cwidth;
		if (number && j == 0) {	/* first columns */
			printf("%7.7d ", linenr++);	/* 7 == NUM_WIDTH-1 */
			width -= NUM_WIDTH;
		}
		pad = 0;
		if (p = (char *) L_BUF(i, j))
			for (; pad < width - 1 && *p; pad++) putchar(*p++);
		if (j < maxcol - 1) while (pad++ < width - 1)
				putchar(' ');
	}
	putchar('\n');
  }
  if (!no_header) printf("\n\n\n\n\n");
}

void print(filep)
FILE *filep;
{
  short c = '\0';
  short page_number = 0;
  short lines;
  short cnt;
  short w = width;
  BOOL pr_number = TRUE;	/* only real lines are numbered, not folded
			 * parts */

  linenr = 1;
  if (number) w -= NUM_WIDTH;

  do {
	/* Check printing of page */
	page_number++;

	if (page_number < start_page && c != EOF) {
		pr_number = FALSE;
		c = skip_page(length, w, filep);
		if (c == '\n') pr_number = TRUE;
		continue;
	}
	if (c == EOF) return;

	if (minix_number) linenr = (page_number - 1) * length + 1;

	if (page_number == start_page) c = getc(filep);

	/* Print the page */
	lines = length;
	while (lines && c != EOF) {
		if (lines == length && !no_header) out_header(page_number);
		if (number)
			if (pr_number)
				printf("%7.7d ", linenr++);	/* 7 == NUM_WIDTH-1 */
			else
				printf("%7c ", ' ');	/* 7 == NUM_WIDTH-1 */
		pr_number = FALSE;
		cnt = 0;
		while (c != '\n' && c != EOF && cnt < w) {
			if (c == '\t') {
				int i, max;
				max = TO_TAB(cnt);
				for (i = cnt; i < max; i++) putchar(' ');
				cnt = max - 1;
			} else if (c == '\b' && back_space) {
				putchar('\b');
				cnt--;
			} else
				putchar(c);
			c = getc(filep);
			cnt++;
		}
		putchar('\n');
		if (dont_fold && c != '\n' && c != EOF) EAT(filep);
		lines--;
		if (c == '\n') {
			c = getc(filep);
			pr_number = TRUE;
		}
	}
	if (lines == length)	/* We never printed anything on this
				 * page --  */
		return;		/* even the header, so dont try to fill it up */
	if (!no_header)		/* print the trailer -- 5 blank lines */
		printf("\n\n\n\n\n");
  } while (c != EOF);

  /* Fill last page */
  if (page_number >= start_page) {
	while (lines--) putchar('\n');
  }
}

static char *myalloc(size)
size_t size;			/* How many bytes */
{
  void *ptr;

  ptr = malloc(size);
  if (ptr == NULL) {
	fprintf(stderr, "malloc returned NULL\n");
	exit(1);
  }
  return(char *) ptr;
}

void out_header(page)
short page;
{
  time_t t;
  struct stat buf;

  if (strlen(header)) {
	stat(header, &buf);	/* use last modify time for file */
	t = buf.st_mtime;
  } else
	(void) time(&t);	/* use current time for stdin */
  print_time(t);
  printf("  %s   Page %d\n\n\n", header, page);
}

char *moname[] = {
	  "Jan", "Feb", "Mar", "Apr", "May", "Jun",
	  "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
};

/* Print the date. */
void print_time(t)
time_t t;
{
  struct tm *tm;

  tm = localtime(&t);

  printf("\n\n%s %2d %2d:%02d %d",
         moname[tm->tm_mon],
         tm->tm_mday,
         tm->tm_hour,
         tm->tm_min,
         1900 + tm->tm_year
	);
}
