
var ko_calendar = function ()
{
	var result = {};

	function log(message)
	{
		// Firebug debugging console
		//console.log(message);
	}
	
	function buildDate( entry ){
		/* display the date/time */
		var dateString = 'All Day Event';
		var times = entry.getTimes();
		if ( times.length ){
			/* if the event has a date & time, override the default text */
			var startTime = times[0].getStartTime();
			var endTime = times[0].getEndTime();
			var startJSDate = startTime.getDate();
			var endJSDate = new Date(endTime.getDate());
			// If the start and end are dates (full day event)
			// then the end day is after the last day of the event (midnight that morning)
			var allDayEvent = false;
			if (startTime.isDateOnly() && endTime.isDateOnly()){
				endJSDate.setDate(endJSDate.getDate() - 1);
				if (endJSDate.getTime() == startJSDate.getTime()){
					// This is a one day event.
					allDayEvent = true;
				}
			}
			var oneDayEvent = false;
			var startDay = new Date(startJSDate.getFullYear(), startJSDate.getMonth(), startJSDate.getDate());
			var endDay = new Date(endJSDate.getFullYear(), endJSDate.getMonth(), endJSDate.getDate());
			if (startDay.getTime() == endDay.getTime()) oneDayEvent = true;
			if (allDayEvent){
				dateString = 'All Day Event';
			}else if (oneDayEvent){
				dateString = startJSDate.toString("h tt");
				dateString += ' - ';
				dateString += endJSDate.toString("h tt");
			}else{
				if (!startTime.isDateOnly()){
					dateString = startJSDate.toString("ddd, MMM d, yyyy h:mm tt");
				}else{
					dateString = startJSDate.toString("ddd, MMM d, yyyy");
				}
				dateString += ' - ';
				if (!endTime.isDateOnly()){
					dateString += endJSDate.toString("ddd, MMM d, yyyy h:mm tt");
				}else{
					dateString += endJSDate.toString("ddd, MMM d, yyyy");
				}
			}
		}
		var dateRow = document.createElement('div');
		dateRow.setAttribute('className','ko-calendar-entry-date-row');
		dateRow.setAttribute('class','ko-calendar-entry-date-row');
		dateDisplay = document.createElement('div');
		dateDisplay.innerHTML = dateString;
		dateDisplay.setAttribute('className','ko-calendar-entry-date-text');
		dateDisplay.setAttribute('class','ko-calendar-entry-date-text');
		dateRow.appendChild(dateDisplay);
		return dateRow;
	}

	function buildLocation(entry){
		var locationDiv = document.createElement('div');
		var locationString = entry.getLocations()[0].getValueString();
		if (locationString != null){
			locationDiv.appendChild(document.createTextNode(locationString));
			locationDiv.setAttribute('className','ko-calendar-entry-location-text');
			locationDiv.setAttribute('class','ko-calendar-entry-location-text');
		}	
		return locationDiv;
	}

	/**
	 * Show or hide the calendar entry (as a <div> child of item) when the item is clicked.
	 * Initially this will show a div containing the content text.
	 * This could collect other information such as start/stop time
	 * and location and include it in the node.
	 *
	 * @param {div} HTML element into which we will add and remove the calendar entry details.
	 * @param {calendar entry} Google Calendar entry from which we will get the details.
	 */
	koCalendarStorage = {
	    activeItem: null,
	    activeDesc: null
	};
	function onDateClicked(item, entry){

		var entryDesc = entry.getContent().getText();
		if (entryDesc == null) return function() {}
		var descDiv = null;

		return function (){
		                
            if( koCalendarStorage.activeItem == item ){

                item.removeChild( koCalendarStorage.activeDesc );
                koCalendarStorage.activeItem = null;
                koCalendarStorage.activeDesc = null;

            }else{

                if( koCalendarStorage.activeItem ){
                    koCalendarStorage.activeItem.removeChild( koCalendarStorage.activeDesc );                    
                }

                descDiv = document.createElement('div');
                bodyDiv = document.createElement('div');
                bodyDiv.setAttribute('className','ko-calendar-entry-body');
                bodyDiv.setAttribute('class','ko-calendar-entry-body');
                bodyDiv.innerHTML = Wiky.toHtml(entryDesc);
                descDiv.appendChild(bodyDiv);
                descDiv.appendChild(buildLocation(entry));
                item.appendChild(descDiv);

                koCalendarStorage.activeItem = item;
                koCalendarStorage.activeDesc = descDiv;

            }
		}
	}

	/**
	 * Callback function for the Google data JS client library to call with a feed 
	 * of events retrieved.
	 *
	 * Creates an unordered list of events in a human-readable form.  This list of
	 * events is added into a div with the id of 'outputId'.  The title for the calendar is
	 * placed in a div with the id of 'titleId'.
	 *
	 * @param {json} feedRoot is the root of the feed, containing all entries 
	 */
	function createListEvents(titleId, outputId, maxResults, autoExpand, googleService, urls){
		function mergeFeeds(resultArray){
			// This function merges the input arrays of feeds into one single feed array.
			// It is assumed that each feed is sorted by date.  We find the earliest item in
			// the lists by comparing the items at the start of each array.
			// Store all of the feed arrays in an an array so we can "shift" items off the list.
			var entries = new Array();
			for (var i=0; i < resultArray.length; i++){
				if (resultArray[i]){
					log("Feed " + i + " has " + resultArray[i].feed.getEntries().length + " entries");
					entries.push(resultArray[i].feed.getEntries());
				}
			}
			log("Merging " + entries.length + " feeds into " + maxResults + " results.");
			// Now look at the first element in each feed to figure out which one is first.
			// Insert them in the output in chronological order.
			var output = new Array();
			while(output.length < maxResults){
				var firstStartTime = null;
				var firstStartIndex = null;
				for (var i=0; i < entries.length; i++){
					var data = entries[i][0];
					if (data != null){
						var times = data.getTimes();
						if (times.length > 0){
							var startDateTime = times[0].getStartTime().getDate();
							if (firstStartTime == null || startDateTime < firstStartTime){
								//log( startDateTime + " from feed " + i + " is before " + firstStartTime + " from feed " + firstStartIndex);
								firstStartTime = startDateTime;
								firstStartIndex = i;
							}
						}
					}
				}
				if (firstStartTime != null){
					// Add the entry to the output and shift it off the input.
					log("Pushing " + startDateTime);
					output.push(entries[firstStartIndex].shift());
				}else{
					// No new items were found, so we must have run out.
					break;
				}
			}
			return output;
		}

		function processFinalFeed(feedRoot) {
			// var entries = feedRoot.feed.getEntries();
			var entries = feedRoot;
			var eventDiv = document.getElementById(outputId);
			if (eventDiv.childNodes.length > 0) {
				eventDiv.removeChild(eventDiv.childNodes[0]);
			}	  
			/* set the ko-calendar-title div with the name of the calendar */
			//document.getElementById(titleId).innerHTML = feedRoot.feed.title.$t;
			/* loop through each event in the feed */
			var prevDateString = null;
			var eventList = null;
			var len = entries.length;
			for (var i = 0; i < len; i++){
				var entry = entries[i];
				var title = entry.getTitle().getText();
				var startDateTime = null;
				var startJSDate = null;
				var times = entry.getTimes();
				if (times.length > 0) {
					startDateTime = times[0].getStartTime();
					startJSDate = startDateTime.getDate();
				}
				var entryLinkHref = null;
				if (entry.getHtmlLink() != null) {
					entryLinkHref = entry.getHtmlLink().getHref();
				}
				dateString = startJSDate.toString('dddd MMMM d');
				if (dateString != prevDateString) {
					// Append the previous list of events to the widget
					if (eventList != null) eventDiv.appendChild(eventList);
					// Create a date div element
					var dateDiv = document.createElement('div');
					dateDiv.setAttribute('className','ko-calendar-date');
					dateDiv.setAttribute('class','ko-calendar-date');
					dateDiv.appendChild(document.createTextNode(dateString));
					// Add the date to the calendar
					eventDiv.appendChild(dateDiv);
					// Create an div to add each agenda item
					eventList = document.createElement('div');
					eventList.setAttribute('className','ko-calendar-event-list');
					eventList.setAttribute('class','ko-calendar-event-list');
					prevDateString = dateString;
				}
				var li = document.createElement('div');
				// Add the title as the first thing in the list item
				// Make it an anchor so that we can set an onclick handler and
				// make it look like a clickable link
				var entryTitle = document.createElement( "div" );				
				var entryTime = buildDate( entry );
				var entryTitleAnchor = document.createElement('a');
				entryTitleAnchor.setAttribute('className','ko-calendar-entry-title');
				entryTitleAnchor.setAttribute('class','ko-calendar-entry-title');
				entryTitleAnchor.setAttribute('href', "javascript:;");
				entryTitleAnchor.appendChild(document.createTextNode(title));

				entryTitle.appendChild( entryTime );
				entryTitle.appendChild( entryTitleAnchor ); 

				// Show and hide the entry text when the entryTitleDiv is clicked.
				li.appendChild( entryTitle );
				
				entryTitle.onclick = onDateClicked( li, entry );
				if ( autoExpand ) entryTitle.onclick();
				eventList.appendChild(li);
			}
			if (eventList != null) eventDiv.appendChild(eventList);
		}
		// Keep a list of all of the queries to be sorted later.
		var sQueries = new Array();
		// Store the list of urls which we will be iterating through.
		var sUrls = urls;
		function callback(feedRoot){
			// If the feed is not invalid then push it into a list.
			if (feedRoot) sQueries.push(feedRoot);
			var url = '';
			// Skip blank urls.
			do {
                url = sUrls.pop();
			} while (url == '');
			if ( url != undefined ){
				var query = new google.gdata.calendar.CalendarEventQuery(url);
				query.setOrderBy('starttime');
				query.setSortOrder('ascending');
				query.setFutureEvents(true);
				query.setSingleEvents(true);
				query.setMaxResults(maxResults);
				googleService.getEventsFeed(query, callback, handleGDError);
			}else{
				// We are done.
				// Merge the events in sQueries and apply them.				
				// For now we just insert them individually.
				// for (var i=0; i < sQueries.length; i++)
				// {
					// if (sQueries[i])
					// {
						// processFinalFeed(sQueries[i]);
					// }
				// }
				var finalFeed = mergeFeeds(sQueries);
				processFinalFeed(finalFeed);
			}
		}
		return callback;
	}

	/**
	 * Callback function for the Google data JS client library to call when an error
	 * occurs during the retrieval of the feed.  Details available depend partly
	 * on the web browser, but this shows a few basic examples. In the case of
	 * a privileged environment using ClientLogin authentication, there may also
	 * be an e.type attribute in some cases.
	 *
	 * @param {Error} e is an instance of an Error 
	 */
	function handleGDError(e) {
		
		// For production code, just ignore the error
		// Remove the return below for testing.
		return;
	
		//document.getElementById('jsSourceFinal').setAttribute('style', 'display:none');
		if (e instanceof Error) {
			/* alert with the error line number, file and message */
			alert('Error at line ' + e.lineNumber + ' in ' + e.fileName + '\n' + 'Message: ' + e.message);
			/* if available, output HTTP error code and status text */
			if (e.cause) {
				var status = e.cause.status;
				var statusText = e.cause.statusText;
				alert('Root cause: HTTP error ' + status + ' with status text of: ' + statusText);
			}
		} else {
			alert(e.toString());
		}
	}

	/**
	 * Uses Google data JS client library to retrieve a calendar feed from the specified
	 * URL.  The feed is controlled by several query parameters and a callback 
	 * function is called to process the feed results.
	 *
	 * @param {string} titleId is the id of the element in which the title could be written.
	 * @param {string} outputId is the id of the element in which the output is to be written.
	 * @param {string} calendarUrl is the URL for a public calendar feed
	 * @param {string} calendarUrl2 is the URL for a second public calendar feed
	 * @param {number} maxResults is the maximum number of results to be written to the output element.
	 */  
	function loadCalendar(titleId, outputId, maxResults, autoExpand, calendars)
	{
		// Uncomment the following two lines for offline testing.
		//ko_calendar_test.testCalendar();
		//return;

		var service = new google.gdata.calendar.CalendarService('google-calendar-widget');
		var requestFunc = createListEvents(titleId, outputId, maxResults, autoExpand, service, calendars);

		// Calling the created callback with no parameters will start the process of downloading
		// the set of calendars pushed in with calendar.
		requestFunc();
	}

	result.loadCalendarDefered = function(titleId, outputId, maxResults, autoExpand, calendarUrl, calendarUrl2, calendarUrl3)
	{
		var calendars = new Array();
		calendars.push(calendarUrl);
		calendars.push(calendarUrl2);
		calendars.push(calendarUrl3);

		google.setOnLoadCallback(function() { loadCalendar(titleId, outputId, maxResults, autoExpand, calendars); });
	}
	
	result.init = function()
	{
		// init the Google data JS client library with an error handler
		google.gdata.client.init(handleGDError);
	}
	
	return result;

} ();

google.load("gdata", "2.x");
google.setOnLoadCallback(ko_calendar.init);
