/* Copyright (c) 1993 by Sanjay Ghemawat */
/*
 * Printing commands.
 */

#include <ctype.h>
#include <stdio.h>
#include <string.h>
extern "C" {
#include <tcl.h>
}

#include "arrays.h"
#include "collect.h"
#include "commands.h"
#include "item_tcl.h"
#include "misc_tcl.h"

#include "item.h"

#include "Date.h"
#include "Month.h"
#include "Time.h"
#include "WeekDay.h"
#include "Year.h"

static inline void append(charArray& a, char const* str) {
    a.concat(str, strlen(str));
}

/*
 * Helper postscript generators.
 */
static void ps_printtime(charArray&);
static void ps_printtimeofday(charArray&, int time);
static void ps_printday(charArray&, Date, Occurrences const&);

/*
 * Generate postscript for month containing specified date.
 *
 *	psmonth <date>
 *
 * The postscript is returned and should be printed with the contents
 * of header.ps prefixed.
 */

int Cmd_PsMonth(ClientData, Tcl_Interp* tcl, int argc, char* argv[]) {
    if (argc != 2) {
	TCL_Error(tcl, "illegal number of arguments to psmonth");
    }

    // Get first of the month
    int dateDays;
    if (Tcl_GetInt(tcl, argv[1], &dateDays) != TCL_OK) {
	TCL_Error(tcl, "illegal date");
    }
    Date date(dateDays);

    int   y = date.GetYear();
    Month m = date.GetMonth();

    Date start = Date(1, m, y);
    Date finish = Date(m.Size(y), m, y);

    // Generate output
    char buffer[100];
    charArray output;

    append(output, "                SetLandScape\n");
    append(output, "7               SetGridWidth\n");
    append(output, "5               SetGridHeight\n");
    append(output, "0.5 inch        SetBorderWidth\n");
    append(output, "0.3 inch        SetBorderHeight\n");
    append(output, "0.5 inch        SetHeaderHeight\n");
    append(output, "0.1 inch        SetHeaderSep\n");
    append(output, "10              SetMinLines\n");
    append(output, "15              SetMaxLines\n");
    append(output, "0.3 inch        SetColumnHeight\n");
    append(output, "0.1 inch        SetColumnSep\n");
    append(output, "0.25            SetTitleFraction\n");

    sprintf(buffer, "(%s, %d) SetHeaderLeft\n", m.Name(), y);
    append(output, buffer);

    ps_printtime(output);

    append(output, "ComputeLayout\n");
    append(output, "0	()	(Sun)	()	ColumnHead\n");
    append(output, "1	()	(Mon)	()	ColumnHead\n");
    append(output, "2	()	(Tue)	()	ColumnHead\n");
    append(output, "3	()	(Wed)	()	ColumnHead\n");
    append(output, "4	()	(Thu)	()	ColumnHead\n");
    append(output, "5	()	(Fri)	()	ColumnHead\n");
    append(output, "6	()	(Sat)	()	ColumnHead\n");

    int col = start.GetWDay().Index() - 1;
    int row = 0;

    int num = 1;

    Occurrences list;
    collect_occurrences(list, start, finish, 0);

    Date xd = start;
    while (xd <= finish) {
	sprintf(buffer, "%d %d () (%2d)\n", row, col, num);
	append(output, buffer);

	ps_printday(output, xd, list);
	append(output, "ShowDay\n");

	xd += 1;
	num++;
	col++;
	if (col == 7) {
	    col = 0;
	    row++;
	    if (row == 5) {
		row = 0;
	    }
	}
    }

    append(output, "\nshowpage\n");
    output.append('\0');

    Tcl_SetResult(tcl, output.as_pointer(), TCL_VOLATILE);
    return TCL_OK;
}

/*
 * Generate postscript for specified number of <days> starting at
 * a given <date> and with the specified number of <columns>.
 * The <landscape> option is a boolean that is true iff the output
 * should be in landscape mode.
 *
 *	psdays <date> <days> <columns> <landscape>
 *
 * The postscript is returned and should be printed with the contents
 * of header.ps prefixed.
 */
int Cmd_PsDays(ClientData, Tcl_Interp* tcl, int argc, char* argv[]) {
    // Parse arguments

    if (argc != 5) {
	TCL_Error(tcl, "illegal number of arguments to psdays");
    }

    int dateDays;
    if (Tcl_GetInt(tcl, argv[1], &dateDays) != TCL_OK) {
	TCL_Error(tcl, "illegal date");
    }
    Date start(dateDays);

    int num;
    if ((Tcl_GetInt(tcl, argv[2], &num) != TCL_OK) || (num < 1)) {
	TCL_Error(tcl, "illegal number of days");
    }
    Date finish = start + (num - 1);

    int cols;
    if ((Tcl_GetInt(tcl, argv[3], &cols) != TCL_OK) || (cols < 1)) {
	TCL_Error(tcl, "illegal number of columns");
    }

    int landscape;
    if (Tcl_GetBoolean(tcl, argv[4], &landscape) != TCL_OK) {
	TCL_Error(tcl, "illegal landscape/portrait specification");
    }

    // Generate output
    char buffer[100];
    charArray output;

    int year1;
    int year2;
    year1 = start.GetYear();
    year2 = finish.GetYear();

    int rows = (num + cols - 1) / cols;

    if (landscape) {
	append(output, "SetLandScape\n");
    }
    else {
	append(output, "SetPortrait\n");
    }

    sprintf(buffer, "%d SetGridWidth\n", cols);
    append(output, buffer);

    sprintf(buffer, "%d SetGridHeight\n", rows);
    append(output, buffer);

    append(output, "0.5 inch        SetBorderWidth\n");
    append(output, "0.3 inch        SetBorderHeight\n");
    append(output, "0.5 inch        SetHeaderHeight\n");
    append(output, "0.1 inch        SetHeaderSep\n");
    append(output, "18              SetMinLines\n");
    append(output, "18              SetMaxLines\n");
    append(output, "0.1             SetTitleFraction\n");

    if (year1 != year2) {
	sprintf(buffer, "(%d - %d) SetHeaderLeft\n", year1, year2);
    }
    else {
	sprintf(buffer, "(%d) SetHeaderLeft\n", year1);
    }
    append(output, buffer);

    ps_printtime(output);

    append(output, "ComputeLayout\n");

    int col = 0;
    int row = 0;

    Occurrences list;
    collect_occurrences(list, start, finish, 0);

    Date xd = start;
    while (xd <= finish) {
	int day, year;
	Month month;
	WeekDay wday;
	xd.BreakDown(day, wday, month, year);

	sprintf(buffer, "%d %d (%s) (%s %d)\n",
		row, col,
		wday.ShortName(),
		month.ShortName(),
		day);
	append(output, buffer);
	ps_printday(output, xd, list);
	append(output, "ShowDay\n");

	xd += 1;
	col++;
	if (col == cols) {
	    col = 0;
	    row++;
	}
    }

    append(output, "\nshowpage\n");
    output.append('\0');

    Tcl_SetResult(tcl, output.as_pointer(), TCL_VOLATILE);
    return TCL_OK;
}

static void ps_printtime(charArray& output) {
    Time now(Time::Now());
    Date today(now);

    int hour, min;
    now.BreakDownClock(hour, min);
    min += hour * 60;

    int mday;
    WeekDay wday;
    Month month;
    int year;
    today.BreakDown(mday, wday, month, year);

    char buffer[80];

    sprintf(buffer, "%d/%d/%d, ", month.Index(), mday, year);

    /*
     * Fake entries to put printing time on top right.
     * Save old values on PS stack, and restore after
     * printing the time.
     */

    /* Save old parameters */
    append(output, "BorderHeight\n");
    append(output, "HeaderHeight\n");
    append(output, "HeaderLeft\n");
    append(output, "HeaderRight\n");

    /* Print heading */
    append(output, "HeaderHeight 2 div	 SetHeaderHeight\n");
    append(output, "()			 SetHeaderLeft\n");

    append(output, "(Printed ");
    append(output, buffer);
    ps_printtimeofday(output, min);
    append(output, ") SetHeaderRight\n");

    append(output, "gsave ComputeLayout grestore\n");

    /* Restore saved parameters */
    append(output, "SetHeaderRight\n");
    append(output, "SetHeaderLeft\n");
    append(output, "SetHeaderHeight\n");
    append(output, "SetBorderHeight\n");
}

static void ps_printtimeofday(charArray& output, int time) {
    int hour;
    int minute;
    char meridian;

    hour = time / 60;
    if (hour >= 12) {
	meridian = 'p';
	hour -= 12;
    }
    else {
	meridian = 'a';
    }
    if (hour == 0) hour = 12;

    minute = time % 60;

    char buffer[20];
    sprintf(buffer, "%d:%02d%cm", hour, minute, meridian);
    append(output, buffer);
}

static void ps_printday(charArray& output,Date date,Occurrences const& list){
    output.append('[');

    for (int i = 0; i < list.size(); i++) {
	if (list[i].date != date) {
	    continue;
	}

	Item* item = list[i].item->value();

	/* Print this reminder */
	output.append('[');

	/* Print appt header */
	Appointment* appt = item->AsAppointment();
	if (appt != 0) {
	    output.append('(');
	    ps_printtimeofday(output, appt->GetStart());
	    append(output, " to ");
	    ps_printtimeofday(output, appt->GetFinish());
	    output.append(')');
	}

	/* Print all the lines */

	char const* text = item->GetText();
	int len = strlen(text);

	/* Strip trailing whitespace */
	while (len > 0) {
	    char c = text[len - 1];

	    if (isascii(c) && isspace(c)) {
		len--;
		continue;
	    }
	    else {
		break;
	    }
	}

	int bol = 1;		/* At beginning of line? */
	for (int j = 0; j < len; j++) {
	    if (bol) {
		output.append('(');
		bol = 0;
	    }
	    char c = text[j];
	    switch (c) {
	      case '(':
	      case ')':
	      case '\\':
		/* Quote with a backslash */
		output.append('\\');
		output.append(c);
		break;
	      case '\n':
		append(output, ")\n");
		bol = 1;
		break;
	      default:
		output.append(c);
		break;
	    }
	}
	if (! bol) {
	    /* Finish the line */
	    append(output, ")\n");
	}

	/* Finish reminder */
	append(output, "]\n");
    }

    append(output, "]\n");
}
