/* Paginate.c Copyright © 1993 Fergus Duniho */

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <time.h>
#include "NumToStr.c"

size_t seglen(char *s);
void overwrite (char *dest, char *str, size_t pos, size_t dlen, size_t slen);
char *justify (char *str, size_t length);
char *addstr (char *rstr, char *str);
void print_usage ();
void lineprint (char *line, int page, int lmar, int len);
char *convert (char *str, int page, int len);
char *timecnv (char *fmt, struct tm *tptr, size_t len);
struct tm *get_time ();

main (int argc, char *argv[]) {
    struct tm *t;  /* Time Pointer */
    char c,
	empty_str[] = "",
	*header = empty_str,
	*hd[3] = {empty_str, empty_str, empty_str},
	*footer = "@C- #d -",
	*ft[3] = {empty_str, empty_str, empty_str},
	*lnfmt = empty_str; /* printf style format string for line numbering */
    size_t i,	      /* Loop Counter */
	j,	   /* General Purpose Variable */
	top = 4,   /* These 4 values represent 1 inch margins for an */
	bot = 57,  /* 8½ x 11 inch sheet of paper where each square */
	lmar = 8,  /* inch is 6 lines by 10 characters, which will be */
	rmar = 65, /* printed on a printer which puts a ½ inch margin */
		   /* at the top and a ¼ inch margin on the side. */
	bottom,    /* Variable For Bottom Value Of Current Page */
	page1 = 1, /* Page Number For First Page */
	page,	   /* Variable That Keeps Track Of Page Number */
	linenum,   /* Line Number for Counting Lines on Page */
	line = 1,  /* Line Number for Counting Lines in Document */
	linelen,   /* Line Length For Headers & Footers */
	tpage = 0, /* Flag that indicates whether there is a title page */
	newline = 1, /* Flag which indicates start of a new line */
	done = 0;  /* Flag which indicates when to leave a pair of loops */

    /* Parse Command Line For Argument Values */

    for (i = 1; i < argc; i++)
	if (argv[i][0] == '-')
	    switch (argv[i][1]) {
		case 'H':                        /* Default Header */
		    header = argv[++i];
		    break;
		case 'h':
		    if (argv[i][2] == '0')       /* Even Header */
			hd[0] = argv[++i];
		    else if (argv[i][2] == '1')  /* Odd Header */
			hd[1] = argv[++i];
		    else if (argv[i][2] == 'f')  /* Header For 1st Page */
			hd[2] = argv[++i];
		    break;
		case 'F':                        /* Default Footer */
		    footer = argv[++i];
		    break;
		case 'f':
		    if (argv[i][2] == '0')       /* Even Footer */
			ft[0] = argv[++i];
		    else if (argv[i][2] == '1')  /* Odd Footer */
			ft[1] = argv[++i];
		    else if (argv[i][2] == 'f')  /* Footer For 1st Page */
			ft[2] = argv[++i];
		    break;
		case 'N':
		    lnfmt = argv[++i];
		    break;
		case 'r':                        /* Right Margin */
		    rmar = atoi(argv[++i]);
		    rmar = (rmar >= 1) ? rmar : 1;
		    break;
		case 'l':                        /* Left Margin */
		    lmar = atoi(argv[++i]);
		    lmar = (lmar >= 1) ? lmar : 1;
		    break;
		case 't':                        /* Top Line */
		    top = atoi(argv[++i]);
		    top = (top >= 1) ? top : 1;
		    break;
		case 'b':                        /* Bottom Line */
		    bot = atoi(argv[++i]);
		    break;
		case 'p': /* Page Number */
		    page1 = atoi(argv[++i]);
		    break;
		case 'T':                        /* Title Page */
		    tpage = 1;
		    break;
		default:
		    print_usage ();
		    break;
	    }
	    else
		print_usage ();

    if (bot <= top) {
	perror ("The bottom value should be greater than the top value.\n");
	exit (0);
    }

    /* Set Values for Footer and Header Strings */

    t = get_time();
    footer = timecnv(footer, t, rmar);
    header = timecnv(header, t, rmar);

    if (hd[0] == empty_str)
	hd[0] = header;  /* Header For Even Pages */
    else
	hd[0] = timecnv(hd[0], t, rmar);

    if (hd[1] == empty_str)
	hd[1] = header;  /* Header For Odd Pages */
    else
	hd[1] = timecnv(hd[1], t, rmar);

    if (hd[2] == empty_str)
	hd[2] = header;  /* Header For The First Page */
    else
	hd[2] = timecnv(hd[2], t, rmar);

    if (ft[0] == empty_str)
	ft[0] = footer;  /* Footer For Even Pages */
    else
	ft[0] = timecnv(ft[0], t, rmar);

    if (ft[1] == empty_str)
	ft[1] = footer;  /* Footer For Odd Pages */
    else
	ft[1] = timecnv(ft[1], t, rmar);

    if (ft[2] == empty_str)
	ft[2] = footer;  /* Footer For The First Page */
    else
	ft[2] = timecnv(ft[2], t, rmar);


    page = page1;

    /* Paginate Standard Input Into Standard Output */

    while (!done) {
	if (tpage) {
	    footer = header = empty_str; /* Turns header and footer off. */
	    tpage = 0; /* No more than one title page. */
	    page--; /* Assures that page will equal page1 next time round */
	}
	else {
	    j = (page == page1) ? 2 : (page & 1);
	    header = hd[j];
	    footer = ft[j];
	}
	bottom = (*footer == 0) ? bot : bot - 2;

	for (linenum = 1; linenum < top; linenum++)
		putchar ('\n');

	if (*header != 0) {
	    lineprint (header, page, lmar, rmar);
	    putchar ('\n');
	    linenum += 2;
	}

	while (linenum <= bottom && !done) {
	    if ((c = getc(stdin)) != EOF) {
		if (newline) {
		    if (lnfmt != empty_str)
			printf (lnfmt, line++);
		    newline = 0;
		}
		if (c == 12)
		    break;
		putchar (c);
		if (c == '\n') {
		    linenum++;
		    newline = 1;
		}
	    }
	    else
		done = 1;
	}
	if (*footer != 0) {
	    for (; linenum <= bottom; linenum++)
		putchar ('\n');
	    putchar ('\n');
	    lineprint (footer, page, lmar, rmar);
	}
	putchar (12);
	page++;
    }
}

void lineprint (char *line, size_t page, size_t lmar, size_t len) {
    char *str1, *str2;
    size_t i;

    str1 = convert(line, page, len);
    str2 = justify(str1, len);
    for (i = 1; i < lmar; i++)
	putchar (' ');
    puts (str2);
    free (str1);
    free (str2);
}

void print_usage () {
    fprintf (stderr,
	"[33mPaginate V1.00[31m Copyright © 1993 Fergus Duniho\n\n"
	"Usage: Paginate <infile >outfile -F/K,-f0/K,-f1/K,-ff/K,-H/K,-h0/K\n"
	"       -h1/K,-hf/K,-N/K,-t/K/N,-b/K/N,-l/K/N,-r/K/N,-p/K/N,-T/S\n"
	" [32m-H -h0 -h1 -hf[31m Default, Even, Odd, and First Page Headers\n"
	" [32m-F -f0 -f1 -ff[31m Default, Even, Odd, and First Page Footers\n"
	" [32m-N[31m Line Number String [32m-t[31m Top Line [32m-b[31m Bottom Line [32m-p[31m First Page #\n"
	" [32m-l[31m Left Margin [32m-r[31m Relative Right Margin [32m-T[31m Title page\n"
	"\nDefault: Paginate -F \"@C- #d -\" -H \"\" -N \"\" -l 8 -r 65 -t 4 -b 57 -p 1\n");
    exit (0);
}

char *convert (char *str, size_t page, size_t len) {
    char *rstr = (char *)malloc(len + 1);
    char *r = rstr;
    while (*str != 0) {
	if (*str != '#')
	    *rstr++ = *str;
	else
	    switch (*(++str)) {
		case '#':
		    *rstr++ = '#';
		    break;
		case 'r':
		    rstr = addstr(rstr, roman(page, 0));
		    break;
		case 'R':
		    rstr = addstr(rstr, roman(page, 1));
		    break;
		case 'd':
		    rstr = addstr(rstr, itoa(page, 10));
		    break;
		case 'o':
		    rstr = addstr(rstr, itoa(page, 8));
		    break;
		case 'b':
		    rstr = addstr(rstr, itoa(page, 2));
		    break;
		case 'x':
		    rstr = addstr(rstr, itoa(page, 16));
		    break;
	    }
	str++;
    }
    *rstr = 0;
    return r;
}



char *addstr (char *rstr, char *str) {
    while ((*rstr++ = *str++) != 0);
    return --rstr;
}

char *justify (char *str, size_t length) {
    size_t pos = 0, len;
    char *line = (char *)malloc(length + 1);
    char *ch = line;
    size_t i;

    for (i = 0; i < length; i++)
	*ch++ = ' ';
    *ch = 0;

    while (*str != 0) {
	if (*str == '@') {
	    if (*(++str) == 'R')
		pos = length - seglen(++str);
	    else if (*str == 'C')
		pos = (length - seglen(++str)) / 2;
	    else if (*str == '-') {
		if (isxdigit(*(++str))) {
		    if (isdigit(*str))
			pos -= (*str - '0');
		    else if (islower(*str))
			pos -= ((*str - 'a') + 10);
		}
		str++;
	    }
	    else {
		if (isxdigit(*str)) {
		    if (isdigit(*str))
			pos += (*str - '0');
		    else if (islower(*str))
			pos += ((*str - 'a') + 10);
		}
		else if (*str == 'L')
		    pos = 0;
		str++;
	    }
	}
	else {
	    len = seglen(str);
	    overwrite(line, str, pos, length, len);
	    str += len;
	}
    }
    return line;
}

void overwrite (char *dest, char *str, size_t pos, size_t dlen, size_t slen) {
    if (pos > dlen)
	return;
    dest += pos;
    while (slen-- && *dest && *str)
	*dest++ = *str++;
}

size_t seglen(char *s) {
    size_t len = 0;

    while (1) {
	if (*s == '@')
	    if (*(++s) == '@')
		len++;
	    else
		return len;
	else if (*s == 0)
	    return len;
	else
	    len++;
	s++;
    }
}

char *timecnv (char *fmt, struct tm *tptr, size_t len) {
    char *s;

    if (*fmt == 0)
	return fmt;
    if ((s = (char *)malloc(len + 1)) == 0) {
	perror ("Out of memory.");
	exit (2);
    }
    if (strftime(s, len, fmt, tptr) == 0) {
	fprintf (stderr, "The expansion of %s is too long.\n", fmt);
	exit (2);
    }
    return s;
}

struct tm *get_time () {
    time_t *tod;

    time (tod);
    return localtime(tod);
}
