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 = 'Intera giornata';
			}
			else if (oneDayEvent)
			{
				dateString = startJSDate.toString("ddd d MMM");
				dateString += ', ';
				dateString += startJSDate.toString("h:mm tt");
				dateString += ' - ';
				dateString += endJSDate.toString("h:mm 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');

		/*dateLabel = document.createElement('div');
		dateLabel.appendChild(document.createTextNode('When: '));
		dateLabel.setAttribute('className','ko-calendar-entry-date-label');
		dateLabel.setAttribute('class','ko-calendar-entry-date-label');
		dateRow.appendChild(dateLabel);
		*/

		dateDisplay = document.createElement('div');
		//dateDisplay.appendChild(document.createTextNode(dateString));
		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.
	 */
	function createClickHandler(item, entry)
	{
		var entryDesc = entry.getContent().getText();
		if (entryDesc == null)
		{
			return function() {}
		}

		var descDiv = null;
		return function () 
		{
			if (descDiv == null)
			{
				descDiv = document.createElement('div');
				
				descDiv.appendChild(buildDate(entry));
				descDiv.appendChild(buildLocation(entry));
				
				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);

				item.appendChild(descDiv);
			}
			else
			{
				// Hide all the children of this node (which should be text we added above)
				item.removeChild(descDiv);
				descDiv = null;
			}
		}
	}

	/**
	 * 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 dd MMM yyyy');

				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');
				
				/* if we have a link to the event, create an 'a' element */
				/*
				if (entryLinkHref != null) {
					entryLink = document.createElement('a');
					entryLink.setAttribute('href', entryLinkHref);
					entryLink.appendChild(document.createTextNode(title));
					li.appendChild(entryLink);
					//li.appendChild(document.createTextNode(' - ' + dateString));
				}
				else
				*/
				{

					// 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('a');
					entryTitle.setAttribute('className','ko-calendar-entry-title');
					entryTitle.setAttribute('class','ko-calendar-entry-title');
					entryTitle.setAttribute('href', "javascript:;");
					entryTitle.appendChild(document.createTextNode(title));

					// Show and hide the entry text when the entryTitleDiv is clicked.
					entryTitle.onclick = createClickHandler(li, entry);

					li.appendChild(entryTitle);

					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);
