/*
 * @Class: Calendar
 * 
 * @Description: An interactive calendar widget
 * 
 * @Usage: new Calendar(containerElement)
 *	
 * @Methods: no public methods
 * 
 */

var Calendar = (!detectBrowser.modernBrowser())?function(){}:Class.create();
Calendar.prototype = (!detectBrowser.modernBrowser())?{}:{
	initialize: function(div) {
		// don't draw calendar if div doesn't exist
		if(!div){ return false; }

		// This is the element into which all of the calendar's DOM elements are inserted
		this.container = div;

		this.today = new Date();
		this.month = this.today.getMonth();
		this.year = this.today.getFullYear();

		// Back and forward buttons to navigate months
		this.backward = new Element("a", {"class": "backward",'href':'#'});
		Event.observe(this.backward, "click", function(e) {
			this.changeMonth(this.month - 1);
			Event.stop(e);
			return false;
		}.bind(this));
		this.forward = new Element("a", {"class": "forward",'href':'#'});
		Event.observe(this.forward, "click", function(e) {
			this.changeMonth(this.month + 1);
			Event.stop(e);
			return false;
		}.bind(this));

		this.display_month = new Element("span").insert(Calendar.prototype.monthNames[this.month]);
		this.display_year = new Element("span").insert(this.year);
	
		// navigational elements
//		var tableNav_wrapper = new Element("div",{'id':'tableNav_wrapper'});
		var tableNav_wrapper = $('tableNav_wrapper');
		tableNav_wrapper.innerHTML = "";
		var tableNav = new Element("div", {'id': "tableNav"});
		tableNav.insert(this.backward);
		tableNav.insert(this.forward);
		tableNav.insert(this.display_month);
		tableNav.insert(this.display_year);
		tableNav_wrapper.insert(tableNav);
		this.container.insertBefore(tableNav_wrapper, this.container.firstChild);

		var tableAlreadyExists = false;
		this.table = this.container.getElementsBySelector("table")[0];

		// collect of all the days in the entire month
		this.cells = this.table.getElementsBySelector("td");
		// collection of all the week rows (empty or not)
		this.rows = this.table.getElementsBySelector("tr");

		// lazy-loaded list of events
		this.monthEventLists = {};

		this.populateTable();
	},

	// draw calendar
	populateTable: function() {
		var date = new Date(this.year, this.month, "1");
		var firstDay = date.getDay();
		date.setFullYear(this.year, this.month, "32");
		var numDays = 32 - date.getDate();

		// use much faster direct node manipulation instead of prototype's innerHTML-based method
		var newDrawMethod = false;

		var cellTexts = [];

		// give calendar cells numbers and styles, style the cell for today.
		for(var i = 0; i < this.cells.length; i++) {
			if(newDrawMethod && this.cells[i].firstChild && this.cells[i].firstChild.nodeType==1 && $(this.cells[i].firstChild).hasClassName('this_day_wrap')){
				this.cells[i].innerHTML = "";
			}
			var cellDigits = ((i - firstDay + 1)>9)?(i - firstDay + 1):"0"+(i - firstDay + 1).toString();
			var cellText = (i >= firstDay && i < (numDays + firstDay))?(cellDigits):String.fromCharCode(160);
			cellTexts[i] = cellText;
			if(newDrawMethod){
				if(this.cells[i].firstChild){
					this.cells[i].firstChild.nodeValue = cellText;
				} else {
					this.cells[i].appendChild(document.createTextNode(cellText));
				}
			} else {
				this.cells[i].update(cellText);
			}

			if(this.cells[i].hasClassName("event")){
				this.cells[i].removeClassName("event");
			}
			if(this.cells[i].hasClassName("today")){
				this.cells[i].removeClassName("today");
			}
			this.cells[i].stopObserving();

/*
			// handle keyboard focus for the link inside of the cell
			Event.observe(
				this.cells[i].firstChild,
				'focus',
				(function(cellIndex){
					return function(ev){
						if(
							typeof(this.cellOverlayHandlers)=='object' 
							&& this.cellOverlayHandlers!=null 
							&& this.cellOverlayHandlers.length > 0 
							&& typeof(this.cellOverlayHandlers[cellIndex])=='function'
						){
							this.cellOverlayHandlers[cellIndex](ev);
						}
					}.bind(this)
				}.bind(this))(i)
			);

			// warning: Calling a Prototypical function here out of laziness -- Dependence on FloatingInfo class internals
			Event.observe(
				this.cells[i].firstChild,
				'blur',
				(function(cellIndex){
					return function(ev){
						FloatingInfo.prototype.hideOverlay();
						ev.stop();
					}.bind(this);
				}.bind(this))(i)
			);
*/

		}
	
		// Style "today's" cell properly, (if we're displaying the current month)
		if(this.month == this.today.getMonth() && this.year == this.today.getFullYear()){
			var thisDay = this.cells[this.today.getDate() + (firstDay - 1)];
			thisDay.addClassName("today");
			var thisDayWrap = new Element("div", {"class":"this_day_wrap"}).insert(thisDay.innerHTML);
			thisDay.innerHTML = "";
			thisDay.insert(thisDayWrap);
			var thisDayHeight = Math.round(this.table.getHeight() / 6);
			thisDayWrap.setStyle({
				'height':(thisDayHeight - 2) + 'px',
				'lineHeight':(thisDayHeight - 2) + 'px'
			});
		}

		// fetch events for this month and convert into a handy array of hashes
		if(typeof(this.monthEventLists[this.month])=='undefined'){
			this.monthEventLists[this.month] = this.getEventListItems().select(function(listItem){
				var startDateElement = listItem.getElementsBySelector("abbr.dtstart")[0];
				return startDateElement && startDateElement.getAttribute("title").indexOf("-"+(this.month+1)+"-") > -1;
			}.bind(this)).collect(function(listItem){
				//
				// Parse hCalendar microformat
				//
				var linkElem = listItem.getElementsBySelector("a.url")[0];
				var startDateElem = listItem.getElementsBySelector("abbr.dtstart")[0];
				var endDateElem = listItem.getElementsBySelector("abbr.dtstart")[0];
				var summaryElem = listItem.getElementsBySelector("h3.summary")[0];
				var descriptionElem = listItem.getElementsBySelector("p.description")[0];
				var startDateAttrs = startDateElem?startDateElem.getAttribute("title").split("-"):null;
				var endDateAttrs = endDateElem?endDateElem.getAttribute("title").split("-"):null;
				return {
					'start_day':(startDateAttrs && startDateAttrs.length>=3)?parseInt(startDateAttrs[2]):undefined,
					'start_month':(startDateAttrs && startDateAttrs.length>=3)?parseInt(startDateAttrs[1]):undefined,
					'start_year':(startDateAttrs && startDateAttrs.length>=3)?parseInt(startDateAttrs[0]):undefined,
					'end_day':(endDateAttrs && endDateAttrs.length>=3)?parseInt(endDateAttrs[2]):undefined,
					'end_month':(endDateAttrs && endDateAttrs.length>=3)?parseInt(endDateAttrs[1]):undefined,
					'end_year':(endDateAttrs && endDateAttrs.length>=3)?parseInt(endDateAttrs[0]):undefined,
					'summary':summaryElem?summaryElem.innerHTML.strip():"",
					'description':descriptionElem?descriptionElem.innerHTML.strip():"",
					'url':linkElem?linkElem.getAttribute("href").strip():"#",
					'linktext':linkElem?linkElem.innerHTML.strip():""
				};
			}.bind(this));
		}

		if(this.monthEventLists[this.month].length > 0) {
			// assign overlay events for any day cells that contain events

			// cellOverlayHandlers is an array that holds all the current closures that pop up overlays to show events
			this.cellOverlayHandlers = [];
			for(var i = 0; i < this.cells.length;i++) {
				if(i >= (firstDay)){
					var cell = this.cells[i];
					var dayIndex = i - firstDay;
					var calendarDay = dayIndex + 1;
					// grab all the events for this given day
					var todaysEvents = this.monthEventLists[this.month].select(function(item){return item.start_day==calendarDay;});

					if(todaysEvents.length > 0){

						// overlay closure to float information about this event
						var todaysEventOverlay = function(cellElement, private_month, eventCollection) {
							return function (event) {
								var currentEventIndex = 0;
								new FloatingInfo(cellElement, {
									'float':'auto',
									'padding':0,
									'floatDirection':'auto',
									'arrowClass':'float_arrow',
									'overlayClasses':{
										'top':'floatType_calendar_top',
										'inner':'floatType_calendar_inner',
										'bottom':'floatType_calendar_bottom'
									},
									// this function is called *once* by FloatingInfo in order to allow for custom rendering in the calendar overlay
									'contentRender':function(contentElement_){
										// this render function is called by the buttons in the calendar overlay
										this.renderOverlayContents(contentElement_,eventCollection,currentEventIndex);
									}.bind(this)
								});
								Event.stop(event);
							}.bind(this);
						}.bind(this)($(cell), this.month, todaysEvents);

						// for keyboard handling: the text of the cell has to be a link so that IE can focus on it (accessibility/508)
						cell.update("<a href='#'>" + cellTexts[i] + "</a>");

						// handle keyboard focus for the link inside of the cell
						Event.observe(
							this.cells[i].firstChild,
							'focus',
							(function(cellIndex){
								return function(ev){
									if(
										typeof(this.cellOverlayHandlers)=='object' 
										&& this.cellOverlayHandlers!=null 
										&& this.cellOverlayHandlers.length > 0 
										&& typeof(this.cellOverlayHandlers[cellIndex])=='function'
									){
										this.cellOverlayHandlers[cellIndex](ev);
									}
								}.bind(this)
							}.bind(this))(i)
						);

						// warning: Calling a Prototypical function here out of laziness -- Dependence on FloatingInfo class internals
						Event.observe(
							this.cells[i].firstChild,
							'blur',
							(function(cellIndex){
								return function(ev){
									FloatingInfo.prototype.hideOverlay();
									ev.stop();
								}.bind(this);
							}.bind(this))(i)
						);

						cell.addClassName("event");
						var hoverObj = new mouseOverClassify();
						hoverObj.classify(cell, 'eventHover');

						// hover a floating info overlay for day cells which have events associated with them
						Event.observe(cell, "mouseover", todaysEventOverlay);
						this.cellOverlayHandlers[i] = todaysEventOverlay;

					} else {
						this.cellOverlayHandlers[i] = null;
					}
				}
			} // end overlay assignment loop

		}

		// style calendar as 6 rows, always
		this.rows[4].className = "";
		this.rows[5].className = "";
		this.rows[6].className = "row_last";
		this.table.className = "rows_6";
	},
	
	renderOverlayContents:function(contentElement,eventCollection,currentEventIndex){
		contentElement.innerHTML = "";
		var eventObject = eventCollection[currentEventIndex];
		var title = new Element("h3",{});
		title.update(eventObject.summary);
		var body = new Element("p",{});
		body.update(eventObject.description);
		var link = new Element("a",{'className':'event_url','href':eventObject.url});
		link.update("&rsaquo; "+eventObject.linktext);

		//
		// Back/forward buttons on calendar overlay
		// these are only drawn if we have more than one event on this day
		//
		if(eventCollection.length > 1){
			var eventNavContainer = new Element("div",{'className':'event_nav_container'});
			var locationLabel = new Element("div",{'className':'event_label'});
			var prevBtn = new Element("a",{'href':'#','className':'event_prev'});
			var nextBtn = new Element("a",{'href':'#','className':'event_next'});
			// locationLabel is the label that says, for example: "Event 1 of 5"
			locationLabel.update("Event "+(currentEventIndex+1)+" of "+eventCollection.length);
			prevBtn.update(" ");
			nextBtn.update(" ");
			var callRender = function(incrementor){
				currentEventIndex = currentEventIndex + incrementor;
				Event.stopObserving(prevBtn,'click',prevFunc);
				Event.stopObserving(nextBtn,'click',nextFunc);
				//
				// re-render the inner content region of the calendar overlay
				//
				this.renderOverlayContents(contentElement,eventCollection,currentEventIndex); 
			}.bind(this);
			//
			// forward / back click event handlers
			//
			var prevFunc = function(ev){
				if(currentEventIndex>0){
					callRender(-1);
				} else {
					currentEventIndex = eventCollection.length - 1;
					callRender(0);
				}
				ev.stop();
				return false;
			};
			var nextFunc = function(ev){
				if(currentEventIndex < eventCollection.length - 1){
					callRender(1);
				} else {
					currentEventIndex = 0;
					callRender(0);
				}
				ev.stop();
				return false;
			};
			Event.observe(nextBtn,'click',nextFunc);
			Event.observe(prevBtn,'click',prevFunc);

			eventNavContainer.appendChild(prevBtn);
			eventNavContainer.appendChild(locationLabel);
			eventNavContainer.appendChild(nextBtn);
			contentElement.appendChild(eventNavContainer);
		}
		contentElement.appendChild(title);
		contentElement.appendChild(body);
		contentElement.appendChild(link);
	},

	// fetch the list item elements representing events
	getEventListItems:function(){
		if(typeof(this.eventListCache)=='undefined'){
			this.eventListCache = this.container.getElementsBySelector("ul.eventlist li");
		}
		return this.eventListCache;
	},

	// navigate to newMonth and redraw the calendar situated in newMonth
	changeMonth: function(newMonth) {
		this.month = newMonth;
	
		if(this.month == -1) {
			this.month = 11;
			this.year--;
		} else if(this.month == 12) {
			this.month = 0;
			this.year++;
		}
	
		this.display_month.update(Calendar.prototype.monthNames[this.month]);
		this.display_year.update(this.year);
	
		this.populateTable();
	},

	monthNames: [
		"January", 
		"February", 
		"March", 
		"April", 
		"May", 
		"June", 
		"July", 
		"August", 
		"September", 
		"October", 
		"November", 
		"December"
	]
};

if(detectBrowser.modernBrowser()){
	Event.observe(window, 'load', function() { 
	 	$$('.NASACalendar').each(function(calendarElement){
			var c = new Calendar(calendarElement);
		});
	});
}