/*
 * jQuery hcal plugin
 * @requires jQuery v1.1 or later
 *
 * Examples at: [need an example...]
 * Dual licensed under the MIT and GPL licenses:
 *   http://www.opensource.org/licenses/mit-license.php
 *   http://www.gnu.org/licenses/gpl.html
 *
 * Based on 'jQuery hcal parser' by Tim Baxter
 * http://thebitterpill.com/07-2007/a-jquery-hcal-parser/
 *
 * 'jQuery hcal parser' was originally based on 'js-hcalendar' by dglasser
 * http://code.google.com/p/js-hcalendar/
 *
 * Revision: $Id$
 * Version: 1.0.0  Dec-01-2009
 */
 
(function($) {
	/**
	 * hcal provides a mechanism for displaying an HTML calendar associated with hcal events.
	 *
	 */
	
	// jQuery plugin
	$.fn.hcal = function (opts) {
		return this.each(function() {
			var conf = $.extend({},opts);
			if(hcal_component.inst && hcal_component.inst[$(this).attr('id')]){ 
				hcal_component.inst[$(this).attr('id')].destroy();
			}
			if(conf !== false){
				new hcal_component().init(this, conf);
			}
		});
	};
	
	
	// core
	function hcal_component() {
		return {
			
			// CONFIG/ GLOBALS 
			options : {
				dayNames : ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"],
				dayAbbrevs : ["S", "M", "T", "W", "T", "F", "S"],
				monthNames : ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"],
				monthLength : [31,28,31,30,31,30,31,31,30,31,30,31],
				
				firstDayOfWeek : 0,
				DEBUG : 1,
				globalDateHash : Object()
			},
			
			// INITIALIZATION
			init : function(elem, conf) {
				this.container = $(elem);
				if(this.container.size == 0) { alert("Invalid container node!"); return }
					
				this.refresh();
			},
			
			debug: function (message) {
				if (this.options.DEBUG){
					alert(message);
				}
			},
			
			/*********************************************************************
			**	First: check for abilities and readiness 
			**	Second: find events (.vevent), process and add to globalDateHash
			**	Third: make month tables and append to page 
			**********************************************************************/
			refresh: function () {
				var _this = this;
				
				if (!(document.getElementById && document.createElement)) {
					this.debug("No DOM!");
					return;
				}
				
				//Parse hcal Events
				$('.vevent').each(function() {
					var eventHash = {};
					var startDateObj;
					var startDateInfo;
					var endDateObj;
					var endDateInfo;
					
					eventHash.summary = $(this).find(".summary, :first").html();
					eventHash.description = $(this).find(".description").html();
					eventHash.time = $(this).find(".time, :first").html();
					
					startDateObj = $(this).find(".dtstart");
					endDateObj = $(this).find(".dtend");
					//_this.debug( JSON.stringify( endDateObj ) );
					
					startDateInfo = _this.parseDT( startDateObj ); 
					if (endDateObj.length > 0) {
						endDateInfo = _this.parseDT( endDateObj );
					}
					
					if (endDateInfo == null) {
						endDateInfo = [[startDateInfo[0]],[startDateInfo[1]],[startDateInfo[2]]];
					}
					
					var startDate = startDateInfo[2];
					var endDate = endDateInfo[2];
				
					// handle events that span months:
					// If event goes into next month, grab end of this month and add event until then.
					// If event starts in previous month, grab start of month and add event till end date.
					if ((startDateInfo[0] < endDateInfo[0]) || (endDateInfo[0] > endDateInfo[0]) || (startDateInfo[1] < endDateInfo[1]) || (endDateInfo[1] > startDateInfo[1])) {
						endDate = _this.getMonthLength(startDateInfo[0], startDateInfo[1]);
						_this.addEvent(eventHash, startDateInfo, startDate, endDate);
						
						startDate = 1;
						endDate = parseInt(endDateInfo[2]);
						_this.addEvent(eventHash, endDateInfo, startDate, endDate);
					} 
					//For events that are within a single month, just populate the event days normally.
					else {
						_this.addEvent(eventHash, startDateInfo, startDate, endDate);
					}
					return;  
				});
								
				//Create Calendar HTML / Table
				for (year in this.options.globalDateHash) {
					for (month in this.options.globalDateHash[year]) {
						var mt = this.makeMonthTable(this.options.globalDateHash[year][month], year, month);
						$( this.container ).append(mt);
					}
				}
			},
			
			
			// Trims the starting zero off of a number to ensure JS gets a regular int, not an octal 
			trimStartingZero: function (number) {
				number = (number + '');
				if (number.substring(0,1) == "0") {
					number = number.substring(1,number.length)
				}
				return parseInt(number, 10);
			},
			
			
			// parses datetime 
			parseDT: function (dt) {
				var dtText;
				var result;
				//this.debug( JSON.stringify( $(dt).attr('title') ) );
				
				if ($(dt).attr('title')) {
					dtText = $(dt).attr('title');
				} else {
					dtText = $(dt).html();
				}
				
				if (dtText != null) {
					//Convert time from long format: '2009-11-24T08:00-08:00'
					if (dtText.indexOf('T') > -1) {
						dtText = dtText.substring(0, dtText.indexOf('T'));
						dtText = dtText.replace( new RegExp( "-", "gi" ), "" );
					}
					
					result = dtText.match( /^(\d{4})(\d{2})(\d{2})/ );
					
					//this.debug( result );
				}
				
				if (result == null) {
					this.debug("didn't recognize DT: " + dtText );
					//this.debug("didn't recognize DT: " + dtText +' '+ JSON.stringify( $(dt) ) );
					//this.debug("didn't recognize DT: " +  JSON.stringify( $(dt).attr('title') ) );
					return
				}
				
				return [result[1], result[2], result[3]];
			},
			
			
			getMonthLength: function(year, month) {
				//Add extra day in Feb during leap year
				if(month == 2  &&  this.is_leap_year(year)){
					return this.options.monthLength[month - 1] + 1;
				}else{
					return this.options.monthLength[month - 1];
				}
			},
			
			
			is_leap_year: function (year) {
				if (year % 100 == 0) {
					if (year % 400 == 0) return true;
				} else {
					if ((year % 4) == 0) return true;
				}
				return false;
			},	
			
			
			/**
			 * Adds EventHash Object representing Event Data on a specific Day to the GlobalDateHash Object. 
			 * If the startDateDay and endDateDay are not equal, the event will be added to all Days in between.
			 */
			addEvent: function (eventHash, dateInfo, startDate, endDate) {
				// ParseDT returns an Array with three parts:
				// First [0] is the Year, Second [1] is the Month, Third [2] is the Day. 
				//  If the startDate is not equal to endDate, it's a multi-day event and we must loop through 
				// and add the event for each day in the span globalDateHash.
				for (var i = this.trimStartingZero(startDate); i <= this.trimStartingZero(endDate); i++) {
					// If the Year doesn't exist in the globalDateHash, add Object for it.
					if (this.options.globalDateHash[ dateInfo[0] ] == null) {
						this.options.globalDateHash[ dateInfo[0] ] = new Object();
					}
					// If the Month doesn't exist in the globalDateHash, add Object to represent it.
					if (this.options.globalDateHash[ dateInfo[0] ][ dateInfo[1] ] == null) {
						this.options.globalDateHash[ dateInfo[0] ][ dateInfo[1] ] = new Object();
					}
					// If the Day doesn't exist in the globaDateHash, add Array to store Events on this day.
					if (this.options.globalDateHash[ dateInfo[0] ][ dateInfo[1] ][ i ] == null) {
						this.options.globalDateHash[ dateInfo[0] ][ dateInfo[1] ][ i ] = new Array();
					}
					// Add this Event to the Array List of Events for this Day.
					this.options.globalDateHash[ dateInfo[0] ][ dateInfo[1] ][ i ].push(eventHash);
				}
			},
			
			//Create Table of the specified Month for the specified Year, with events. 
			makeMonthTable: function (monthHash, year, month) {
				
				currentMonthLength = this.getMonthLength(year, month);
				previousMonthLength = this.getMonthLength(year, month-1);
				
				var today = new Date;
				var todayYear = today.getFullYear();
				var todayMonth = today.getMonth() + 1;
				var todayDay = today.getDate();    
				var days = new Array(currentMonthLength+1); // We are going to index this array starting at 1.  Because I said so.
				
				var monthIndex = this.trimStartingZero(month)-1;
				//if(monthIndex == 0){  monthIndex = this.options.monthLength.length; }
				
				var monthName = this.options.monthNames[ monthIndex ];
				var dayName = '';
				
				// Create an HTML Table to Represent the Month and set necessary Attributes.
				var monthTable =  document.createElement('table');
				$(monthTable).addClass('calTable');
				/*
				//Calendar Name: (Month + Year)
				var caption = document.createElement('caption')
				$(monthTable).append( $(caption).addClass('calHeader').attr("align", 'top') );
				$(caption).html( '<span class="i18n-month-'+ (monthIndex+1) +'">'+ monthName + "</span> " + year );
				*/
				
				// Create thead with rows for month/year, full day names and abbreviated day names
				var thead = document.createElement("thead");
				$(monthTable).append(thead);
				
				var titleRow = document.createElement('tr');
				$(thead).append(titleRow)
				
				//Calendar Name: (Month + Year)
				var titleTH = document.createElement('th')
				$(titleRow).append( $(titleTH).addClass('calHeader').attr("colspan", 7).attr("colSpan", 7) );
				$(titleTH).html( '<span class="i18n-month-'+ (monthIndex+1) +'">'+ monthName + "</span> " + year );
				
				//Days of the week (full)
				var headerRow = document.createElement('tr');
				for (var i = 0; i < this.options.dayNames.length; i++) {
					dayName = this.options.dayNames[i];
					$(headerRow).html($(headerRow).html() + '<th scope="col" class="i18n-weekday-'+ (i+1) +'">'+ dayName +'</th>');
				}
				$(thead).append($(headerRow).addClass('longDays'));
				
				//Days of the week (abbreviated)
				var abbrevHeaderRow = document.createElement('tr');
				for (var i = 0; i < this.options.dayAbbrevs.length; i++) {
					dayName = this.options.dayAbbrevs[i]
					$(abbrevHeaderRow).html($(abbrevHeaderRow).html() + '<th scope="col" class="i18n-weekday-abbr-'+ (i+1) +'">'+ dayName +'</th>');
				}
				$(thead).append($(abbrevHeaderRow).addClass('abbrevDays'));
					
				// create tbody and fill with days
				var tbody = document.createElement("tbody");
				$(monthTable).append(tbody);
				
				for (var i = 1; i <= currentMonthLength; i++) {
					days[i] = document.createElement('td');
					if (todayYear == year && todayMonth == month && todayDay == i) {
						days[i].className += ' calDayToday';
					}
					$(days[i]).append('<div class="calDayLabel">' + i + '</div>');
				}
				  
				// populate days here
				for (var day in monthHash) {
					var eventString = '';
					for (var i = 0; i < monthHash[day].length; i++) {
						dayTD = days[day-0];
						$(dayTD).addClass('calEventDay');
						//eventString += '<p><strong>' + monthHash[day][i].summary + '</strong> ';
						eventString += '<p><span>' + monthHash[day][i].summary + '</span> ';
						if (monthHash[day][i].description) {
							eventString += monthHash[day][i].description;
						}
						eventString += '</p>';
						$(dayTD).append('<div class="event">' + eventString + '</div>');
					}
				}
				
				var dateToCheck = new Date();
				dateToCheck.setYear(year);
				dateToCheck.setDate(1);
				dateToCheck.setMonth(month-1);
				var dayOfFirstOfMonth = dateToCheck.getDay();
				
				var row = tbody.appendChild(document.createElement("tr"));
				
				//If looking to previous month in previous year, set to December
				//if(previousMonthLength === undefined){
				if(month == 1){
					previousMonthLength = this.options.monthLength[ (this.options.monthLength.length - 1) ];
				}
				
				// Loop through empty Days before first Day of Month and fill with 'outOfRange' Cells.
				for (var i = 0; i < dayOfFirstOfMonth; i++) {
					$(row).append('<td class="outOfRange"><div class="calDayLabel"> ' + (previousMonthLength - (dayOfFirstOfMonth - i-1)) + '</div></td>');
				}
				  
				for (var i = 1; i <= currentMonthLength; i++) {
					if (row.childNodes.length == 7) {
						row = tbody.appendChild(document.createElement("tr"));
					}
					if(row.childNodes.length == 0 ||row.childNodes.length == 6){
						$(days[i]).addClass('weekend');
					}	
					row.appendChild(days[i]);
				}
				
				// fill in next month's days to end
				newmonth = 1;
				while (row.childNodes.length < 7) {
					$(row).append('<td class="outOfRange"><div class="calDayLabel">' + newmonth + '</div></td>');
					newmonth++;
				}
				return monthTable;
			}
		}
	};
})(jQuery);
