/*
 * @file cmscalendar.js
 * @details Contains the CMSCalendar javascript class and associated methods for ajax-based event calendar
 * @author Ben Kinder
 * @date Oct 5, 2011
 *
 * Example usage:
 *
 * // the following code would initialize a small calendar with id 6 to show in div with id="acalendar"
 *
 * $(document).ready(function() {
 *   var cal = new CMSCalendar("acalendar", 6, "small");
 *   cal.show();
 * });
 *
 * The following css should also be included in any page that uses the calendar:
 *
 * <link href='/css/cmscalendar.css' rel='stylesheet' type='text/css' />
*/


/* @brief constructor for CMSCalendar objects
 * @param containerElementId: the id of the html node where the calender will be inserted
 * @param calid: the database id of the calendar
 * @param size: "small" or "large"
 */
function CMSCalendar(containerElementId, calid, size) {
	// default to the current month
	var d = new Date();
	this.month = d.getMonth(); // 0-11
	this.year = d.getFullYear();

	this.size = size == "large" ? "large" : "small";
	this.id = calid;
	this.containerElementId = containerElementId;
}


// shows the calendar
CMSCalendar.prototype.show = function () {
	var calhtml = '';

	calhtml += '<div class="cmscalendar'+this.size+'">';

	// month and year headers, with buttons to increment / decrement each
	calhtml += '<span class="calnavbutton" id="prevmonth'+this.id+'">&lt;</span>' + '<span class="calnavtext" id="curmonth'+this.id+'"></span>' + '<span class="calnavbutton" id="nextmonth'+this.id+'">&gt;</span> ';

	calhtml += '<span class="calnavbutton" id="prevyear'+this.id+'">&lt;</span>' + '<span class="calnavtext" id="curyear'+this.id+'"></span>' + '<span class="calnavbutton" id="nextyear'+this.id+'">&gt;</span>';


	// this div will be filled with the a table containing the calendar itself by the showMonthTable function
	calhtml += '<div id="caltable'+this.id+'"></div>';

	// the caleventsummary will show the event summaries when a day is clicked on the small calendar
	// or "More" is clicked on the large calendar
	calhtml += '<div id="caleventsummary'+this.id+'" class="summarypopup"></div>';

	// the "caleventdetails" div will be used to display details when an event is clicked
	// making calendar id part of the div's id so there will be a unique one for each calendar
	calhtml += '<div id="caleventdetails'+this.id+'" class="eventpopup"></div>';

	calhtml += '</div>';

	$('#' + this.containerElementId).empty().append(calhtml);

	// all popup divs should start out hidden
	$('.summarypopup').hide();
	$('.eventpopup').hide();

	// create a variable that the anonymous event callback functions can use to reference this object
	var ourclass = this;

	// bind events to the methods that change months and years
	$('#prevmonth'+this.id).click(function() { ourclass.decMonth() });
	$('#nextmonth'+this.id).click(function() { ourclass.incMonth() });
	$('#prevyear'+this.id).click(function() { ourclass.decYear() });
	$('#nextyear'+this.id).click(function() { ourclass.incYear() });

	// show the current month on the calendar
	this.updateMonth();
}

// Puts the days of the month into an html table for display, puts events in cells where needed
CMSCalendar.prototype.showMonthTable = function() {

	var calhtml = '';

	// make a table for the calendar grid
	// add headers for days of the week
	calhtml += '<table class="cmscalendartable'+this.size+'">';
	var weekdays;
	if (this.size == "large")
		// full weekday headers for large calendar
		weekdays = Array('Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday');
	else
		// abbreviated weekday headers for small calendars
		weekdays = Array('S', 'M', 'T', 'W', 'T', 'F', 'S');
	calhtml += '<tr>';
	for (days = 0; days < weekdays.length; days++) {
		calhtml += '<th class="caldayheader">' + weekdays[days] + '</th>';
	}
	calhtml += '</tr>';


	// find out which day of the week the first day of the current month is on
	var testdate = new Date();
	testdate.setMonth(this.month);
	testdate.setFullYear(this.year);
	testdate.setDate(1);
	var firstday = testdate.getDay(); // returns the weekday (0-6)
	var monthdays = daysInMonth(this.month, this.year);

	// tracks which day of the week we are currently looking at
	var curdayofweek = 0;

	calhtml += '<tr>';
	// pad the first row with blank cells
	for (cellidx = 0; cellidx < firstday; cellidx++) {
		calhtml += '<td class="blankcell">&nbsp;</td>';
		curdayofweek++;
	}

	// tracks which days have events attached so we can bind them to the cells' click events
	var eventdays = Array();

	// maximum number of events that can be shown in a single day cell on the large calendar
	var MaxDayEvents = 2;

	// print each day in the month, checking for events associated with the day
	for (calday = 1; calday <= monthdays; calday++) {
		calhtml += '<td class="calcell">';

		if (this.size == "large") {
			// with a large calendar, the calendar day will be floating in the top-left corner of the cell
			calhtml += '<span class="calday">' + calday + '</span>';

			// are there any events for this day?
			if (this.monthevents[calday].length) {

				eventdays[eventdays.length] = calday;

				totalevents = this.monthevents[calday].length;

				// do we have more events than will fit in the calendar cell?
				if (totalevents > MaxDayEvents) {
					showevents = MaxDayEvents;
				} else {
					showevents = totalevents;
				}

				// show as many events as we can fit
				for (eidx = 0; eidx < showevents; eidx++) {
					// this div will have click events bound to show details below after it has been added to the DOM
					calhtml += '<div class="calfakelink" id="eventlink_day_'+calday+'_idx_'+eidx+'" >'+
						'<span style="color: '+this.monthevents[calday][eidx]['textVal']+'; background-color: '+this.monthevents[calday][eidx]['colorVal'] +'" >'
						+ this.monthevents[calday][eidx]['caption'] + '</span></div>';
				}

				// if there are more than max events, show a "more" link for popup display
				if (showevents < totalevents) {
					calhtml += '<div class="calfakelink" id="eventlink_day_'+calday+'_more">' + 'More...' + '</div>';
				}
			}
		} else {
			// for the "small" calendar, make the cell's number a link to open the list of events
			// if they exist, otherwise make the number a non-link in a different style
			if (this.monthevents[calday].length) {
				// set the cell's id uniquely using the calendar id and day number, separated by underscore
				// so it can be bound to the click event below
				calhtml += '<span class="smcaldayevents" id="calcell_'+this.id+'_'+calday+'">' + calday + '</span>';
				eventdays[eventdays.length] = calday;
			} else {
				// no events for this day
				calhtml += '<span class="smcalday">' + calday + '</span>';
			}
		}

		calhtml += '</td>';
		curdayofweek++;

		// check for end of week
		if (curdayofweek % 7 == 0) {
			calhtml += '</tr>';
			curdayofweek = 0;
			// do we need a row for another week?
			if (calday < monthdays - 1) {
				calhtml += '<tr>';
			}
		}
	}

	// fill out the rest of the last week with blanks
	while (curdayofweek % 7 != 0) {
		calhtml += '<td class="blankcell">&nbsp;</td>';
		curdayofweek++;
	}


	calhtml += '</table>';


	$('#caltable'+this.id).empty().append(calhtml);

	// variable for passing "this" object to anonymous event callback functions
	var ourclass = this;


	// bind click events on cells that have events to click on.  What an event!
	for (var didx in eventdays) {
		var thisday = eventdays[didx];

		if (this.size == "small") {
			// pass the current value in the thisday variable
			// to the anonymous function to bind the event so the value gets preserved
			(function (aday) {
				$('#calcell_'+ourclass.id+'_'+aday).click( function() { ourclass.showDayEvents(aday); } );
			} (thisday));
		} else if (this.size == "large") {
			totalevents = this.monthevents[thisday].length;


			if (totalevents > MaxDayEvents) {
				showevents = MaxDayEvents;
			} else {
				showevents = totalevents;
			}

			// bind each event to method that will show event details
			for (eidx = 0; eidx < showevents; eidx++) {
				// send daynum and eidx as anonymous function parameters to preserve their current values when the click event is triggered
				(function (theday, theindex) {
					$('#eventlink_day_'+theday+'_idx_'+theindex).click(function() { ourclass.showEventDetails(theday, theindex); } );
				} (thisday, eidx));
			}


			// Bind the "More.." link to popup all events for the day
			if (showevents < totalevents) {
				(function (theday) {
					$('#eventlink_day_'+theday+'_more').click(function() { ourclass.showDayEvents(theday); } );
				} (thisday));
			}

		}
	}
}

// updates the month/year calendar header
CMSCalendar.prototype.updateHeader = function() {
	var monthnames = Array('January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December');
	var monthname = monthnames[this.month];
	$('#curmonth'+this.id).empty().append(monthname);

	$('#curyear'+this.id).empty().append(this.year);
}


// increments the month, refreshes the calendar
// this function should be bound to the click event on the "Next Month" button
CMSCalendar.prototype.incMonth = function() {
	this.month++;
	if (this.month > 11) {
		this.month -= 12;
		this.year++;
	}

	this.updateMonth();
}

// decrements the month, refreshes the calendar
// this function should be bound to the click event on the "Previous Month" button
CMSCalendar.prototype.decMonth = function() {
	this.month--;
	if (this.month < 0) {
		this.month += 12;
		this.year--;
	}

	this.updateMonth();
}

// increments the year, refreshes the month display
// bound to the "Next Year" button
CMSCalendar.prototype.incYear = function() {
	this.year++;

	this.updateMonth();
}

// decrements the year, refreshes the month display
// bound to the "Previous Year" button
CMSCalendar.prototype.decYear = function() {
	this.year--;

	this.updateMonth();
}

// updates the month, retrieves event info
// this function should be called whenever the month has changed
// and the display needs to be updated
CMSCalendar.prototype.updateMonth = function() {
	this.retrieveMonthEvents();
	this.updateHeader();
	this.showMonthTable();
}

// retrieves the events for the current month
CMSCalendar.prototype.retrieveMonthEvents = function() {
	// object reference that can be used in anonymous callback functions
	var calobj = this;

	$.ajax(
		'/Calendar/ajax_get_month_events',
		{
			async: false,
			type: "POST",
			data: {
				"calendarid": this.id,
				"year": this.year,
				"month": this.month + 1 // convert month number from 0-11 range to 1-12
			},
			success: function(json_data) {
				try {
					data = jQuery.parseJSON(json_data);
				} catch (error) {
					alert('json parsing error: ' + error + '   data: ' + json_data);
				}

				if (data['success'] == "1") {
					calobj.monthevents = data['events'];
				} else {
					alert('error retrieving month events: ' + data['error']);
				}

			}
		}
	);
}


// shows events when a day's cell is clicked on the small calendar
// or when there are more than MAX events for a day on a large calendar size, this popup will show therest
CMSCalendar.prototype.showDayEvents = function(daynum) {

	// if there is only 1 event for this day (small cal), and it is not a link, go straight to the details 
	if (this.monthevents[daynum].length == 1 && this.monthevents[daynum][0]['page'] == '') {
		this.showEventDetails(daynum, 0);

	} else {

		var html = '';

		titlebar = '<div id="calpopuptitle'+this.id+'" class="calpopuptitle">'+
			'<div class="calpopupclose">X</div>'+
			'Events for '+this.month + '/' + daynum + '/' + this.year +'</div>';

		html += titlebar;

		html += '<div class="calpopupbody">';

		// show a link for each event, the event can then be clicked on for extra details
		for (eidx in this.monthevents[daynum]) {
			html += '<div class="calfakelink" id="eventlink'+eidx+'"><span style="color: '+this.monthevents[daynum][eidx]['textVal']+'; background-color: '+ this.monthevents[daynum][eidx]['colorVal']+'">' + this.monthevents[daynum][eidx]['caption'] + '<span></div>';
		}
		html += '</div>';


		$('#caleventsummary'+this.id).empty().append(html);

		// create a variable that the anonymous event callback functions can use to reference this object
		var ourclass = this;

		// bind each event to method that will show event details
		for (eidx in this.monthevents[daynum]) {
			// send daynum and eidx as anonymous function parameters to preserve their current values when the click event is triggered
			(function (theday, theindex) {
				$('#eventlink'+theindex).click(function() { ourclass.showEventDetails(theday, theindex); } );
			} (daynum, eidx));
		}

		$('#calpopuptitle'+this.id).click( function () { ourclass.closeEventSummary(); } );


		$('#caleventsummary'+this.id).show();
	}
}

// shows the details of an event
CMSCalendar.prototype.showEventDetails = function(daynum, eventidx) {
	var html = '';

	var theevent = this.monthevents[daynum][eventidx];

	if (theevent['page'] != '') {
		// if this event is a link to an exterior page, redirect there
		newurl = theevent['page'];
		location.href = newurl;
	} else {
		// not a link, so we will show some details content in the caleventdetails div

		var titlebar = theevent['caption'];

		titlebar = '<div id="caleventdetailstitle'+this.id+'" class="calpopuptitle">'+
			'<div class="calpopupclose">X</div>'+
			theevent['caption']+'</div>';

		$('#caleventdetails'+this.id).empty().append(
			titlebar +
			'<div class="calpopupbody" > <div id="caleventdetailscontent'+this.id+'"></div></div>');

		var ourclass = this;

		$('#caleventdetailstitle'+this.id).click( function () { ourclass.closeEventDetails(); } );


		// if the event has a reference to a page to display inline as details, show it
		if (theevent['inlinePageId'] != 0) {
			theevent['inlinePageId'];
			$('#caleventdetailscontent'+this.id).load('/content/inline/' + theevent['inlinePageId']);
		} else {
			// otherwise if there are details, show those
			html = theevent['details'];
			$('#caleventdetailscontent'+this.id).empty().append(html);
		}

		switch (theevent['popupsize']) {
			case "small":
				$('#caleventdetails'+this.id).addClass('eventpopupsmall');
				$('#caleventdetails'+this.id).removeClass('eventpopupmedium');
				$('#caleventdetails'+this.id).removeClass('eventpopuplarge');

				$('#caleventdetailscontent'+this.id).addClass('eventpopucontentpsmall');
				$('#caleventdetailscontent'+this.id).removeClass('eventpopupcontentmedium');
				$('#caleventdetailscontent'+this.id).removeClass('eventpopupcontentlarge');
				break;
			case "medium":
				$('#caleventdetails'+this.id).removeClass('eventpopupsmall');
				$('#caleventdetails'+this.id).addClass('eventpopupmedium');
				$('#caleventdetails'+this.id).removeClass('eventpopuplarge');

				$('#caleventdetailscontent'+this.id).removeClass('eventpopupcontentsmall');
				$('#caleventdetailscontent'+this.id).addClass('eventpopupcontentmedium');
				$('#caleventdetailscontent'+this.id).removeClass('eventpopupcontentlarge');
				break;
			case "large":
				$('#caleventdetails'+this.id).removeClass('eventpopupsmall');
				$('#caleventdetails'+this.id).removeClass('eventpopupmedium');
				$('#caleventdetails'+this.id).addClass('eventpopuplarge');

				$('#caleventdetailscontent'+this.id).removeClass('eventpopupcontentsmall');
				$('#caleventdetailscontent'+this.id).removeClass('eventpopupcontentmedium');
				$('#caleventdetailscontent'+this.id).addClass('eventpopupcontentlarge');
				break;
		}

		$('#caleventdetails'+this.id).show();
		// $('#caleventdetails'+this.id).css('border', '3px solid black');

	}
}


// hides/closes event details
CMSCalendar.prototype.closeEventDetails = function() {
	$('#caleventdetails'+this.id).hide();
}

// hides/closes event summary
CMSCalendar.prototype.closeEventSummary = function() {
	$('#caleventsummary'+this.id).hide();
}

// returns the number of days in a given month
function daysInMonth(iMonth, iYear)
{
	// this works because javascript rolls extra days over to the next month.
	// So, for example, setting a Date object to Feb 32, 2011 should cause
	// the date returned to be March 4. 
	// 32 - 4 = 28 days in february, etc etc
	return 32 - new Date(iYear, iMonth, 32).getDate();
}


