// calMain.js: JavaScript for main calendar program

// Last update: January 24, 2008

/*
 * This file contains functions that are used to render a calendar.  To display
 * a calendar, create an HTML file like this:
 *	<head>
 *	  <link href="calendar.css" rel="stylesheet" type="text/css">
 *	  <script src="calMain.js"></script>
 *	  <script src="calEvents.js"></script>
 *	</head>
 *	<body>
 *	  <script>calShow();</script>
 *	</body>
 *
 * Note that this file, calMain.js, initializes the software.  On the
 * other hand calEvents.js defines the event data.
 *
 * To avoid confusion, this program uses the term "date" to mean a specific
 * day of the month, given by an integer between 1 and 31; and it uses the
 * term "fullDate" to refer to an entire month/date/year specification.
 *
 */


/******************************************************************************/
/* Global variables                                                           */
/******************************************************************************/

var calBrowser = new CalBrowser();
var calDayNames = new Array("Sunday", "Monday", "Tuesday", "Wednesday",
	"Thursday", "Friday", "Saturday");
var calDaysPerMonth = new Array(31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31); 
var calDetailsWindow = null;		// Where details will be display
var calErrorCount = 0;			// Number of syntax errors in file
var calErrorMessage = null;		// Description of first syntax error
var calEventList = new Array();		// List of calendar events
var calMonthNames = new Array("January", "February", "March", "April", "May",
	"June", "July", "August", "September", "October", "November",
	"December");
var calMonth;				// The month to display
var calOldHeight;			// Remember window size for NN big fix
var calOldWidth;			// Remember window size for NN big fix
var calYear;				// The year to display

var CAL_REPEAT_YEARLY = 1;
var CAL_REPEAT_MONTHLY = 2;
var CAL_REPEAT_WEEKLY = 3;
var CAL_REPEAT_DAILY = 4;
var CAL_REPEAT_ONCE = 5;


/******************************************************************************/
/* Constructors                                                               */
/******************************************************************************/


//------------------------------------------------------------------------------
// Class CalBrowser: specs for the user's browser

function CalBrowser() {


	// See www.mozilla.org/docs/web-developer/sniffer/browser_type.html

	var agt = navigator.userAgent.toLowerCase();

	this.isAOL = (agt.indexOf("aol") != -1);
	this.isIE = ((agt.indexOf("msie") != -1)
		&& (agt.indexOf("opera") == -1));
	this.isNN = ((agt.indexOf("mozilla")!=-1)
		&& (agt.indexOf("spoofer")==-1)
		&& (agt.indexOf("compatible") == -1)
		&& (agt.indexOf("opera")==-1)
		&& (agt.indexOf("webtv")==-1)
		&& (agt.indexOf("hotjava")==-1));
	this.isOpera = (agt.indexOf("opera") != -1);

	this.isMac = (agt.indexOf("mac")!=-1);


	// Warning: this is _NOT_ always the "advertised" version number!!!

	this.version = parseInt(navigator.appVersion);
}


//------------------------------------------------------------------------------
// Class CalEvent: defines specs for one entry in the event list

function CalEvent(startFullDate, repeat, endFullDate, startTime, endTime,
	name, description) {


	var when;	// temporary variable for computing times


	// Assume data won't be valid

	this.valid = false;


	/*
	 * Memorize start date, whether event repeats, and if so, when it ends.
	 */

	this.startFullDate = new CalFullDate(startFullDate);
	if (!this.startFullDate.valid) {
		if (calErrorMessage == null)
			calErrorMessage = "Invalid start date: "
				+ startFullDate;
		return;
	}

	if (repeat == "once")
		this.repeat = CAL_REPEAT_ONCE;
	else if (repeat == "daily")
		this.repeat = CAL_REPEAT_DAILY;
	else if (repeat == "weekly")
		this.repeat = CAL_REPEAT_WEEKLY;
	else if (repeat == "monthly")
		this.repeat = CAL_REPEAT_MONTHLY;
	else if (repeat == "yearly")
		this.repeat = CAL_REPEAT_YEARLY;
	else {
		if (calErrorMessage == null)
			calErrorMessage = "Invalid repeat: " + repeat;
		return;
	}

	if (endFullDate == "none") {
		this.endFullDate = null;
	}
	else {
		this.endFullDate = new CalFullDate(endFullDate);
		if (!this.endFullDate.valid) {
			if (calErrorMessage == null)
				calErrorMessage = "Invalid end date: "
					+ endFullDate;
			return;
		}
	}


	// Memorize start and end times

	this.startTime = new CalTime(startTime);
	if (!this.startTime.valid) {
		if (calErrorMessage == null)
			calErrorMessage = "Invalid start time: " + startTime;
		return;
	}


	if (endTime == "none")
		this.endTime = null;
	else {
		this.endTime = new CalTime(endTime);
		if (!this.endTime.valid) {
			if (calErrorMessage == null)
				calErrorMessage = "Invalid end time: "
					+ endTime;
			return;
		}
	}


	// Memorize other info about the event

	this.name		= name;
	this.description	= description;

	this.valid = true;
}


//------------------------------------------------------------------------------
// Class CalFullDate: defines the internal representation of a full date

function CalFullDate(fullDate) {


	// Assume date won't be valid

	this.valid = false;


	/*
	 * Class Date can't handle dates like mm/dd/02 or mm/dd, so
	 * have to do some syntax checking for ourselves.
	 */

	var fields = fullDate.split("/");
	if (fields.length != 2 && fields.length != 3)
		return;
	var month = Number(fields[0]) - 1;
	var date = Number(fields[1]);


	// If year is missing, use this year.  If year is 02, use 2002.

	var year = Number((new Date()).getFullYear());
	if (fields.length == 3) {
		year = Number(fields[2]);
		if (year < 50)
			year += 2000;
		else if (year < 100)
			year += 1900;
		// else, leave year unchanged
	}
	if (isNaN(month) || isNaN(date) || isNaN(year))
		return;


	// Use Date constructor to find actual date info

	var when = new Date(year, month, date, 0, 0, 0, 0);

	this.day = when.getDay();
	this.date = when.getDate();
	this.month = when.getMonth();
	this.year = when.getFullYear();

	this.valid = true;
}


//------------------------------------------------------------------------------
// Class CalMonthSpec: specs for this month (event list, what month it is, etc.)

function CalMonthSpec(month, year) {
	var day;
	var evt;
	var fullDate = new CalFullDate("1/1/2002");	// Gets changed below
	var i;
	var size;
	var week;


	/*
	 * Create a 3D array to hold event info.  The array always has six weeks
	 * of data (regardless of the month), seven days per week, and an
	 * unlimited  number of events per day.
	 */

	var eventList = new Array(6);
	for (week = 0; week < 6; week++) {
		eventList[week] = new Array(7);
		for (day = 0; day < 7; day++) {
			eventList[week][day] = new Array();  // events go here
		}
	}


	// Find date of the first day that will appear on the calendar

	var firstFullDate = new Date(year, month, 1, 0, 0, 0, 0);
	var dayOfTheFirst = firstFullDate.getDay();
	firstFullDate.setMilliseconds(firstFullDate.getMilliseconds()
		- 86400000*dayOfTheFirst)

	var firstDate = firstFullDate.getDate();
	var firstMonth = firstFullDate.getMonth();
	var firstYear = firstFullDate.getFullYear();


	/*
	 * Scan the event list to find events that fall in this month.  For
	 * each one found, append the event info to the eventList array.
	 */

	for (i = 0; i < calEventList.length; i++) {
		evt = calEventList[i];

		// Define first day that appears on the calendar

		fullDate.day = 0;
		fullDate.date = firstDate;
		fullDate.month = firstMonth;
		fullDate.year = firstYear;

		for (week = 0; week < 6; week++) {
			for (day = 0; day < 7; day++) {
				if (evt.fallsOn(fullDate)) {
					size = eventList[week][day].length;
					eventList[week][day][size] = evt;
				}
				fullDate.increment();
			}
		}
	}
	this.eventList = eventList;


	// Memorize other information that defines this month

	fullDate.date = 1;
	fullDate.month = month;
	fullDate.year = year;
	this.lastDateOfMonth = fullDate.getLastDateOfMonth();

	this.month = Number(month);	// Use Number in case argument ...
	this.year = Number(year);	// ... is a string.
	this.dayOfTheFirst = dayOfTheFirst;
}


//------------------------------------------------------------------------------
// Class CalTime: defines a time of day when an event starts or stops

function CalTime(time) {

	/*
	 * This constructor checks that the input string looks like a time, but
	 * otherwise leaves the string unchanged.  That allows the caller to
	 * format the time in whatever way he prefers.  Examples of valid times
	 * are "8", "8:00", "8:00 AM" "16:00", and "4:00PM".  The seconds field
	 * cannot be present, so "8:00:25" is invalid.
	 *
	 * The routine also computes the time of the event in minutes since the
	 * start of the day.  The caller can use that information to sort
	 * events.
	 */


	var i;
	var hour;
	var minute;
	var qualifier;


	// Assume the time won't be valid

	this.valid = false;


	if (time == null)
		return;
	this.time = time;	// Note: we will modify time below

	time = time.toUpperCase();

	if ((i = time.indexOf("PM", 0)) > 0) {
		qualifier = "PM";
		time = time.substr(0, i);
	}
	else if ((i = time.indexOf("AM", 0)) > 0) {
		qualifier = "AM";
		time = time.substr(0, i);
	}
	else
		qualifier = "";

	var fields = time.split(":");
	if (fields.length > 2)
		return;
	hour = fields[0]
	minute = fields[1];
	if (typeof(minute) == "undefined")
		minute = 0;
	if (isNaN(hour) || isNaN(minute))
		return;
	hour = Number(hour);
	minute = Number(minute);


	// Don't let idiots request weird times

	if (minute < 0 || minute > 59)
		return;
	if (hour <= 0)
		return;
	if (qualifier != "" && hour > 12)
		return;
	if (qualifier == "" && ((hour*60 + minute) > 1440))
		return;		// Thus 24:00 is allowed


	// Adjust time for AM/PM, being sure to get 12:xx right

	if (qualifier == "AM") {
		if (hour == 12)
			hour = 0;
	}
	else if (qualifier == "PM" && hour != 12) {
			hour += 12;
	}


	// Express time as a string so it can be displayed neatly

	if (hour == 0)
		this.string = "" + 12 + ":";
	else if (hour <= 12)
		this.string = "" + hour + ":";
	else
		this.string = "" + (hour-12) + ":";

	if (minute < 10)
		this.string += "0" + minute;
	else
		this.string += minute;

	if (hour < 12)
		this.string += " am";
	else
		this.string += " pm";


	// Compute minutes since start of day for sorting purposes

	this.minutes = hour*60 + minute;

	this.valid = true;
}


/******************************************************************************/
/* Methods                                                                    */
/******************************************************************************/

//------------------------------------------------------------------------------
// Method CalEvent.fallsOn: determine if event falls on given date

CalEvent.prototype.fallsOn = function fallsOn(fullDate) {

	evt = this;		// The event to be evaluated


	// Short names to make it easier to write code

	// Start date:
	var m0 = evt.startFullDate.month;
	var d0 = evt.startFullDate.date;
	var y0 = evt.startFullDate.year;

	// The date being checked:
	var m = fullDate.month;
	var d = fullDate.date;
	var y = fullDate.year;

	// The end date:
	if (evt.endFullDate != null) {
		var m1 = evt.endFullDate.month;
		var d1 = evt.endFullDate.date;
		var y1 = evt.endFullDate.year;
	}
	else {
		// Null => no end date, so pick something in the future
		var m1 = m + 1;
		var d1 = d + 1;
		var y1 = y + 1;
	}


	if (evt.repeat == CAL_REPEAT_ONCE) {
		if (calCompareDates(m, d, y, m0, d0, y0) == 0)
			return true;
		else
			return false;
	}


	// Check date range

	if (calCompareDates(m, d, y, m0, d0, y0) < 0) {
		// today falls before start
		return false;
	}
	if (calCompareDates(m, d, y, m1, d1, y1) > 0) {
		// today falls after end
		return false;
	}


	// Check repeating events; daily first, then weekly; etc.

	if (evt.repeat == CAL_REPEAT_DAILY)
		return true;

	else if (evt.repeat == CAL_REPEAT_WEEKLY) {
		if (fullDate.day == evt.startFullDate.day)
			return true;
		else
			return false;
	}

	else if (evt.repeat == CAL_REPEAT_MONTHLY) {
		// Monthly events must appear exactly once in every month

		if (fullDate.date > evt.startFullDate.date)
			return false;
		else if (fullDate.date == evt.startFullDate.date)
			return true;
		else if (fullDate.isLastDateOfMonth())
			return true;	// put event on last day of month
		else
			return false;
	}

	else { // Assert evt.repeat == CAL_REPEAT_YEARLY
		// Yearly dates must appear exactly once a year

		if (fullDate.month != evt.startFullDate.month)
			return false
		if (fullDate.date > evt.startFullDate.date)
			return false;
		else if (fullDate.date == evt.startFullDate.date)
			return true;
		else if (fullDate.isLastDateOfMonth())
			return true;	// put event on last day of month
		else
			return false;
	}

	// Never get here
}


//------------------------------------------------------------------------------
// Method CalFullDate.getLastDateOfMonth: find last date of this month

CalFullDate.prototype.getLastDateOfMonth = function getLastDateOfMonth() {
	if (this.month == 1 && this.isLeapYear())
		return calDaysPerMonth[this.month] + 1;
	else
		return calDaysPerMonth[this.month];
}


//------------------------------------------------------------------------------
// Method CalFullDate.increment: increment full date by one day

CalFullDate.prototype.increment = function increment() {
	this.day++;
	if (this.day > 6)
		this.day = 0;
	if (this.isLastDateOfMonth()) {
		this.date = 1;
		this.month++;
		if (this.month > 11) { // Month 11 is December
			this.month = 0;
			this.year++;
		}
	}
	else
		this.date++;
}


//------------------------------------------------------------------------------
// Method CalFullDate.isLastDateOfMonth: find if this is the last date of month

CalFullDate.prototype.isLastDateOfMonth = function isLastDateOfMonth() {
	return (this.date == this.getLastDateOfMonth());
}


//------------------------------------------------------------------------------
// Method CalFullDate.isLeapYear: find if this is a leap year

CalFullDate.prototype.isLeapYear = function isLeapYear() {
	return ((this.year % 4 == 0) &&
        	(this.year % 100 != 0 || this.year % 400 == 0));
}


//------------------------------------------------------------------------------
// Method CalMonthSpec.show: show calendar for the specified month 

CalMonthSpec.prototype.show = function show() {
	var monthSpec = this;

	var classID;
	var day;
	var eventList = monthSpec.eventList;
	var evt;
	var html;		// scratch for computing HTML strings
	var i;
	var needFiller;
	var size;
	var today = new Date();
	var week;


	var itIsThisMonth = (today.getMonth() == monthSpec.month) &&
		(today.getFullYear() == monthSpec.year);


	// Wrap the date, navigation arrows, and popdown menus in a one-row table.
	// TBD: move styles into calendar.css when done

	html=""
+ "<table style='table-layout:fixed;' width='65%' align='center'>"
+ " <tr>"
+ "  <td width='28px'>"
+ "   <a href='javascript:calShowOtherMonth(-1)'><img style='vertical-align: middle;' border='0' alt='previous month' title='previous month' src='left_arrow.gif'></a>"
+ "  </td>"
+ "  <td class='month'>"
+ calMonthNames[monthSpec.month] + "  " + monthSpec.year 
+ "  </td>"
+ "  <td width='28px'>"
+ "   <a href='javascript:calShowOtherMonth(+1)'><img style='vertical-align: middle;' border='0' alt='next month' title='next month' src='right_arrow.gif'></a>"
+ "  </td>"
+ "  <td align='right'>"
+ "   <form style='display:inline;' method='post'>"
+ "    <select name='theMonth' onchange='calReshow(this); return true;'>";

	for (i = 0; i < 12; i++) {
		html = html + "<option value='" + i + "'";
		if (i == monthSpec.month)
			html = html + " selected";
		html = html + ">" + calMonthNames[i] + "</option>";
	}

	html = html
+ "    </select>"
+ "   </form>"
+ "  </td>"
+ "  <td align='left'>"
+ "   <form method='post' style='display:inline;'>"
+ "    <select name='theYear' onchange='calReshow(this); return true;'>";


	for (i = monthSpec.year-1; i < monthSpec.year+3; i++) {
		html = html + "<option value='" + i + "'";
		if (i == monthSpec.year)
			html = html + " selected";
		html = html + ">" + i + "</option>";
	}

	html = html
+ "    </select>"
+ "   </form>"
+ "  </td>"
+ " </tr>"
+ "</table><br>";

	document.write(html);


	// Create the calendar proper in a 7 by 6 table.  Note that we use
	// a null background to work around a NN bug with nested tables.

	document.write("<table class='calendar' bordercolor='black' background=''"
		+ "align='center' cellpadding='3' frame='border'>");


	// Write a header that shows day names

	document.write("<tr>");
	for (i = 0; i < 7; i++)
		document.write("<th class='daynames'>"
			+ calDayNames[i] + "</th>");
	document.write("</tr>");


	// Now write the calendar proper

	var date = 1 - monthSpec.dayOfTheFirst;
	for (week = 0; week < 6; week++) {
		document.write("<tr class='calendar' bordercolor='black'>");
		for (day = 0; day < 7; day++, date++) {


			// Assume we will need to write some filler

			needFiller = true;


			// Start a table cell and write the date in it

			if (itIsThisMonth && (date == today.getDate()))
				classID = "class='today'";
			else if (date < 1 || date > monthSpec.lastDateOfMonth)
				classID = "class='othermonth'";
			else
				classID = "class='day'";

			html = "<td " + classID + " width='14.3%' height='50'>";
			if (date >= 1 && date <= monthSpec.lastDateOfMonth) {
				html = html + "<span class='date'>" + date
					+ "</span><br>";
				needFiller = false;
			}
			document.write(html);


			// Now write the event names, if any

			if (eventList[week][day] != null) {
				size = eventList[week][day].length;
				for (i = 0;  i < size; i++) {
					evt = eventList[week][day][i];
					document.write("<a href='javascript:calShowDetails("
						+ Number(week) + "," + Number(day) + "," + Number(i) + "," + date
						+ ")'>");
					document.write(evt.name + "<br>");
					document.write("</a>");
					needFiller = false;
				}
			}


			// If we didn't write any text yet, write some filler
			// so that NN gets the cell color right.

			if (needFiller)
				document.write("&nbsp;");

			document.write("</td>");
		}
		document.write("</tr>");
	}
	document.write("</table>");

}


//------------------------------------------------------------------------------
// Method CalMonthSpec.sort: sort events in order of time

CalMonthSpec.prototype.sort = function sort() {
  var day;
  var events;
  var evt1;
  var evt2;
  var tmp;
  var i;
  var j;
  var week;

  for (week = 0; week < 6; week++) {
    for (day = 0; day < 7; day++) {
      events = this.eventList[week][day];
      for (i = 0; i < events.length; i++) {
        evt1 = events[i];
        for (j = i+1; j < events.length; j++) {
          evt2 = events[j];
          if (evt1.startTime.minutes > evt2.startTime.minutes) {
            tmp = evt1;
            evt1 = events[i] = evt2;
            evt2 = events[j] = tmp;
          }
        }
      }
    }
  }
}


//------------------------------------------------------------------------------
//------------------------------------------------------------------------------




/******************************************************************************/
/* Function definitions                                                       */
/******************************************************************************/


//------------------------------------------------------------------------------
// Function calCloseDetailsWindow: close detail window

function calCloseDetailsWindow() {
	if (calDetailsWindow && !calDetailsWindow.closed)
		calDetailsWindow.close();
	calDetailsWindow = null;
}


//------------------------------------------------------------------------------
// Function calCompareDates: compare two dates

function calCompareDates(m1, d1, y1, m2, d2, y2) {
	/*
	 * Returns (-1, 0 or +1) according to date1 (<, == or >) date2.
	 * Dates are specified by month, day, and year.
	 */

	if (y1 < y2)
		return -1;
	else if (y1 > y2)
		return +1;

	if (m1 < m2)
		return -1;
	else if (m1 > m2)
		return +1;

	if (d1 < d2)
		return -1;
	else if (d1 > d2)
		return +1;
	else
		return 0;
}


//------------------------------------------------------------------------------
// Function calFindMonthAndYearFromURL: extract month and year from the URL

function calFindMonthAndYearFromURL() {
	var today = new Date();
	var fields;
	var param;


	calMonth = -1;
	calYear = -1;
	if (window.location.search != "") {
		fields = window.location.search.split("?");
		var i;
		for (i = 0; i < fields.length; i++) {
			param = fields[i].split("=");
			if (param.length < 2)
				continue;
			if (param[0] == "month")
				calMonth = Number(param[1])-1;
			else if (param[0] == "year")
				calYear = Number(param[1]);
		}
	}
	if (calMonth == -1)
		calMonth = today.getMonth();
	if (calYear == -1)
		calYear = today.getFullYear();
}


//------------------------------------------------------------------------------
// Function calItem: make one entry in the calendar database

function calItem(startFullDate, repeat, endFullDate, startTime, endTime, name) {
	var description = "";
	var i;


	// Gather extra arguments into a description string, with <br> used
	// to separate the strings into lines.

	for (i = 6; i < arguments.length; i++) {
		if (i >= 7)
			description += "<br>";
		description += arguments[i];
	}

	var evt = new CalEvent(startFullDate, repeat, endFullDate, startTime,
		endTime, name, description);
	if (!evt.valid)
		calErrorCount++;
	if (calErrorCount == 1) {
		// Use empty list so user realizes something needs to be fixed

		calEventList = new Array();
	}
	if (calErrorCount > 0)
		return;

	calEventList[calEventList.length] = evt;
}


//------------------------------------------------------------------------------
// function calReshow: show calendar for a new month

function calReshow(select) {
	var option = select.options[select.selectedIndex];
	if (select.name == "theMonth")
		calMonth = Number(option.value);
	else if (select.name == "theYear")
		calYear = Number(option.value);


	/*
	 * Changing window.location has side effect of redrawing calendar.
	 * Note that NN 4 has bizarre problem.  It can't display remote
	 * files using search and can't debug local files using href!
	 */

	if (true) {
		if (calBrowser.isNN && (calBrowser.version <= 4)
			&& (window.location.host != "localhost")) {

			var url = (window.location.pathname.split("?"))[0]
				+ "?month=" + (calMonth+1) + "?year=" + calYear;
			window.location.href = url;
		}
		else {
			window.location.search = "?month=" + (calMonth+1) + "?year=" + calYear;
		}
	}
	else {
		var url = (window.location.pathname.split("?"))[0]
			+ "?month=" + (calMonth+1) + "?year=" + calYear;
		window.location.href = url;
	}
}


//------------------------------------------------------------------------------
// Function calResizeHandler: reload document after NN 4 window is resized
function calResizeHandler() {
	if (calBrowser.isNN && (calBrowser.version <= 5)) {
		if ((window.innerWidth != calOldWidth) || (window.innerHeight != calOldHeight)) {
			window.location.reload();
		}
	}
}


//------------------------------------------------------------------------------
// Function calShow: main function, shows the calendar for this month

function calShow() {
	if (calErrorCount > 0) {
		var msg = "" + calErrorCount + " error(s) found in event file.";
		msg = msg + "  First problem: " + calErrorMessage;
		alert(msg);
	}


	// Display today's calendar

	monthSpec = new CalMonthSpec(calMonth, calYear);
	monthSpec.sort();		// sort all events in time order
	monthSpec.show();
}


//------------------------------------------------------------------------------
// function calShowDetails:

function calShowDetails(week, day, i, date) {
	var monthSpec = this.monthSpec;
	var evt = monthSpec.eventList[week][day][i];


	// Find actual date in case event wasn't really in this month

	var when = new Date(monthSpec.year, monthSpec.month, date, 0, 0, 0, 0);

	when = calDayNames[day] + " " + calMonthNames[when.getMonth()] + " " + 
		when.getDate() + ", " + when.getFullYear();

	var time = evt.startTime.string;
	if (evt.endTime != null)
		time += "-" + evt.endTime.string;


	// Write a description of the event

	var html = 
	  "<html>"
+	  "  <head>"
+	  "    <title>Details for " + evt.name + "</title>"
+	  "  </head>"
+	  "<body>"
+	  "  <div style='text-align: center; font-size: 150%; "
+	  "	font-weight: bold;'>" + evt.name + "</div>"
+	  "  <div style='text-align: center; font-weight: bold;'>"
+		when + "</div>"
+	  "  <div style='text-align: center; font-weight: bold;'>"
+		time + "</div>"
+	  "  <br><br>"
+	  "  <div align='left'>" + evt.description + "</div>"
+	  "</body>"
+	"</html>";


	/*
	 * If this is a Macintosh running NN 4.79 and perhaps later, we can't
	 * use a separate details window because it comes up blank.  Perhaps
	 * I could figure out why; but I'll take the easy out and use the
	 * main window instead.
	 */

	if (calBrowser.isNN && calBrowser.isMac) {
		document.write(html);
		return;
	}


	calCloseDetailsWindow(); // close window if it's open to avoid IE errors
	calDetailsWindow = window.open(
		"about:blank",
		"calDetailsWindow" + (new Date()).valueOf(),
		"width=300,height=260,resizable=yes");
	calDetailsWindow.document.write(html);
}


//------------------------------------------------------------------------------
// function calShowOtherMonth

function calShowOtherMonth(n) {
	// If n == -1, show previous month.  If n == +1, show next month

	if (n == -1) {
		calMonth--;
		if (calMonth < 0) {
			calMonth = 11;
			calYear--;
		}
	}
	else {	// n == +1
		calMonth++;
		if (calMonth > 11) {
			calMonth = 0;
			calYear++;
		}
	}

	// Setting window.location has side effect of redrawing calendar.

if (true) {
	if (calBrowser.isNN && (calBrowser.version <= 4)
		&& (window.location.host != "localhost")) {

		var url = (window.location.pathname.split("?"))[0]
			+ "?month=" + (calMonth+1) + "?year=" + calYear;
		window.location.href = url;
	}
	else {
		window.location.search = "?month=" + (calMonth+1) + "?year=" + calYear;
	}
}
else {
	window.location.search = "?month=" + (calMonth+1) + "?year=" + calYear;
}
	return true;

};


/******************************************************************************/
/* Main code                                                                  */
/******************************************************************************/

calFindMonthAndYearFromURL();

// Memorize window sizes so we can work around NN 4 window resize bug

if (calBrowser.isNN && (calBrowser.version <= 5)) {
	calOldWidth = window.innerWidth;
	calOldHeight = window.innerHeight;
}


// Establish a handler to deal with NN 4's resize bug

window.onresize = calResizeHandler;
