/* Copyright 2012 Virtual Interconnect Software, LLC.  All rights reserved. */
function FXCalendarStackEvents()
{
	//when called, this function stacks the events within a day on a calendar so they fit within a minimal number of columns
	//the summary and start/end are all read using the embedded hCalendar microformat for the event
	
	//getElementsByClassName is used quite extensively since we're processing a microformat, so make sure we have a consistent function to use
	var getElementsByClassName = function(el, classNames) {
				if(el.getElementsByClassName)
				{
					return el.getElementsByClassName(classNames);
				}
				else
				{
					var names = classNames.split(" ");
					var allEls = el.getElementsByTagName("*");
					var outEls = [];
					for(var e = 0; e < allEls.length; ++e)
					{
						if(allEls[e].className)
						{
							var noMatch = false;
							for(var c = 0; c < names.length && !noMatch; ++c)
							{
								if((" " + allEls[e].className + " ").indexOf(" " + names[c] + " ") <= 0)
								{
									noMatch = true;
								}
							}
							if(!noMatch)
							{
								outEls.push(allEls[e]);
							}
						}
					}
					return outEls;
				}
			};
	var parse8601Date = function(dateStr) {
				//parse an ISO-8601 date into a JS Date object
				var d = new Date();
				d.setUTCFullYear(dateStr.substring(0, 4));
				d.setUTCMonth(dateStr.substring(5, 7) - 1);
				d.setUTCDate(dateStr.substring(8, 10));
				d.setUTCHours(dateStr.substring(11, 13));
				d.setUTCMinutes(dateStr.substring(14, 16));
				d.setUTCSeconds(dateStr.substring(17, 19));
				var timestampWithoutOffset = d.getTime();
				var offset = dateStr.substring(19, dateStr.length);
				var offsetSeconds = (offset.length == 6 ? (offset.charAt(0) == "-" ? -1 : 1) * ((offset.substring(1, 3) * 60 * 60 *1000) + (offset.substring(4, 6) * 60 * 1000)) : 0);
				var dateWithOffset = new Date(timestampWithoutOffset - offsetSeconds);
				return dateWithOffset;
			};
	var getPercentWithinRange = function(dateObj, startHour, endHour) {
				return ((dateObj.getHours() + (dateObj.getMinutes() / 60)) - startHour) / (endHour - startHour);
			};
	
	//first, locate all calendars that we can apply this effect to
	//we can only apply this to calendars that have embedded hCalendar microformat data
	var calendars = getElementsByClassName(document, "CalendarMonth vcalendar");
	
	//parse out all of the events within this class so we can easily organize them
	for(var c = 0; c < calendars.length; ++c)
	{
		//we're not reorganizing events into different days, just reorganizing events within days, so we need to do this on a day-by-day level
		var days = getElementsByClassName(calendars[c], "CalendarMonthDay");
		for(var d = 0; d < days.length; ++d)
		{
			//find all events within the day and parse them so we can compare on a numeric basis
			var eventObjs = [];
			var events = getElementsByClassName(days[d], "vevent");
			for(var e = 0; e < events.length; ++e)
			{
				var summaryEl = getElementsByClassName(events[e], "summary")[0];
				var dtStartEl = getElementsByClassName(events[e], "dtstart")[0];
				var dtEndEl = getElementsByClassName(events[e], "dtend")[0];
				var start = parse8601Date(dtStartEl.textContent);
				var end = parse8601Date(dtEndEl.textContent);
				
				eventObjs.push({el: events[e], summary: summaryEl.textContent, dtstart: start, dtstartTimestamp: start.getTime(), dtend: end, dtendTimestamp: end.getTime()});
			}
			if(eventObjs.length > 0)
			{
				//we were able to find some events
				//figure out the time range to display
				var dayStart = 9;
				var dayEnd = 17;
				for(var e = 0; e < eventObjs.length; ++e)
				{
					//figure out the start and end hours
					dayStart = Math.min(dayStart, eventObjs[e].dtstart.getHours());
					dayEnd = Math.max(dayEnd, eventObjs[e].dtend.getHours() + (eventObjs[e].dtend.getMinutes() > 0 ? 1 : 0));
				}
				
				//adjust the end times so they're at least as large as one line of text
				//dtend is NOT to be used for accuracy - it's purely for display purposes
				//as such, we may need to adjust it to make sure at least one line of text is always visible
				for(var e = 0; e < eventObjs.length; ++e)
				{
					eventObjs[e].dtend = new Date(Math.max(eventObjs[e].dtend.getTime(), eventObjs[e].dtstart.getTime() + (14 / eventObjs[e].el.parentNode.clientHeight) * (dayEnd - dayStart) * 60 * 60 * 1000));
					eventObjs[e].dtendTimestamp = eventObjs[e].dtend.getTime();
				}
				
				//now that we know minimum sizes for events, recalculate the time range
				for(var e = 0; e < eventObjs.length; ++e)
				{
					//figure out the start and end hours
					dayStart = Math.min(dayStart, eventObjs[e].dtstart.getHours());
					dayEnd = Math.max(dayEnd, eventObjs[e].dtend.getHours() + (eventObjs[e].dtend.getMinutes() > 0 ? 1 : 0));
				}
				
				//calculate start and end timestamps for the day
				var dayNumEl = getElementsByClassName(days[d], "CalendarMonthDayNum")[0];
				var dateStr = dayNumEl.getAttribute("title");
				var dayStartDate = new Date(dateStr);
				dayStartDate.setHours(dayStart);
				var dayEndDate = new Date(dateStr);
				dayEndDate.setHours(dayEnd);
				
				//first, sort them by duration, longest first
				eventObjs.sort(function(a, b) {
							var aLen = (a.dtendTimestamp - a.dtstartTimestamp);
							var bLen = (b.dtendTimestamp - b.dtstartTimestamp);
							return (aLen == bLen ? 0 : (aLen > bLen ? -1 : 1));
						});
				
				//now, start sorting them into non-overlapping columns
				var columns = [];
				while(eventObjs.length > 0)
				{
					var x = 0;
					for(x = 0; x < columns.length; ++x)
					{
						//try to fit the event into this column
						var overlaps = false;
						for(var y = 0; y < columns[x].length && !overlaps; ++y)
						{
							if(eventObjs[0].dtstartTimestamp < columns[x][y].dtendTimestamp && eventObjs[0].dtendTimestamp > columns[x][y].dtstartTimestamp)
							{
								overlaps = true;
							}
						}
						if(!overlaps)
						{
							break;
						}
					}
					if(!columns[x])
					{
						columns[x] = [];
					}
					
					//add it to the column
					columns[x].push(eventObjs.shift());
				}
				
				//build the columns with our new list of non-overalapping events
				for(var x = 0; x < columns.length; ++x)
				{
					for(var y = 0; y < columns[x].length; ++y)
					{
						columns[x][y].el.style.position = "absolute";
						columns[x][y].el.style.left = (x / columns.length * 102) + "%";
						columns[x][y].el.style.width = (1 / columns.length * 95) + "%";
						var start = (columns[x][y].dtstart >= dayStartDate ? getPercentWithinRange(columns[x][y].dtstart, dayStart, dayEnd) : 0);
						var end = (columns[x][y].dtend <= dayEndDate ? getPercentWithinRange(columns[x][y].dtend, dayStart, dayEnd) : 1);
						columns[x][y].el.style.top = (start * 98) + "%";
						columns[x][y].el.style.height = ((end - start) * 98) + "%";
						columns[x][y].el.firstChild.style.borderRadius = "3px";
						columns[x][y].el.firstChild.style.MozBorderRadius = "3px";
						columns[x][y].el.firstChild.style.overflow = "hidden";
					}
				}
			}
		}
	}
}

RegisterInit("FXCalendarStackEvents();");

