;(function($){

/**
* Listener for click events on the calendar bubble. Prevent clicks on the calendar bubble from reaching the document.body and making the bubble close
*
* @param {Event} event
*/
var onFrontEndCalendarBubbleClick = function (event) {
	event.stopPropagation();
};

/**
* Listener for calendar occurrenceclick events. Displays information about an event occurrence when clicked on the front end.
*
* @param {Event} event
* @param {Event} mouseEvent Mouse-related event that originally triggered this occurrence click, if any
* @param {IWP_Module_Calendar_Occurrence} occurrence
*/
var onFrontEndCalendarOccurrenceClick = function (event, mouseEvent, occurrence) {
	mouseEvent.preventDefault();

	var header = $.htmlEncode(occurrence.event.summary);

	var body = '';
	body += '<ul class="calendar-occurrence-info">';

	body += '<li class="calendar-occurrence-info-start"><strong>' + $.htmlEncode(iwp.lang.get('module_calendar_Starts')) + ':</strong> <span>' + $.date('D, M j, h:ia', occurrence.start) + '</span></li>';
	body += '<li class="calendar-occurrence-info-end"><strong>' + $.htmlEncode(iwp.lang.get('module_calendar_Ends')) + ':</strong> <span>' + $.date('D, M j, h:ia', occurrence.end) + '</span></li>';

	if (occurrence.event.location) {
		body += '<li class="calendar-occurrence-info-location"><strong>' + $.htmlEncode(iwp.lang.get('module_calendar_Location')) + ':</strong> <span>' + $.htmlEncode(occurrence.event.location) + '</span></li>';
	}

	body += '</ul>';

	if (occurrence.event.description) {
		//	it's already htmlencoded because it's a wysiwyg editor in the control panel
		body += '<div class="calendar-occurrence-description">' + occurrence.event.description + '</div>';
	}

	//	show the bubble
	var bubble = $.calendarBubbleShow({ x: mouseEvent.pageX, y: mouseEvent.pageY }, header, body);

	//	stop clicks on the bubble from reaching the body, thus closing the bubble
	$(bubble).click(onFrontEndCalendarBubbleClick);
};

/**
* Listener for the calendar moreeventsclick event. The only view that will trigger this on the front end is the full view, so use this to show the agenda like popup, but only if day/week views are not available.
*
* @param {Event} event
* @param {Event} mouseEvent Mouse related event that originally triggered this click, if any.
* @param {Date} date The day that was clicked on
*/
var onFrontEndCalendarMoreEventsClick = function (event, mouseEvent, date) {
	onFrontEndCalendarRangeSelect.apply(this, [event, mouseEvent, { first: date, last: date }]);
};

/**
* Listener for calendar rangeselect events. The only view that will trigger this on the front end is the compact month view, so use this to show either the agenda-like popup, or to navigate other views on the page to the day or week view if available.
*
* @param {Event} event
* @param {Event} mouseEvent Mouse-related event that originally triggered this range select, if any
* @param {Object} range Object describing the selected date range with two parameters 'first' and 'last'
*/
var onFrontEndCalendarRangeSelect = function (event, mouseEvent, range) {

	var first;
	if (typeof range.first == 'string') {
		first = IWP_Module_Calendar.parseJSONDate(range.first);
	} else {
		first = range.first.copy();
	}

	first.setMidnight();

	//	locate other calendar instances on the page and see if they have a view suitable for displaying detailed event information
	var found = false;
	var calendar, i, j;
	for (i = IWP_Module_Calendar.instances.list.length; i--;) {
		calendar = IWP_Module_Calendar.instances.list[i];

		if ($.inArray('day', calendar.availableViews) >= 0) {
			calendar.showView('Day');
			calendar.setDate(first);
			found = true;
			continue;
		}

		if ($.inArray('week', calendar.availableViews) >= 0) {
			calendar.showView('Week');
			calendar.setDate(first);
			found = true;
			continue;
		}
	}

	if (found) {
		return;
	}

	//	did not find another suitable calendar to manipulate, show the agenda dialog

	calendar = this;

	var last = first.copy();
	last.addDate(1);

	var occurrences = calendar.getOccurrences(first, last);

	if (occurrences.length === 1) {
		onFrontEndCalendarOccurrenceClick(event, mouseEvent, occurrences[0]);
		return;
	}

	var header = $.htmlEncode($.date('D, M j, Y', first));
	var body = '';

	body += '<div class="agendadialog">';

	if (occurrences.length) {
		if (occurrences.length > 1) {
			body += '<div>' + $.htmlEncode(iwp.lang.get('module_calendar_Thereareeventsscheduledtoday')).replace(/%%events%%/, '<strong>' + occurrences.length + '</strong>') + '</div>';
		}

		body += '<ol>';
		var occurrence;
		for (i = 0; i < occurrences.length; i++) {
			occurrence = occurrences[i]
			body += '<li>';
			body += $.htmlEncode(occurrence.event.summary);

			if (occurrence.event.allDay) {
				body += ' <em>(' + $.htmlEncode(iwp.lang.get('module_calendar_alldayevent')) + ')</em>';
			} else {
				body += ' <em>(' + ($.date('g:ia', occurrence.start).replace(/(\:00|am)/g, '') + ' - ' + $.date('g:ia', occurrence.end).replace(/(\:00)/g, '')) + ')</em>';
			}

			body += '</li>';
		}
		body += '</ol>';

	} else {
		body += '<div>' + $.htmlEncode(iwp.lang.get('module_calendar_Therearenoevents')) + '</div>';

	}

	body += '</div>';

	//	show the bubble
	var bubble = $.calendarBubbleShow({ x: mouseEvent.pageX, y: mouseEvent.pageY }, header, body);

	//	stop clicks on the bubble from reaching the body, thus closing the bubble
	$(bubble).click(onFrontEndCalendarBubbleClick);
};

/**
 * This links the date navigation and range highlighting of all calendars visible on the front end
 *
 * @param {Event} event
 * @param {Date} oldDate
 * @param {boolean} silent
 */
var onFrontEndCalendarDateChange = function (event, oldDate, silent) {
	var range = this.getCurrentView().getViewDateRange();

	var instance, instanceIsCompact;
	for (var i = IWP_Module_Calendar.instances.list.length; i--;) {
		instance = IWP_Module_Calendar.instances.list[i];

		if (instance === this) {
			//	do not alter the originating calendar instance
			continue;
		}

		if (silent) {
			//	do not trigger more events if this was triggered silently
			continue;
		}

		instanceIsCompact = (instance.getCurrentView().constructor === IWP_Module_Calendar.viewClasses.Tinymonth);

		instance.setDate(this.getDate(), true);

		if (instanceIsCompact) {
			instance.getCurrentView().setHighlightRange(range.first, range.last);
		}
	}
};

var onFrontEndCalendarViewChange = function (event, currentView) {
	var range = currentView.getViewDateRange();

	var instance;
	for (var i = IWP_Module_Calendar.instances.list.length; i--;) {
		instance = IWP_Module_Calendar.instances.list[i];

		if (instance === this) {
			//	do not alter the originating calendar instance
			continue;
		}

		instanceIsCompact = (instance.getCurrentView().constructor === IWP_Module_Calendar.viewClasses.Tinymonth);

		if (instanceIsCompact) {
			instance.getCurrentView().setHighlightRange(range.first, range.last);
		}
	}
};

var onFrontEndCalendarDateNavigatorClick = function (event, clickEvent, currentView) {
	var calendar = this;

	var instance;
	for (var i = IWP_Module_Calendar.instances.list.length; i--;) {
		instance = IWP_Module_Calendar.instances.list[i];
		instanceIsCompact = (instance.getCurrentView().constructor === IWP_Module_Calendar.viewClasses.Tinymonth);

		if (instance === this) {
			continue;
		}

		instance.setDate(calendar.getDate());

		if ($.inArray('month', instance.availableViews) >= 0) {
			instance.showView('Month');
		}
	}
};

/**
* Bind frontend events to a new calendar instance
*
* @param {IWP_Module_Calendar} calendar The calendar instance to process
*/
var processNewFrontEndCalendarInstance = function (calendar) {
	var view = calendar.getCurrentView();
	if (view === null) {
		return;
	}

	var isCompact = (view.constructor === IWP_Module_Calendar.viewClasses.Tinymonth);

	//	bind events appropriate to the calendar type
	//	compact calendars should receive highlight updates but should not control date navigation of full calendars
	//	full calendars should control date navigation of compact calendars but should not receive highlight updates

	if (isCompact) {
		$(calendar)
			.bind('rangeselect', onFrontEndCalendarRangeSelect)
			.bind('occurrenceclick', onFrontEndCalendarOccurrenceClick)
			.bind('datenavigatorclick', onFrontEndCalendarDateNavigatorClick);

		//	if this compact calendar is instanciated after other full calendars, it won't have a highlight range
		var instance, found = false;
		for (var i = IWP_Module_Calendar.instances.list.length; i--;) {
			instance = IWP_Module_Calendar.instances.list[i];
			if (instance === calendar || instance.getCurrentView().constructor === IWP_Module_Calendar.viewClasses.Tinymonth) {
				//	skip the current instance or compact instances
				continue;
			}

			//	found a full calendar
			found = true;
			var range = instance.getCurrentView().getViewDateRange();
			view.setHighlightRange(range.first, range.last);
			break;
		}

		//	loop over the instances again and enable/disable the clickable month navigators on compact views if necessary
		for (var i = IWP_Module_Calendar.instances.list.length; i--;) {
			instance = IWP_Module_Calendar.instances.list[i];
			if (instance.getCurrentView().constructor !== IWP_Module_Calendar.viewClasses.Tinymonth) {
				continue;
			}

			//	found a compact calendar, set it's clickableMonthNavigator preference - the clickableMonthNavigator() function will handle re-rendering
			instance.getCurrentView().clickableMonthNavigator(found);
		}

	} else {
		$(calendar)
			.bind('datechange', onFrontEndCalendarDateChange)
			.bind('viewchange', onFrontEndCalendarViewChange)
			.bind('occurrenceclick', onFrontEndCalendarOccurrenceClick)
			.bind('moreeventsclick', onFrontEndCalendarMoreEventsClick)
			.trigger('viewchange', view);
	}
};

/**
* Listener for calendar newinstance events
*
* @param {Event} event
* @param {IWP_Module_Calendar} calendar The newly created calendar instance
*/
var onNewFrontendCalendarInstance = function (event, calendar) {
	processNewFrontEndCalendarInstance(calendar);
};

//
//	process all calendars that exist when this code is called

for (var i = IWP_Module_Calendar.instances.list.length; i--;) {
	processNewFrontEndCalendarInstance(IWP_Module_Calendar.instances.list[i]);
};

//
//	setup a bind to listen for all new calendars

$(IWP_Module_Calendar.instances).bind('newinstance', onNewFrontendCalendarInstance);

})(jQuery);
