/*
 *	Calendar program - one page per month (writes to standard output)
 *
 *	  calen [month] year [len] [-o]
 *				   
 *	If one of the numeric arguments {month, year, len} is specified, it
 *	is assumed to be the year; calendar generated is for entire year.
 *	(Note: Unlike the UN*X 'cal', nn is treated as 19nn.)
 *   
 *	If two numeric arguments are specified, first is assumed to be the
 *	month (1-12) and second the year; calendar generated is for that
 *	month/year only.	   
 *
 *	If all are specified, calendar generated is for 'len' consecutive
 *	months starting at month/year.
 *
 *      If last argument is '-o' flag, output will contain backspaces and
 *	overstrikes to emphasize month and year. (Not all printers can
 *	handle this; check local conventions before printing.  If using
 *	'lp' on PWB, use its '-o' flag to convert the backspaces to
 *	overstruck lines.)
 *
 *	Author: AW Rogers
 *
 *	Originally written in Fortran-IV on GE Timesharing, 10/65
 *	Converted to C/UN*X, 3/83
 */

/*
 *	Constant definitions:
 */
#define	MIN 1753	/* Valid years (1753: start of Gregorian calendar) */
#define	MAX 9999

#define JAN 1		/* Significant months and weekdays */
#define DEC 12
#define SUN 0
#define SAT 6

#define SINGLE "*"	/* Normal and overstrike (XIH*) character sequences */
#define OVERSTRIKE "X\bI\bH\b*"

/*
 *	Macro definitions:
 */
#define day_of_week(I) ((I) % 7)

/*
 *	Global declarations:
 */
char output_seq[8]; 	/* either SINGLE or OVERSTRIKE; used in PrintChar */


/*
 *	Main(argc, argv)
 *
 *	Gets and checks command line arguments; prints desired calendar(s)
 */
main(argc, argv)
    int argc;
    char *argv[];
{
    int mon, year, len;		/* Command line arguments */

   /*
    *   Get and verify command line arguments (see above)
    */

    /* Check for -o as last command line argument (ignore erroneous flags) */
    if (argv[argc-1][0] == '-' && argv[--argc][1] == 'o')
	strcpy(output_seq, OVERSTRIKE);
    else
	strcpy(output_seq, SINGLE);

    /* Supply defaults for numeric arguments as described above */
    switch (argc) {
    case 2:		/* 1 argument supplied (year) */
	sscanf(argv[1], "%d", &year);
	mon = JAN;
	len = 12;
	break;
    case 3:		/* 2-3 arguments supplied (mon year [len]) */
    case 4:
	sscanf(argv[1], "%d", &mon);
	sscanf(argv[2], "%d", &year);
	if (argc == 3)
	    len = 1;
	else
	    sscanf(argv[3], "%d", &len);
	break;
    default:
	printf("usage: %s [month] year [len] [-o]\n", argv[0]);
	return;
    }	/* end switch */

    /* Validate month and year; quit if invalid */
    if (year > 0 && year < 100)		/* Convert nn to 19nn first */
	year += 1900;
    if (year < MIN || year > MAX || mon < JAN || mon > DEC) {
	printf("valid months: %d..%d; valid years: %d..%d\n", JAN, DEC,
	    MIN, MAX);
	return;
    }

   /*
    *   Main loop: prints calendar for each requested month
    */

    while (len-- > 0) {
	PrintCal(mon, year);
       /* Prepare for next month's calendar */
	if (++mon > DEC) {			/* Bump month (and year) */
	    mon = JAN;
	    if (++year > MAX) 			/* Quit if year too big */
		return;
	}
    }	/* end calendar generation loop */

}   /* end main */


/*
 *	PrintCal(mon, year)
 *
 *	Prints calendar for specified month and year (assumed valid)
 */

PrintCal(mon, year)
    int mon, year;
{
    int i, j, k, 		/* General-purpose loop indices */
	start, end,		/* Range of calendar boxes containing dates */
	date = 0,		/* Date to print inside box */
	chars;			/* Length of current month name */

    static char blanks[36] = "                                   ";
				/* String of blanks for centering heading */

    char digits[5],		/* Individual digits of year (for printing) */
	 *padding;		/* Pointer to effective start of 'blanks' */

/*
 *   Names and lengths of months (first element is dummy):
 */
    static struct {
	char *name;		/* name of month */
	char len[2];		/* number of days (non-leap, leap) */
	char first[2];		/* first of month wrt 1/1 (same) */
    } month[13] = { {"", {0,0}, {0,0}},
	{"JANUARY",   {31,31}, {0,0}}, {"FEBRUARY",  {28,29}, {3,3}},
	{"MARCH",     {31,31}, {3,4}}, {"APRIL",     {30,30}, {6,0}},
	{"MAY",       {31,31}, {1,2}}, {"JUNE" ,     {30,30}, {4,5}},
	{"JULY",      {31,31}, {6,0}}, {"AUGUST",    {31,31}, {2,3}},
	{"SEPTEMBER", {30,30}, {5,6}}, {"OCTOBER",   {31,31}, {0,1}},
	{"NOVEMBER",  {30,30}, {3,4}}, {"DECEMBER",  {31,31}, {5,6}}};

/*
 *   Names of weekdays, centered (roughly) in 9-character field:
 */
    static char weekday[7][10] = {" SUNDAY  ", "MONDAY  ", " TUESDAY ",
	"WEDNESDAY", "THURSDAY ", " FRIDAY  ", "SATURDAY "};

/*
 *   5 x 7 dot-matrix representations of letters A-Z and digits 0-9.
 *   Printed as large characters (using ' ' and '*'); see PrintChar.
 */
    static char large_letters[26][7] = {
	14,17,17,31,17,17,17, 30,17,17,30,17,17,30, 14,17,16,16,16,17,14,
	30,17,17,17,17,17,30, 31,16,16,30,16,16,31, 31,16,16,30,16,16,16,
	14,17,16,23,17,17,14, 17,17,17,31,17,17,17, 31,4,4,4,4,4,31,
	1,1,1,1,1,17,14,      17,18,20,24,20,18,17, 16,16,16,16,16,16,31,
	17,27,21,21,17,17,17, 17,17,25,21,19,17,17, 14,17,17,17,17,17,14,
	30,17,17,30,16,16,16, 14,17,17,17,21,18,13, 30,17,17,30,20,18,17,
	14,17,16,14,1,17,14,  31,4,4,4,4,4,4,       17,17,17,17,17,17,14,
	17,17,17,17,17,10,4,  17,17,17,17,21,21,10, 17,17,10,4,10,17,17,
	17,17,17,14,4,4,4,    31,1,2,4,8,16,31};
    static char large_digits[10][7] = {
	14,17,17,17,17,17,14, 2,6,10,2,2,2,31,      14,17,2,4,8,16,31,
	14,17,1,14,1,17,14,   2,6,10,31,2,2,2,      31,16,16,30,1,17,14,
	14,17,16,30,17,17,14, 31,1,2,4,8,16,16,     14,17,17,14,17,17,14,
	14,17,17,15,1,17,14};

   /*
    *   Initialize starting weekday of first month (first box to contain a
    *   date) and digits in year (for printing in 5x7 format):
    */

    start = year + (year - 1)/4 - (year - 1)/100 + (year - 1)/400;
    start = day_of_week(start + month[mon].first[IsLeap(year)]);
    sprintf(digits, "%4d", year);

   /*
    * Print heading with month and year in large letters/digits 
    */

    printf("\f\n\n\n");
    SolidLine(2);
    BorderLine(2);

    /* Create a string of blanks for centering month/year */
    chars = strlen(month[mon].name);
    padding = &blanks[3 * (chars - 3)];

    /* Print 7 lines containing the month and year in large characters */
    for (j = 0; j <= 6; ++j) {
	printf(" **%s", padding);
	for (k = 0; k <= chars - 1; ++k)	/* Letters in month name */
	    PrintChar(large_letters[month[mon].name[k] - 'A'][j]);
	printf("%12s", " ");
	for (k = 0; k <= 3; ++k)		/* Digits in year */
	    PrintChar(large_digits[digits[k] - '0'][j]);
	printf(" %s**\n", padding);
    }

    /* Print names of weekdays above calendar boxes */
    BorderLine(2);
    SolidLine(2);
    BoxLine(1);
    printf(" **");
    for (j = SUN; j <= SAT; ++j)
	printf("%13s%5s", weekday[j], "*");
    printf("*\n");

   /* 
    *   Print body of calendar
    */
    end = start + month[mon].len[IsLeap(year)] - 1;	/* Last date to print */

    /* Print first 5 weeks of calendar */
    for (j = 0; j <= 34; ++j) {
	if (day_of_week(j) == SUN) {	/* Top and left border */
	    BoxLine(1);
	    SolidLine(1);
	    printf(" **");
	}
	if (j >= start && j <= end)		/* Date or blank */
	    printf("%3d%15s", ++date, "*");
	else
	    printf("%18s", "*");
	if (day_of_week(j) == SAT) {	/* Right border and bottom */
	    printf("*\n");
	    BoxLine(5);
	}
    }

    /* Print last line (with 30/31 if needed) and bottom border */
    printf(" **");
    for (j = 35; j <= 41; ++j)
	if (j <= end)		/* Print date or blank */
	    printf("%16d%2s", ++date, "*");
	else
	    printf("%18s", "*");
    printf("*\n");
    SolidLine(2);
}   /* end PrintCal */


/*
 *	IsLeap(y)
 *
 *	Return 1 if year y is leap; 0 if not
 */
int IsLeap(y)
    int y;
{
    return y % 4 == 0 && y % 100 != 0 || y % 400 == 0;
}   /* end IsLeap */


/*
 *	PrintChar(i)
 *
 *	Prints least significant 6 bits of i in binary representation,
 *	using ' ' for 0 and '*' for 1.
 */
PrintChar(i)
    char i;
{
    int msk = 32;   	/* Output field width = log2(msk)+1 = 6 */

    for (; msk > 0; msk /= 2)
	if (i & msk)
	    printf(output_seq);	/* Globally defined */
	else
	    printf(" ");

}   /* end PrintChar */


/*
 *	BorderLine(n)
 *
 *	Print n border lines (** 125 spaces **)
 */
BorderLine(n)
    int n;
{
    static char c[] = "**";

    while (n--)
	printf(" %2s%127s\n", c, c);

}   /* end BorderLine */


/*
 *	BoxLine(n)
 *
 *	Print n box lines (** 17 spaces * 17 spaces .. **)
 */
BoxLine(n)
    int n;
{
    static char c[] = "*";

    while (n--)
	printf(" **%18s%18s%18s%18s%18s%18s%18s*\n", c, c, c, c, c, c, c);

}   /* end BoxLine */


/*
 *	SolidLine(n)
 *
 *	Print n solid lines (129 *)
 */
SolidLine(n)
    int n;
{
    static char c[] = "*******************************************";

    while (n--)
	printf(" %43s%43s%43s\n", c, c, c);

}   /* end SolidLine */
