﻿Namespace("vc.Calendar.Views");

vc.Calendar.Views.View = new Class(function() {
	// References/Imports
	var DateTime = vc.DateTime;

	// Private Fields
	var calElement = null;
	var config = {};
	var today,
		periodStart,
		periodDuration,
		periodEnd,
		startOfWeek,
		dayVisibilityMask,
		showDaysOfWeekHeader,
		showDaysOfWeekFooter,
		showDatesOfOverlappingMonths,
		headerFooterDayOfWeekFormat,
		calRows,
		calCols,
		thead,
		tbody,

		dayOfWeek,
		endDayOfWeek,
		daysInMonth,
		viewStartDateOffset,
		viewEndDateOffset,
		firstDayOfView,
		lastDayOfView,

		calGrid,
		calGridTds,
		viewPeriodStartTdIndex,
		viewPeriodEndTdIndex,

		calEventList,
	//calEventListItems,
		eventListPeriodDays,
		eventListPeriodEnd;

	// Private Methods
	function render() {
		var html = "";

		html += renderViewHeader();
		html += renderViewBody();
		html += renderEventListContainer();

		return html;
	}

	function renderViewHeader() {
		var html = "",
			monthName = config.monthNames[periodStart.getMonth()];

		html += "<div class=\"vc-Calendar-Header\">";
		html += "<div class=\"vc-Calendar-Header-Left\">" + "<span class=\"vc-Calendar-Button-PreviousPeriod\">&lt;&lt;</span>" + "</div>";
		html += "<div class=\"vc-Calendar-Header-Center\">" + monthName + " " + periodStart.getFullYear() + "</div>";
		html += "<div class=\"vc-Calendar-Header-Right\">" + "<span class=\"vc-Calendar-Button-NextPeriod\">&gt;&gt;</span>" + "</div>";
		html += "</div>";

		return html;
	}

	function renderViewBody() {
		var s = "<table cellspacing=\"0\">",
			i;

		if (!tbody) { // first render, build all elements
			if (showDaysOfWeekHeader)
				s += renderDaysOfWeekHeader();

			s += renderDateRows();
			s += "</table>";
		}
		return s;
	}

	function renderDaysOfWeekHeader() {
		var html = "";

		html += "<thead>";
		html += renderDaysOfWeekRow();
		html += "</thead>";

		return html;
	}

	function renderDaysOfWeekRow() {
		var i,
			html = "";

		html += "<tr>";

		// render days of week header
		var dayOfWeek = startOfWeek;
		for (i = 0; i < 7; i++, dayOfWeek++) {
			if (dayOfWeek > 6)
				dayOfWeek = 0;

			if (!dayVisibilityMask[dayOfWeek])
				continue;

			html += "<th>" + DateTime.dayOfWeekToString(dayOfWeek, headerFooterDayOfWeekFormat) + "</th>";
		}

		html += "</tr>";

		return html;
	}

	function renderDateRows() {

		var i,
			s = "<tbody><tr>",
			dayOfView = DateTime.cloneDate(firstDayOfView),
			lastDayToIterate = lastDayOfView;

		while (dayOfView <= lastDayToIterate) {
			dayOfWeek = dayOfView.getDay();
			if ((dayOfView > periodStart) && (dayOfWeek == startOfWeek))
				s += "</tr><tr>";

			s += renderDateCell(dayOfView);

			DateTime.addDays(dayOfView, 1);
		}

		s += "</tr></tbody>";

		return s;
	}

	function renderDateCell(date) {
		var s = "",
			cssClass = "",
			isOverlappingDate = IsOverlappingDate(date);


		if (DateTime.isToday(date))
			cssClass += " vc-Calendar-Date-Today";

		if (isOverlappingDate)
			cssClass += " vc-Calendar-Date-Overlapping";

		s += "<td class=\"" + cssClass + "\">";

		if (!(isOverlappingDate && !showDatesOfOverlappingMonths))
			s += date.getDate();

		s += "</td>";

		return s;
	}

	function renderEventListContainer() {
		var html = "";

		html += "<div class=\"vc-Calendar-EventList\"><ul></ul></div>";

		return html;
	}

	function IsOverlappingDate(date) {
		return ((date < periodStart) || (date > periodEnd));
	}

	function getCalGridTds() {
		if ((!calGrid) || (calGrid.length == 0)) {
			calGrid = $("tbody", calElement);
			calGridTds = null;
		}

		if ((!calGridTds) || (calGridTds.length == 0)) {
			calGridTds = calGrid.find("td");
		}

		return calGridTds;
	}

	function getCalEventList() {
		if ((!calEventList) || (calEventList.length == 0)) {
			calEventList = $('.vc-Calendar-EventList ul', calElement);
		}

		//		if ((!calEventListItems) || (calEventListItems.length == 0)) {
		//			calEventListItems = calEventList.children("li.vc-Calendar-EventList-Date");
		//		}
		return calEventList;
	}

	function parseEventFromXml(eventXml) {
		var xml = $(eventXml),
			event = {};

		var dateStartStr = xml.attr("DateStart"),
			dateEndStr = xml.attr("DateEnd");

		var uid = xml.attr("Uid"),
			summary = xml.find("Summary").text(),
			description = xml.find("Description").text(),
			attendeesXml = xml.find("Attendees"),
			attendees = [];

		attendeesXml.find("Attendee").each(function() {
			attendees.push($(this).text());
		});

		event.uid = uid;
		event.dateStart = DateTime.parseDate(dateStartStr);
		event.dateEnd = DateTime.parseDate(dateEndStr);
		event.summary = summary;
		event.description = description;
		event.attendees = attendees;

		return event;
	}

	function renderPopupEvent(event) {
		var html = "<li id=\"uid:" + event.uid + "\">";

		html += "<span class=\"title\">" + event.summary + "</span>";
		html += "<span><strong>Classes:</strong>" + event.attendees.join() + "</span>";
		html += "</li>";

		return html;
	}

	function appendPopupEvent(event) {
		var eventPeriodStartIndex,
			eventPeriodEndIndex,
			gridTds = getCalGridTds(),
			i;

		// Only process events who's period intersects the view's period
		//if (!((event.dateStart > periodStart) && (event.dateEnd < periodEnd)))
		if ((event.dateEnd <= periodStart) || (event.dateStart > periodEnd))
			return;

		if (event.dateStart < periodStart) {
			eventPeriodStartIndex = viewPeriodStartTdIndex;
		}
		else {
			eventPeriodStartIndex = viewPeriodStartTdIndex + event.dateStart.getDate() - 1;
		}

		if (event.dateEnd > periodEnd) {
			eventPeriodEndIndex = viewPeriodEndTdIndex;
		}
		else {
			eventPeriodEndIndex = viewPeriodStartTdIndex + event.dateEnd.getDate() - 1;
		}

		i = eventPeriodStartIndex;

		do {
			var dateTd = $(gridTds[i]),
				popupDiv = $(".vc-EventPopup", dateTd),
				eventList,
				eventEl;

			if (popupDiv.length == 0) {
				popupDiv = $("<div class=\"vc-EventPopup\"><ul></ul></div>");
				dateTd.append(popupDiv);
			}
			eventList = $("ul", popupDiv);

			eventEl = $("#uid\\:" + event.uid, eventList);

			if (eventEl.length == 0) {
				eventEl = $(renderPopupEvent(event));
				eventList.append(eventEl);
			}

			if (!dateTd.hasClass("vc-Calendar-Date-HasEvent"))
				dateTd.addClass("vc-Calendar-Date-HasEvent");

		} while (++i < eventPeriodEndIndex);
	}

	function appendEventListEvent(event) {
		var elEventList = getCalEventList(),
			eventDate,
			eventDateEnd;

		// Only process events who's period intersects the event list's period
		//if (event.dateEnd <= today)
		if ((event.dateEnd <= today) || (event.dateStart > eventListPeriodEnd))
			return;

		eventDate = event.dateStart;
		if (eventDate < today)
			eventDate = today;

		eventDateEnd = event.dateEnd;
		if (eventDateEnd > eventListPeriodEnd)
			eventDateEnd = eventListPeriodEnd;

		do {
			var eventDateStr = DateTime.formatDate(eventDate, "yyyy-MM-dd"),
				eventDateNiceStr = DateTime.formatDate(eventDate, "ddd, MMM d"),
				dateLi = $("li[date='" + eventDateStr + "']", elEventList);

			if (eventDate.getFullYear() != today.getFullYear())
				eventDateNiceStr += " " + eventDate.getFullYear();

			if (DateTime.isToday(eventDate))
				eventDateNiceStr = "Today";

			// Create dateLi if it doesn't exist already
			if (dateLi.length == 0) {
				dateLi = $("<li class=\"vc-Calendar-EventList-Date\" date=\"" + eventDateStr + "\"><span class=\"title\">" + eventDateNiceStr + "</span><ul></ul></li>");

				// If elEventList contains an li for the date prior to eventDate,
				// insert dateLi after it.
				//				//var prevDateStr = DateTime.formatDate(DateTime.addDays(DateTime.cloneDate(eventDate), -1), "yyyy-MM-dd"),
				//				//	prevDateLi = $("li[date='" + prevDateStr + "']", elEventList);

				//				var prevDateLi = $("li:last-child", elEventList),
				//					prevDateStr = (prevDateLi.length) ? prevDateLi.attr("date") : null;

				//				if (prevDateLi.length) {
				//					dateLi.insertAfter(prevDateLi);
				//				}
				//				// Otherwise, insert liDate as the first date
				//				else {
				//					elEventList.prepend(dateLi);
				//				}
				var eventListItems = calEventList.children("li.vc-Calendar-EventList-Date"),
					eventListItemCount = eventListItems.length,
					i;

				//debugger;

				if (!eventListItemCount) {
					elEventList.append(dateLi);
				}
				else {

					for (i = eventListItemCount - 1; i >= 0; i--) {
						var li = eventListItems.eq(i),
						liDateStr = li.attr("date");

						if (liDateStr < eventDateStr)
							break;
					}

					if (i >= 0)
						dateLi.insertAfter(eventListItems.eq(i));
					else
						elEventList.prepend(dateLi);
				}

			}

			var dateEventListEl = $("ul", dateLi),
				eventLi = $("li[uid='" + event.uid + "']", dateEventListEl);
			// Create eventLi if it doesn't exist already,
			if (!eventLi.length) {
				eventLi = $("<li uid=\"" + event.uid + "\">" + event.summary + "</li>");
			}
			// TODO: replace it if this is a later revision
			var dateEventLis = $("li[uid]", dateEventListEl);

			//			// If this is the first event for this dateLi,
			//			// just prepend it
			//			if (!dateEventLis.length)
			//				dateLi.prepend(eventLi);
			//			else {
			//				// Insert dateLi based on alphabetical position
			//			}
			dateEventListEl.append(eventLi);

			DateTime.addDays(eventDate, 1);
		} while (eventDate < eventDateEnd);
	}

	function appendXmlEvents(xml) {
		$("Events", xml).find("Event").each(function() {
			var event = parseEventFromXml($(this));

			//			// Only process events who's period intersects the view's period
			//			if (!((event.dateStart > periodStart) && (event.dateEnd < periodEnd)))
			//				return;

			appendPopupEvent(event);
			appendEventListEvent(event);
		});

		wireEventPopups();
	}

	function wireEventPopups() {
		$('.vc-Calendar-Date-HasEvent', calGrid).each(function() {
			// options
			var distance = 10;
			var time = 250;
			var hideDelay = 300;

			var hideDelayTimer = null;

			// tracker
			var beingShown = false;
			var shown = false;

			var trigger = $(this);
			var popup = $('.vc-EventPopup ul', this).css('opacity', 0);

			// set the mouseover and mouseout on both element
			$([trigger.get(0), popup.get(0)]).mouseover(function() {
				// stops the hide event if we move from the trigger to the popup element
				if (hideDelayTimer) clearTimeout(hideDelayTimer);

				// don't trigger the animation again if we're being shown, or already visible
				if (beingShown || shown) {
					return;
				} else {
					beingShown = true;

					// reset position of popup box
					popup.css({
						bottom: 20,
						left: -76,
						display: 'block' // brings the popup back in to view
					})

					// (we're using chaining on the popup) now animate it's opacity and position
				.animate({
					bottom: '+=' + distance + 'px',
					opacity: 1
				}, time, 'swing', function() {
					// once the animation is complete, set the tracker variables
					beingShown = false;
					shown = true;
				});
				}
			}).mouseout(function() {
				// reset the timer if we get fired again - avoids double animations
				if (hideDelayTimer) clearTimeout(hideDelayTimer);

				// store the timer so that it can be cleared in the mouseover if required
				hideDelayTimer = setTimeout(function() {
					hideDelayTimer = null;
					popup.animate({
						bottom: '-=' + distance + 'px',
						opacity: 0
					}, time, 'swing', function() {
						// once the animate is complete, set the tracker variables
						shown = false;
						// hide the popup entirely after the effect (opacity alone doesn't do the job)
						popup.css('display', 'none');
					});
				}, hideDelay);
			});
		});
	}

	function setPeriod(newPeriodStart, newPeriodEnd) {
		periodStart = newPeriodStart;
		periodEnd = newPeriodEnd;
		periodDuration = periodEnd.valueOf() - periodStart.valueOf();

		daysInMonth = DateTime.daysInMonth(periodStart.getFullYear(), periodStart.getMonth());
		viewStartDateOffset = startOfWeek - ((Math.floor((startOfWeek + (6 - periodStart.getDay())) / 7) * 7) + periodStart.getDay());
		viewEndDateOffset = endDayOfWeek + ((Math.floor((6 + periodEnd.getDay() - endDayOfWeek) / 7) * 7) - periodEnd.getDay());

		firstDayOfView = DateTime.addDays(DateTime.cloneDate(periodStart), viewStartDateOffset);
		lastDayOfView = DateTime.addDays(DateTime.cloneDate(periodEnd), viewEndDateOffset);

		viewPeriodStartTdIndex = Math.abs(viewStartDateOffset);
		viewPeriodEndTdIndex = viewPeriodStartTdIndex + daysInMonth - 1;

		calGrid = null;
		calGridTds = null;
		calEventList = null;

		eventListPeriodEnd = DateTime.addDays(DateTime.today(), eventListPeriodDays);
	}

	return {
		constructor: function(calEl, cfg) {
			calElement = calEl;
			config = cfg;
			today = DateTime.today();

			startOfWeek = config.startOfWeek;
			dayVisibilityMask = config.dayVisibilityMask;
			showDaysOfWeekHeader = config.showDaysOfWeekHeader;
			showDaysOfWeekFooter = config.showDaysOfWeekFooter;
			showDatesOfOverlappingMonths = config.showDatesOfOverlappingMonths;
			headerFooterDayOfWeekFormat = config.headerFooterDayOfWeekFormat;
			calRows = 0;
			calCols = 0;
			thead = null;
			tbody = null;

			dayOfWeek = startOfWeek;
			endDayOfWeek = (dayOfWeek + 6) % 7;

			eventListPeriodDays = 14;

			var pStart = new Date(today.getFullYear(), today.getMonth(), 1),
				pEnd = DateTime.addMilliseconds(DateTime.addMonths(DateTime.cloneDate(pStart), 1), -1);
			setPeriod(pStart, pEnd);
		},

		getPeriodStart: function() {
			return periodStart;
		},

		render: function() {
			return render();
		},

		nextPeriod: function() {
			var pStart = DateTime.addMonths(new Date(periodStart.getFullYear(), periodStart.getMonth(), 1), 1),
				pEnd = DateTime.addMilliseconds(DateTime.addMonths(DateTime.cloneDate(pStart), 1), -1);
			setPeriod(pStart, pEnd);
		},

		previousPeriod: function() {
			var pStart = DateTime.addMonths(new Date(periodStart.getFullYear(), periodStart.getMonth(), 1), -1),
				pEnd = DateTime.addMilliseconds(DateTime.addMonths(DateTime.cloneDate(pStart), 1), -1);

			setPeriod(pStart, pEnd);
		},

		appendXmlEvents: function(xml) {
			appendXmlEvents(xml);
		}
	};


} ());
