// Calendar widget

var CalendarWidget = function($) {

	function $it(elementID) {
		return document.getElementById(elementID);
	}

	// this function escapes strings for use in specifying IDs in jQuery expressions
	function escapeID(s) {
		var togo = "";
		for (var i = 0; i < s.length; i += 1) {
			var c = s.charAt(i);
			if (c==':' || c=='.' || c=='[' || c==']') {
				togo += '\\';
			}
			togo += c;
		}
		return togo;
	}
	
	// get a date from y, m, d (m is 1-based)
	function getDate(y, m, d) {
		var date = new Date();
		date.setFullYear(y, m - 1, d);
		return date;
	}
	
	// parses a date in format yyyyMMdd
	function parseDate(s) {
		return getDate(s.substring(0, 4), s.substring(4, 6), s.substring(6, 8));
	}
	
	// steps supplied date one day backward
	function backOneDay(d) {
		d.setTime(d.getTime() - 86400000); // I hope this is good enough. It ignores leap-seconds.
	}
	
	// steps supplied date one day forward
	function forwardOneDay(d) {
		d.setTime(d.getTime() + 86400000); // Ignores leap-seconds.
	}
	
	// formats the given date as 'MMMM yyyy'
	function formatMonthYear(d) {
		return MMMM[d.getMonth()] + " " + d.getFullYear();
	}
	
	// formats the given date as 'd MMMM yyyy'
	function formatDMY(d) {
		return d.getDate() + " " + formatMonthYear(d);
	}
	
	// whether the specified dates are on the same day
	function sameDay(d1, d2) {
		return d1.getDate()==d2.getDate() && d1.getMonth()==d2.getMonth() && d1.getFullYear()==d2.getFullYear();
	}
	
	// whether a <= b <= c (comparison done of Date.getTime() )
	function dateBetween(a, b, c) {
		return a.getTime() <= b.getTime() && b.getTime() < c.getTime() + 86400000;
	}
	
	// parse a URL into an object (just resource and query params)
	// XXX DOESN'T DECODE VALUES - THIS WILL NEED FIXING IF USED BEYOND THIS FILE
	function parseURL(url) {
		// alert('parseURL("' + url + '")'); 
		var qIndex = url.indexOf("?");
		var o = {
			 resource: (qIndex == -1) ? url : url.substring(0, qIndex),
			 query: '',
			 paramMap: { }
		};
		if (qIndex >= 0) {
			var qString = url.substring(qIndex + 1);
			o.query = qString;
			var params = qString.split("&");
			o.paramMap = { };
			for (var i = 0; i < params.length ; i += 1) {
				if (params[i].length === 0) {
					continue;
				}
				var p = params[i].split("=");
				var name = p[0];
				var value = p[1];
				o.paramMap[name] = value;
			}
		}
		return o;
	}
	
	// build a URL from the object structure as returned by parseURL
	// XXX DOESN'T ENCODE VALUES - THIS WILL NEED FIXING IF USED BEYOND THIS FILE
	function buildURL(o) {
		var url = o.resource;
		var sep = "?";
		for (var p in o.paramMap) {
			url += sep + p + "=" + o.paramMap[p];
			sep = "&";
		}
		return url;
	}
		
	
	var MMMM = [ "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December" ];
	
	return {
		// A call to this function goes in the HTML body, after the widget markup
		// You could also call it in an onload, or jQuery(document).ready
		init_CalendarWidget: function(nameBase) {
//			$(document).ready(function() {
//				alert("init_CalendarWidget('" + nameBase + "')");
				var nameBaseEsc = escapeID(nameBase);

//				alert("nameBaseEsc = '" + nameBaseEsc + "'");

				// restrict container <div> to size of calendar
				var width = $("#" + nameBaseEsc + "calendar").width();
				var height = $("#" + nameBaseEsc + "calendar").height();
				$("#" + nameBaseEsc).width(width);
				$("#" + nameBaseEsc).height(height);
				
				// These variables store the month and year of the currently-shown control.
				// They are populated in the init function from values in the document.
				var month = parseInt($("#" + nameBaseEsc + " > .data > .month").text());
				var year = parseInt($("#" + nameBaseEsc + " > .data > .year").text());
				// Get today's date, and selected region, from document, as provided by server
				var today = parseDate($("#" + nameBaseEsc + " > .data > .today").text()); // today's date, as supplied by server
				var ds = parseDate($("#" + nameBaseEsc + " > .data > .ds").text()); // beginning of selected region
				var de = parseDate($("#" + nameBaseEsc + " > .data > .de").text()); // end of selected region
				
				// increments the month/year
				function incMonth() {
					month += 1;
					if (month>12) {
						month = 1;
						year += 1;
					}
				}
				
				// decrements the month/year
				function decMonth() {
					month -= 1;
					if (month<1) {
						month = 12;
						year -= 1;
					}
				}
				
				function nextMonthText() {
					var m = month + 1;
					var y = year;
					if (m > 12) {
						m = 1;
						y += 1;
					}
					return MMMM[m - 1] + " " + y;
				}
				
				function prevMonthText() {
					var m = month - 1;
					var y = year;
					if (m < 1) {
						m = 12;
						y -= 1;
					}
					return MMMM[m - 1] + " " + y;
				}
	
				
				
				// Builds calendar elements (copying structure from original)
				// Month and year come from the variables declared above
				// Returns a query matching the newly-built calendar
				function buildCalendar(id) {
					var calQ = $("#" + nameBaseEsc + "calendar").clone().attr("id", id);
					// calQ.hide();
	
					// initialise 'cursor' to 1st day of the month we are rendering
					var cursor = getDate(year, month, 1);
					//msg(cursor + "");
					
					// set month title
					calQ.find("#" + nameBaseEsc + "monthtitle").text(formatMonthYear(cursor));
					
					// count back to previous Monday
					do {
						backOneDay(cursor);
					} while (cursor.getDay() != 1);
					
					// now render 6 weeks of days
					for (var week = 0; week < 6; week += 1) {
						var tr = calQ.find("tbody > tr:eq(" + week + ")");
						for (var day = 0; day < 7; day += 1) {
							// for each day, we set the link parameters, the link text, the link title and the class
							var td = tr.find("td:eq(" + day + ")");
							var a = td.find("a");
							
							a.text(cursor.getDate());
							
							a.attr("title", formatDMY(cursor));
							
							var css;
							if (sameDay(cursor, today)) {
								css = "today";
							} else {
								if (dateBetween(ds, cursor, de)) {
									if (cursor.getMonth() + 1 == month) {
										css = "selected";
									} else {
										css = "otherdaysel";
									}
								} else {
									if (cursor.getMonth() + 1 == month) {
										css = "day";
									} else {
										css = "otherday";
									}
								}
							}
							td.attr("class", css);
							
							var link = a.attr("href");
							var url = parseURL(link);
							url.paramMap.dsd = cursor.getDate();
							url.paramMap.dsm = cursor.getMonth() + 1;
							url.paramMap.dsy = cursor.getFullYear();
							link = buildURL(url);
							a.attr("href", link);
							
							forwardOneDay(cursor);
						}
					}
					calQ.find("#" + nameBaseEsc + "nextmonthlink").click(function(){ return false; });
					calQ.find("#" + nameBaseEsc + "prevmonthlink").click(function(){ return false; });
					
					return calQ; 
				}
		
				function nextHandler() {
					$(this).click(function(){ return false;});
					incMonth();
					var calQ = buildCalendar(nameBase + "calendar_right");
					var oldQ = $("#" + nameBaseEsc + "calendar");
					var scroller = $("#" + nameBaseEsc + "scroller");
					calQ.appendTo(scroller);
					var left = parseInt(oldQ.css("left"));
					calQ.css("left", (left + oldQ.width()) + "px");
					// animate
					$("#" + nameBaseEsc + "scroller").animate({ left: parseInt(scroller.css("left")) - oldQ.width() }, 333, null, function() {
						// upon completion of animation:
						// remove old (calendar), rename new to calendar, install handler on new calendar
						oldQ.remove();
						calQ.attr("id", nameBase + "calendar");
						calQ.find("#" + nameBaseEsc + "nextmonthlink").click(nextHandler);
						calQ.find("#" + nameBaseEsc + "prevmonthlink").click(prevHandler);
						calQ.find("#" + nameBaseEsc + "nextmonthlink").attr("title", "next month: " + nextMonthText());
						calQ.find("#" + nameBaseEsc + "prevmonthlink").attr("title", "previous month: " + prevMonthText());
					});
						
					return false;
				}
				
				function prevHandler() {
					$(this).click(function(){ return false;});
					decMonth();
					var calQ = buildCalendar(nameBase + "calendar_left");
					var oldQ = $("#" + nameBaseEsc + "calendar");
					var scroller = $("#" + nameBaseEsc + "scroller");
					calQ.appendTo(scroller);
					calQ.css("left", (parseInt(oldQ.css("left")) - oldQ.width()) + "px");
					// animate
					$("#" + nameBaseEsc + "scroller").animate({ left: parseInt(scroller.css("left")) + oldQ.width() }, 333, null, function() {
						// upon completion of animation:
						// remove old (calendar), rename new to calendar, install handler on new calendar
						oldQ.remove();
						calQ.attr("id", nameBase + "calendar");
						calQ.find("#" + nameBaseEsc + "nextmonthlink").click(nextHandler);
						calQ.find("#" + nameBaseEsc + "prevmonthlink").click(prevHandler);
						calQ.find("#" + nameBaseEsc + "nextmonthlink").attr("title", "next month: " + nextMonthText());
						calQ.find("#" + nameBaseEsc + "prevmonthlink").attr("title", "previous month: " + prevMonthText());
					});
						
					return false;
				}
	
				$("#" + nameBaseEsc + "nextmonthlink").click(nextHandler);
				$("#" + nameBaseEsc + "prevmonthlink").click(prevHandler);
//			});

		}
	};
}(jQuery);
