/* This file defines the FRL_DateSelecter class.
 * The FRL_DateSelecter creates a popup allowing the user to select
 * a date.  
 *
 * Successful testing for functionality and format has been completed 
 * for the following platforms:
 *	Internet Explorer 6.0 
 *  FireFox 1.5
 *  Opera 9.02
 */

/* Object Constructor
 *
 * The format options are simple but allow for a good deal of 
 * flexibity.  In addition to using this format for output it
 * expected to be follwed for input as well. Feel free to use
 * any combination of:
 * - month  - Full text month (December, May, ...)
 * - mon    - Three letter month (Dec, May, ...)
 * - mm     - one or two digit month (02, 8, 12)
 * - dd     - one or two digit day of month (02, 08, 31)
 * - yyyy   - four digit year
 * The default format string is 'yyyy-mm-dd'.
 * @param   string format - the format used for reading and 
 *													writing date strings */
function FRL_DateSelecter(format) {
	/* default values */
	var today     = new Date();
	this.nowYear  = today.getFullYear();
	this.nowMonth = today.getMonth();
	this.nowDay   = today.getDate();
	this.format   = format != null ? new String(format).toLowerCase() : 'dd-mm-yyyy';
	/* sel* is the date selected on the calendar
	 * dis* is the currently displayed date range */
	this.selYear  = this.disYear  = this.nowYear;
	this.selMonth = this.disMonth = this.nowMonth;
	this.selDay   = 0;

	/* Data used in the interface and when generating output */
	this.months = [
		"January", "February", "March",     "April",   "May",      "June",
		"July",    "August",   "September", "October", "November", "December"];
	this.mons = [
		"Jan", "Feb", "Mar", "Apr", "May", "Jun",
		"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
	this.daysInMonth     = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
	this.daysInMonthLeap = [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
}

/* Generate Calendar Framework
 *
 * By creating its own divs in the document there is no need
 * to prebuild any html.  This allows the Object to be created
 * and function properly by simply including the file. */
FRL_DateSelecter.prototype.build = function() {
	/* The outermost container */
	var tmp = document.createElement('div');
	tmp.setAttribute('id', 'CAL_container');
	document.getElementsByTagName('body')[0].appendChild(tmp);
	
	/* Heading information */
	tmp = document.createElement('div');
	tmp.setAttribute('id', 'CAL_heading');
	document.getElementById('CAL_container').appendChild(tmp);
	
	/* Add the month/year selecter to the header */
	tmp = '<select id="CAL_newMon" onchange="FRL_ds.gridUpdate();">';
	for(i = 0; i < this.months.length; i++) {
		tmp += '<option value="' + i + '">' + this.months[i] + '</option>';
	}
	tmp += '</select><select id="CAL_newYear" onchange="FRL_ds.gridUpdate();">';
	for(i = this.nowYear - 120; i < this.nowYear + 1; i++) {
		tmp += '<option value="' + i + '">' + i + '</option>';
	}
	tmp += '</select>';
	document.getElementById('CAL_heading').innerHTML = tmp;

	/* The calendar grind */
	tmp = document.createElement('div');
	tmp.setAttribute('id', 'CAL_dayGrid');
	document.getElementById('CAL_container').appendChild(tmp);
	
	/* The container for controls to change the grid */
	tmp = document.createElement('div');
	tmp.setAttribute('id', 'CAL_updateGrid');
	document.getElementById('CAL_container').appendChild(tmp);

	/* Add the today and cancel controls to the footer */
	tmp = '<input type="button" id="CAL_today" onclick="FRL_ds.toToday();" value="Today" />';
	tmp += '<input type="button" id="CAL_cancel" onclick="FRL_ds.disable();" value="Cancel" />';
	document.getElementById('CAL_updateGrid').innerHTML = tmp;
}

/* Display Date Selecting Interface
 *
 * This function can be called as the on* event of any form element
 * after the document has loaded.  Calling it before the document
 * load has completed will have unpredictable effects.  The interface
 * will align itself to the top right of whichever element is passed
 * as the first argument.  The second argument is the ID attribute of
 * the element where the date will be writen.
 * e.g. <button onclick="FRL_ds.promptUser(this, 'end_date');" ...
 * @param element neighbor    - the element that determins location
 * @param string  destination - the id of the element where the date string
 *                              will be written */
FRL_DateSelecter.prototype.promptUser = function(neighbor, destination) {
	/* record the location to save the selected date */
	this.target = document.getElementById(destination);
	
	/* If the calendar framework does not exist, create it */
	if(!document.getElementById('CAL_container')) {
		this.build();
	}
	
	/* Place the framework based on neighbor */
	var con = document.getElementById('CAL_container');
	con.style.position = 'absolute';
	
	// find the position of the neighbor.  Note: Using just
	// the offsets has unpredictable results.
	var pos = FRL_find_position(neighbor);
	
	// Locate the popup to the top right of the neighbor
	con.style.top  = pos['top'] + 'px';
	con.style.left = neighbor.offsetWidth + pos['left'] +'px';

	/* generate the month grid based on the date from the target of
	 * the last date selected */
	this.setDayGrid();
	
	this.redraw();
	
	document.getElementById('CAL_container').className = '';
}

/* Update the day grid when month or year changes
 *
 * A change to either the month or the year necesitates that the
 * displayed month be rewritten. */
FRL_DateSelecter.prototype.gridUpdate = function() {
	this.disYear  = document.getElementById('CAL_newYear').value;
	this.disMonth = document.getElementById('CAL_newMon').value;
	this.setDayGrid();
	this.redraw();
}

/* Refresh the display area
 *
 * After any update has been made to the dayGrid, disMonth, and/or
 * disYear the calendar must be rewritten.  */
FRL_DateSelecter.prototype.redraw = function() {
	document.getElementById('CAL_dayGrid').innerHTML = this.dayGrid;
	document.getElementById('CAL_newMon').value      = this.disMonth;
	document.getElementById('CAL_newYear').value     = this.disYear;

	/* IE Workaround See Comments for ie_hover */
	if(window.attachEvent) {
		this.ie_hover();
	}
}

/* Rebuild the day grid
 *
 * When the framework is built or an update has been made to 
 * the dayGrid, disMonth, and/or disYear the calendar must be
 * rebuilt.  However, it will not be rewriten untill refresh()
 * has been called.*/
FRL_DateSelecter.prototype.setDayGrid = function() {
	var y = this.disYear;
	var m = this.disMonth;
	/* reset the grid */
	this.dayGrid = '<span class="CAL_colHeader">' + 
		Array('S', 'M', 'T', 'W', 'T', 'F', 'S').join('</span><span class="CAL_colHeader">') +
		'</span><br />';
	/* get the number of days in th month taking into account
	 * leap year */
	var days = this.isLeap(y) ? this.daysInMonthLeap[m] : this.daysInMonth[m];
	
	/* Get the day of week for the first day in the display month */
	var gDate = new Date(y, m, 1, 0, 0 ,0)
	var firstDow = gDate.getDay();

	/* Generate filler.  If the firstDow is sunday no filler will be
	 * generated.  If it is monday, one day worth of filler will be
	 * generated. If it is tuesday,... */
	var wDay = 0;
	for(var i = 0; i < firstDow; i++) {
		this.dayGrid += '<span class="CAL_filler">&nbsp;</span>';
		wDay++;
	}

	var weeks =  1;	
	
	/* Generate the rest of the grid making sure to start where the 
	 * filler left off. */
	for (var i = 1; i <= days; i++) {
		var cl = 'CAL_d' + wDay;
		/* The Selected day highlighting has been disabled until the 	
		 * functionality to determin the currently selected date for the
		 * target field has been refined */
//		if (i == this.selDay && y == this.selYear && m == this.selMonth) {
//			cl += ' CAL_dSel';
//		}
		if (i == this.nowDay && y == this.nowYear && m == this.nowMonth) {
			cl += ' CAL_dNow';
		}
		this.dayGrid += '<span class="' + 
										cl + '" onclick="FRL_ds.selectDay(' + i + ');">' + 
										i + '</span>';
		if(wDay++ == 6){
			this.dayGrid += '<br />';
			wDay = 0;
			weeks++;
		}
	}
	
	/* The calendar grid can take up anywhere from 4 to 6 rows.  In
	 * order to prevent the change in the number or rows it is always
	 * filled to 6 rows.  Just a little piece of consistancy work. */
	while (weeks <= 6) {
		this.dayGrid += '<span class="CAL_filler">&nbsp;</span>';
		if(wDay++ == 6){
			this.dayGrid += '<br />';
			wDay = 0;
			weeks++;
		}
	}
}

/* Create a string representation of the selected date
 *
 * While not an interesting solution it is simple and flexable.  To
 * extend the possible date string simply add the requsit data to
 * the object and the replace statement below.  */
FRL_DateSelecter.prototype.formatDate = function() {
	var dd = this.selDay < 10 ? '0' + this.selDay : this.selDay;

	// The month needs to be incramented when displayed numerically;
	var mm = Number(this.selMonth) < 9  ? 
					 '0' + (Number(this.selMonth) + 1) : 
					 Number(this.selMonth) + 1;
	

	this.formattedDate = this.format;
	this.formattedDate = this.formattedDate.replace(/yyyy/,  this.selYear);
	this.formattedDate = this.formattedDate.replace(/mm/,    mm);
	this.formattedDate = this.formattedDate.replace(/mon/,   this.mons[this.selMonth]);
	this.formattedDate = this.formattedDate.replace(/month/, this.months[this.selMonth]);
	this.formattedDate = this.formattedDate.replace(/dd/,    dd);
}

/* Select a date from the UI
 *
 * This funciton is called from the day grid when a user has
 * decided which date they want.  After the date is formatted it
 * is simply dumpted in the target identified when the calendar
 * was activated.  */
FRL_DateSelecter.prototype.selectDay = function(d) {
	this.selDay   = d;
	this.selMonth = this.disMonth;
	this.selYear  = this.disYear;
	
	this.formatDate();
	
	if (this.target) {
		this.target.value = this.formattedDate;
		this.target.onchange();
	}
	else {
		alert('errror');
	}
	this.disable();
}

/* Leapyear test
 *
 * While leap years are clearly defined there are too many
 * exceptions to take care of in a silmple one liner.
 * @param   integer y the year to test
 * @returns boolean */
FRL_DateSelecter.prototype.isLeap = function(y) {
	/*	A year is a leap year if it is
	 * - divisible by 4
	 * - but not evenly divisible by 100
	 * - but is if divisible by 100 and dividible by 400 */
	if ((y % 4) == 0) {
		if ((y % 100) == 0 && (y % 400) != 0) {
			return false;
		}
		return true;
	}
	else {
		return false;
	}
}

/* Hide the calendar interface
 *
 * When not in use out of site is out of mind.  There are dozens of
 * ways to do this and I decided on a simple one.  By adding a class
 * that sets the display to none the interface is out of the document
 * flow completely.  Remove the class in promptUser() will make it
 * visible again.  */
FRL_DateSelecter.prototype.disable = function() {
	document.getElementById('CAL_container').className = 'CAL_hidden';
}

/* Set the display month and year to today
 *
 * A simple convenience option for the user that takes advantage
 * of the functionality already built into the interface.  */
FRL_DateSelecter.prototype.toToday = function(ds) {
	document.getElementById('CAL_newYear').value = this.nowYear;
	document.getElementById('CAL_newMon').value  = this.nowMonth;
	document.getElementById('CAL_newMon').onchange();
	this.selectDay(this.nowDay);
}

// A workaround for dealing with Internet Explorere not complying with
// the w3c document specification.  This enables :hover to function as expected 
FRL_DateSelecter.prototype.ie_hover = function() {
	var sfEls = document.getElementById("CAL_container").getElementsByTagName("SPAN");
	for (var i=0; i<sfEls.length; i++) {
		sfEls[i].onmouseover=function() {
			this.className+=" CAL_hover";
		}
		sfEls[i].onmouseout=function() {
			this.className=this.className.replace(new RegExp(" CAL_hover\\b"), "");
		}
	}
}

var FRL_ds = new FRL_DateSelecter();