/*
DatePicker
Created: 2007-05-23
Author: milesck
Sample usage:

<input type="text" id="datefield" />
<script type="text/javascript" defer="true">
// This script can go anywhere; best practice is to have it load externally
// and wait for window.onload (use addWindowOnload from /js/core.js)

var picker = new datePicker('datefield');
// The order that these methods are called makes no difference:
    picker.setEarliestDate('1/1/2007'); // Only allow dates in 2007 and later to be chosen
    picker.setLatestDate(new Date()); // Today is the last day that can be chosen
    picker.setValidDaysOfWeek([1,3,5]); // Only allow Mondays, Wednesdays, Fridays
    picker.setInvalidDates(['12/25/2007']); // Can't choose Christmas, even if it falls on MWF
    picker.setValidDates(['7/25/2007']); // Can choose my birthday, even if it doesn't fall on MWF

</script>
*/

function datePicker(fieldID) {
	
	var field;
	var that = this;
	var SECONDS=1000;
	var MINUTES=60*SECONDS;
	var HOURS = 60*MINUTES;
	var DAYS = 24*HOURS;
	var DAYSOFWEEK = ['S', 'M', 'T', 'W', 'T', 'F', 'S'];
	var MONTHNAMES = ['January', 'February', 'March', 'April', 'May', 'June',
		'July', 'August', 'September', 'October', 'November', 'December'];
	
	var chosenDate;
	var validDaysOfWeek = {any: 1};
	var validDates = {};
	var invalidDates = {};
	var format = 'yyyy-mm-dd';
	var earliestDate, latestDate;

	var calClick = false;

	var showPicker = function (e) {
		calClick = false;
		if (!e) var e = window.event;
		// if we're already showing the calendar, hide it:
		if (document.getElementById('datePicker')) {
			hidePicker(true);
			return false;
		}
		
		var cal = document.createElement('div');
		cal.id = 'datePicker';
		cal.onmousedown = function() { return false; } // Prevent selection
		document.body.insertBefore(cal, null);
		
		// Position the calendar below the field it's attached to:
		var top = field.offsetHeight + field.offsetTop, left = field.offsetLeft;
		var offsetElement = field;
		while (offsetElement = offsetElement.offsetParent) {
			top += offsetElement.offsetTop;
			left += offsetElement.offsetLeft;
		}
		cal.style.top = top + 'px';
		cal.style.left = left + 'px';
        
		// Create an iFrame for IE6 and earlier; this prevents <select>
		// boxes from shining through the picker:
		/*@cc_on
			@if (@_jscript_version < 5.7)
				var iframe = document.createElement('iframe');
                iframe.src = '/js/blankfile';
				cal.appendChild(iframe);
			@end
		@*/
		
		// Figure out what month to display initially:
		var showDate;
		if (field.value) showDate = parseDate(field.value);
		var today = midnight(new Date());
		if (showDate && isValidDate(showDate)) {
			chosenDate = showDate;
		} else {
			chosenDate = undefined;
			if (earliestDate && today < earliestDate) showDate = earliestDate;
			else if (latestDate && today > latestDate) showDate = latestDate;
			else showDate = today;
		}
		
		// Fill the datePicker div with the calendar and other goodies:
		changeMonth(showDate);
		
		// Make datePicker hide when document clicked elsewhere:
		document.onclick = hidePicker;
		cal.onclick = function() { calClick = true; return true; }
		
		// Prevent this event from bubbling up to the handler we just set:
		e.cancelBubble = true;
		if (e.stopPropagation) e.stopPropagation();
		
		// Don't follow link:
		return false;
	}
		
	var hidePicker = function(e) {
		if (calClick) { calClick = false; if(e != true) return; }
		if (!e) var e = window.event;
		var target = e.target || e.srcElement;
		var cal = document.getElementById('datePicker');
		// If hidePicker(true) was called manually, or if this function was 
		// called by a click outside of the picker:
		if (e == true || !nodeIsDescendantOfNode(target, cal)) {
			// Get rid of the document onclick handler:
			document.onclick = function () {};
			// Delete the picker:
			cal.parentNode.removeChild(cal);
		}
	}
	
	var changeMonth = function (m) {
		// Empty out the picker div:
		var cal = document.getElementById('datePicker');
		
		/*@cc_on
			@if (@_jscript_version < 5.7)
                var iframe;
                var oldChild;
                var child = cal.firstChild;
                while (child) {
                    oldChild = child;
                    child = oldChild.nextSibling;
                    if (oldChild.nodeName.toLowerCase() == 'iframe') {
                        iframe = oldChild;
                    } else {
                        cal.removeChild(oldChild);
                    }
                }
				iframe.style.width = '0px';
				iframe.style.height = '0px';
            @else @*/
                while (cal.lastChild)
                    cal.removeChild(cal.lastChild);
			/*@end
		@*/
		
		var year = m.getFullYear();
		var month = m.getMonth();
		// Get the first of the month:
		var d = new Date(year, month);
		// Get the beginning of the week of the first of the month:
		d = new Date(year, month, d.getDate() - d.getDay());
		
		var today = new Date();
		var table = document.createElement('table');
		var tbody = document.createElement('tbody');
		table.appendChild(tbody);
		
		// Create the day-of-week header (S M T W T F S)
		var row = document.createElement('tr');
		for (var day in DAYSOFWEEK) {
			var cell = document.createElement('th');
			cell.appendChild(document.createTextNode(DAYSOFWEEK[day]));
			row.appendChild(cell);
		}
		tbody.appendChild(row);
		
		var evenRow = true;
		// While d is in m (or the previous month):
		while (d.getMonth() == month || d.getMonth() == (month - 1) || 
			(d.getMonth() == 11 && month == 0)) {
			row = document.createElement('tr');
			row.className = (evenRow ? 'evenrow' : 'oddrow');
			for (var i = 0; i < 7; i++) {
				var cell = document.createElement('td');
				cell.className = '';
				if (d.getMonth() != month) cell.className += ' otherMonth';
				if (sameDate(d, today)) cell.className += ' today';
				if (chosenDate && sameDate(d, chosenDate)) cell.className += ' chosen';
				var text = document.createTextNode(d.getDate());
				if (isValidDate(d)) {
					var link = document.createElement('a');
					link.date = d; // this is OK because tomorrow() returns a new d, otherwise it wouldn't be (because all links would share the same date object)
					link.href = "#";
					link.onclick = function() { return setDate(this.date) };
					link.appendChild(text);
					cell.appendChild(link);
				} else {
					cell.className += ' invalid';
					cell.appendChild(text);
				}

				row.appendChild(cell);
				d = tomorrow(d);
			}
			tbody.appendChild(row);
			evenRow = !evenRow;
		}
		
		var title = document.createElement('span');
		title.className = 'title';
		title.appendChild(document.createTextNode(MONTHNAMES[month] + ' ' + year));
		
		var titleBar = document.createElement('div');
		titleBar.className = 'titleBar';
		titleBar.appendChild(title);
		
		if (!earliestDate || earliestDate < new Date(year, month)) {
			var prevLink = document.createElement('a');
			prevLink.className = 'prevLink';
			prevLink.appendChild(document.createTextNode('<'));
			var prevMonth = month - 1; var prevYear = year;
			if (prevMonth < 0) { prevMonth += 12; prevYear -= 1; }
			prevLink.onclick = function() { return changeMonth(new Date(prevYear, prevMonth)); };
			prevLink.href = "#";
			titleBar.appendChild(prevLink);
		}
		
		if (!latestDate || latestDate > new Date(year, month+1)) {
			var nextLink = document.createElement('a');
			nextLink.className = 'nextLink';
			nextLink.appendChild(document.createTextNode('>'));
			nextLink.onclick = function() { return changeMonth(new Date(year, month+1)); };
			nextLink.href = "#"
			titleBar.appendChild(nextLink);
		}
		
		cal.appendChild(titleBar);
		cal.appendChild(table);

		/*@cc_on
			@if (@_jscript_version < 5.7)
				iframe.style.width = (cal.clientWidth) + 'px';
				iframe.style.height = (cal.clientHeight) + 'px';
			@end
		@*/
		
		// Prevent document.onclick from being called:
		if (window.event && window.event.stopPropagation)
            window.event.stopPropagation();
		
		return false;
	}
	
	var sameDate = function (d1, d2) {
		return d1.getDate() == d2.getDate() && 
			d1.getMonth() == d2.getMonth() && 
			d1.getFullYear() == d2.getFullYear();
	}
	
	var midnight = function (d) {
		return new Date(d.getFullYear(), d.getMonth(), d.getDate());
	}
	
	var tomorrow = function (d) {
		return new Date(d.getFullYear(), d.getMonth(), d.getDate()+1);
	}
	
	var nodeIsDescendantOfNode = function(child, parent) {
		while (child = child.parentNode) {
			if (child == parent) return true;
		}
		return false;
	}
	
	var isValidDate = function (d) {
		if (!d) return false;
		if (earliestDate && d < earliestDate || latestDate && d > latestDate)
			return false;
		var valid = (validDaysOfWeek.any || validDaysOfWeek[d.getDay()]);
        if (invalidDates[d]) valid = false;
        if (validDates[d]) valid = true;

		return valid;
	}
	
	var attach = function() {
		field = document.getElementById(fieldID.id);
		var img = document.createElement('img');
		img.src = 'https://www.engin.umich.edu/images/forms/datechooser.png';
		img.style.border = 0;
		var link = document.createElement('a');
		link.href = "#";
		link.onclick = showPicker;
		link.appendChild(img);
		field.parentNode.insertBefore(link, field.nextSibling);
	}
	
	var setDate = function(d) {
		chosenDate = d;
		field.value = dateFormat(d, format);
		hidePicker(true);
		
		return false;
	}
	
	var zeroPad = function (nr) {
		var str = nr.toString();
		if (nr < 10) str = "0" + str;
		return str;
	}
	
	var dateFormat = function(d, format) {
		if (format == 'yyyy-mm-dd') { 
            return d.getFullYear() + '-' + zeroPad(d.getMonth()+1) + '-' + zeroPad(d.getDate())
        } else { // format == 'mm/dd/yyyy'
            return zeroPad(d.getMonth()+1) + '/' + zeroPad(d.getDate()) + '/' + d.getFullYear();
        }
    }
	
	var parseDate = function(d) {
		if (typeof(d) == 'string' || (typeof(d) == 'object' && d instanceof String)) {
    		var match;
    		if (match = d.match(/^(?:(\d{4})-)?(0?[0-9]|1[0-2])-([0-2]?[0-9]|3[0-1])$/)) {
                var year = match[1] ? parseInt(match[1], 10) : (new Date()).getFullYear();
    			return new Date(year, parseInt(match[2], 10)-1, parseInt(match[3], 10));
    		} else if (match = d.match(/^(0?[0-9]|1[0-2])[-\/]([0-2]?[0-9]|3[0-1])(?:[-\/](\d{4}))?$/)) {
                var year = match[3] ? parseInt(match[3], 10) : (new Date()).getFullYear();
    			return new Date(year, parseInt(match[1], 10)-1, parseInt(match[2], 10));
    		}
    		return undefined;
        } else if (typeof(d) == 'object' && d instanceof d) {
            return midnight(d);
        }
        return undefined;
	}
	
	this.setValidDaysOfWeek = function(list) {
		validDaysOfWeek = {}
		for (var i = 0; i < list.length; i++) {
			validDaysOfWeek[list[i]] = 1;
		}
	}
	
	this.setInvalidDates = function(list) {
		invalidDates = {}
		for (var i = 0; i < list.length; i++) {
			var date = parseDate(list[i]);
			if (date) invalidDates[date] = 1;
		}
	}
	
	this.setValidDates = function(list) {
		validDates = {}
		for (var i = 0; i < list.length; i++) {
			var date = parseDate(list[i]);
			if (date) validDates[date] = 1;
		}
	}
	
	this.setEarliestDate = function(date) {
		date = parseDate(date);
		if (date) earliestDate = midnight(date);
	}
	
	this.setLatestDate = function(date) {
		date = parseDate(date);
		if (date) latestDate = midnight(date);
	}
	
	this.setFormat = function(fmt) {
		format = fmt.toLowerCase();
	}
	
	attach();
}