jQuery.fn.abtCal = function(options){
	
    var defaults = {
		today:			new Date(),
		weekdayFormat:	"Short",	// 'Short', 'Long', [Custom Object]
		monthFormat:	"Long",		// 'Short', 'Long', [Custom Object]
		firstDayOfWeek:	"Sun",		// Short format
		previousText:	"Previous",
		todayText:		"Goto Today",
		nextText:		"Next",
		controlsFormat:	'Bottom',	// 'Bottom', 'Top', 'None'
		popupLocation:	"LeftTop",	// 'LeftTop', 'LeftCenter', 'LeftBottom', 'TopLeft', 'TopCenter', 'TopRight' ...
		popupWidth:		225,
		googleFeed:		"",			// Google Cal XML Feed
		scriptJson:		[]
    };	
    var settings = jQuery.extend({}, defaults, options);
	
	var calendarElement;
	var mainElement;
	var popupElement;
	
	var currentMonth;
	var currentDay;
	var currentYear;
	var weekOffset = 0;
	var firstDayOfMonth;
	var lastDayOfMonth;
	var currentEventList = [];
	
	var LongMonths = ['January','February','March','April','May','June','July','August','September','October','November','December'];
	var ShortMonths = ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'];
	var LongDays = ['Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturday'];
	var ShortDays = ['Sun','Mon','Tue','Wed','Thu','Fri','Sat'];
	
		
    return this.each(function(){
	
		Init(this);	
		
		// Load First Calendar
		calendarElement.append( BuildCalendar().show());
		
		SetListeners()
    });	

	function Init( element)
	{
		currentMonth = settings.today.getMonth();
		currentDay = settings.today.getDate();
		currentYear = settings.today.getFullYear();
		SetFirstAndLast();
		
		if ( Object.isArray(settings.monthFormat))
		{
			LongMonths = settings.monthFormat;
			settings.monthFormat = "Long";
		}
		if ( Object.isArray(settings.weekdayFormat))
		{
			LongDays = settings.weekdayFormat;
			settings.weekdayFormat = "Long";
		}
		
		
		for(var i = 0; i < 7; i++)
		{
			if ( LongDays[i].toLowerCase() == settings.firstDayOfWeek.toLowerCase()
				|| ShortDays[i].toLowerCase() == settings.firstDayOfWeek.toLowerCase())
			{
				weekOffset = i;
			}
		}
			
		mainElement = jQuery( element);
		mainElement.append( '<div class="abtCal-calendars"></div>');
		calendarElement = jQuery('.abtCal-calendars', mainElement);		
		mainElement.append( String.Format('<div class="abtCal-popup"><div class="abtCal-popupContent"></div><div class="abtCal-tip {0}"></div><div class="abtCal-close">[close]</div></div>', settings.popupLocation.toLowerCase()));
		popupElement = jQuery('.abtCal-popup', mainElement).hide().css({'position': 'absolute'}).width(settings.popupWidth);
		
		jQuery('.abtCal-close', popupElement).click(function() {
			popupElement.fadeOut();
		});
		
		var controlString = String.Format('<div class="abtCal-controls"><ul class="abtCal-buttons"><li class="abtCal-button prev" title="Previous Month"><span>{0}</span></li><li class="abtCal-button goto-today" title="Current Month"><span>{1}</span></li><li class="abtCal-button next" title="Next Month"><span>{2}</span></li></ul></div>', settings.previousText, settings.todayText, settings.nextText);
		if (settings.controlsFormat.toLowerCase() == 'bottom') {
			mainElement.append( controlString);
		}	
		if (settings.controlsFormat.toLowerCase() == 'top') {
			mainElement.prepend( controlString);
		}	
		settings.googleFeed = settings.googleFeed.replace(/\/basic$/, '/full');
	}
	
	function SetFirstAndLast()
	{
		firstDayOfMonth = new Date(currentYear, currentMonth, 1, 12, 0, 0, 0);
		
		lastDayOfMonth = new Date(firstDayOfMonth)
		lastDayOfMonth.setMonth((lastDayOfMonth.getMonth() + 13) % 12);
		lastDayOfMonth.setDate(lastDayOfMonth.getDate() - 1);
		
		var fixOddJSYear = firstDayOfMonth.getYear();
		if (fixOddJSYear < 1900) fixOddJSYear += 1900;
		
		lastDayOfMonth.setYear(fixOddJSYear);
	}
		
	function BuildCalendar()
	{
		if ( jQuery( '.abtCal-' + ShortMonths[currentMonth] + (currentYear % 100), calendarElement).length > 0) { return; }
		
		var cal  = jQuery('<div class="abtCal-monthView abtCal-' + ShortMonths[currentMonth] + (currentYear % 100) + '"></div>');
		cal.hide();
		
		var monthName = ShortMonths[currentMonth];
		if (settings.monthFormat.toLowerCase() == 'long') { monthName = LongMonths[currentMonth]; }
		cal.append( String.Format('<h3 class="abtCal-title">{0} {1}</h3>', monthName , currentYear));		
		cal.append('<table class="abtCal-table"><thead></thead><tbody></tbody></table>');
		GenerateTableHeader( jQuery('thead', cal));
		GenerateDatesTable( jQuery('tbody', cal));	
		
		LoadEvents();
		return cal;
	}
		
	function GenerateTableHeader(thead)
	{		
		thead.append('<tr class="weekdays"></tr>');
		var row = jQuery('tr', thead);
		
		var weekdays = ShortDays;
		if (settings.weekdayFormat.toLowerCase() == 'long') { weekdays = LongDays; }
		
		for (var i = 0; i < 7; i++)
		{
			var dayOfWeek = (i + weekOffset) % 7;
			var dayName = weekdays[dayOfWeek];
			var className = ShortDays[dayOfWeek].toLowerCase();
			if (dayOfWeek == 0 || dayOfWeek == 6) 
			{
				className = className + ' weekend';
			}
							
			row.append( String.Format('<th class="{0}" scope="col">{1}</th>', className, dayName));
		}
	}
	
	function GenerateDatesTable(tbody)
	{
		var monthStart = new Date(firstDayOfMonth);
		var startWeek = new Date(monthStart.setDate(monthStart.getDate() - monthStart.getDay() + weekOffset));
		var endWeek = new Date(monthStart.setDate(startWeek.getDate() + 6));
		
		if ( startWeek.getDate() != 1 && startWeek.getMonth() == currentMonth)
		{
			// Make sure to get the first week that has current month
			startWeek.setDate(startWeek.getDate() - 7);
			endWeek.setDate(endWeek.getDate() - 7);
		}
		
		while (startWeek.getMonth() == currentMonth || endWeek.getMonth() == currentMonth)
		{
			tbody.append( GenerateDateRow(startWeek));
			startWeek.setDate(endWeek.getDate() + 1);
			endWeek.setDate(endWeek.getDate() + 7);
			
		}
	}
	
	function GenerateDateRow(startDate)
	{
		var weekElement = jQuery('<tr class="week"></tr>');
		for ( var i = 0; i < 7; i++)
		{
			var activeDay = startDate.getDate();
			var activeMonth = startDate.getMonth();
			var activeDayOfWeek = startDate.getDay();
			var activeString = startDate.toDateString();
			
			var dayElement = jQuery( String.Format('<td><div class="abtCal-day"><p class="date">{0}</p><div class="abtCal-content" style="display: none;"></div></div></td>', activeDay));
			
			if (activeMonth == currentMonth)
				dayElement.addClass('current-month');
			else
				dayElement.addClass('inactive-month');
				
			dayElement.addClass( ShortDays[activeDayOfWeek].toLowerCase());			
			if (activeDayOfWeek == 0 || activeDayOfWeek == 6)
				dayElement.addClass('weekend');
			
			if (activeString == settings.today.toDateString())
				dayElement.addClass('today');
				
			dayElement.addClass('day' + activeDay);
			
			weekElement.append(dayElement);
			startDate.setDate(startDate.getDate() + 1)
		}
		
		return weekElement;	
	}
	
	
	function LoadEvents()
	{		
		jQuery.each( settings.scriptJson, function(i, entry) {	
			var startDate =  new Date(entry.start);
			if (startDate == 'Invalid Date') { startDate = new Date(); }
			var endDate =  new Date(entry.end);
			if (endDate == 'Invalid Date') { endDate = startDate; }
			
		
			if (startDate.getMonth() == currentMonth || endDate.getMonth() == currentMonth)
			{
				currentEventList.push({
				//	id: entry.id,
					title		: entry.title,
					url			: entry.url,
					start		: startDate,
					end			: endDate,
					location	: entry.location,
					description	: entry.description
				});
			}
		});	
	
		if (settings.googleFeed.length > 0) 
		{
			var params = {
				'start-min': Date.RFC3339(firstDayOfMonth),
				'start-max': Date.RFC3339(lastDayOfMonth),
				'singleevents': true,
				'max-results': 9999
			};
			jQuery.getJSON(settings.googleFeed + "?alt=json-in-script&callback=?", params, function(data) { EventsDataCallback(data); });
		}
		else
		{
			SetEventsData();
		}
	}
	
	function EventsDataCallback( data)
	{
		// Merge Google Calendar with onpage JSON
		if (data.feed.entry == undefined) return;
		jQuery.each(data.feed.entry, function(i, entry) {
				
				
			var start = new Date( Date.toRFC3339(entry['gd$when'][0]['startTime']));
			var end = new Date( Date.toRFC3339(entry['gd$when'][0]['endTime']));
//			alert(start + '-' + end);
			var url = '';
			jQuery.each(entry.link, function() {
				if (this.type == 'text/html') {
					url = this.href + '&ctz=America/New_York';;
				}
			});
			if (entry['gd$when'][0]['startTime'].indexOf('T') == -1) { end.setDate(end.getDate() - 1); }
			
			currentEventList.push({
			//	id: entry['gCal$uid']['value'],
				title: entry['title']['$t'],
				url: url,
				start: start,
				end: end,
				location: entry['gd$where'][0]['valueString'],
				description: entry['content']['$t']
			});
		});
		SetEventsData();
	}
		
	function SetEventsData()
	{
		var currentCalendar =  jQuery( '.abtCal-' + ShortMonths[currentMonth] + (currentYear % 100), calendarElement);
		var calendarDays = jQuery( '.current-month', currentCalendar);
		while ( currentEventList.length > 0) 
		{
			var event = currentEventList.pop();
			
			var eventContent = jQuery('<div class="abtCal-event"></div>');
			
			// Add Title
			if ( event.title != undefined && event.url != undefined)
			{
				eventContent.append( String.Format( '<p class="abtCal-eventTitle"><strong><a href="{1}" target="_blank">{0}</a></strong></p>', event.title, event.url));
			}
			else if ( event.title != undefined) 
			{
				eventContent.append( String.Format( '<p class="abtCal-eventTitle"><strong>{0}</strong></p>', event.title));
			}
			
			// Add Date
			if ( event.start.getDate() == event.end.getDate()) 
			{
				eventContent.append( String.Format( '<p class="abtCal-eventTime"><em>{0}</em></p>', event.start.toLocaleDateString()));
			}
			else
			{
				eventContent.append( String.Format( '<p class="abtCal-eventTime"><em>{0}</em><span> - </span><em>{1}</em></p>', event.start.toLocaleDateString(), event.end.toLocaleDateString()));
			}
			
			
			// Add Location
			if ( event.location != undefined) 
			{
				eventContent.append( String.Format( '<p class="abtCal-eventLocation"><em>{0}</em></p>', event.location));
			}
			
			// Add Description
			if ( event.description != undefined) 
			{
				eventContent.append( String.Format( '<p class="abtCal-eventDesc">{0}</p>', event.description));
			}
			
			
			// Add Linky
			if ( event.url != undefined)
			{
				eventContent.append( String.Format( '<p class="abtCal-eventLink"><a href="{0}" target="_blank">Read More</a></p>', event.url));
			}
			
			var currentMonthEndDate = event.end.getDate();
			var currentMonthStartDate = event.start.getDate();
			if (currentMonth != event.end.getMonth()) {
				currentMonthEndDate += 31;
			}
			if (currentMonth != event.start.getMonth()) {
				currentMonthStartDate = 1;
			}
			for (var i = currentMonthStartDate; i <= currentMonthEndDate; i++)
			{
				var targetDay = calendarDays.filter('.day' + i).addClass('eventful');
				targetDay.find('.abtCal-content').append(eventContent.clone());
			}
		}
		
		jQuery('.eventful', currentCalendar).click(function() {	
		
			var clickedElm = jQuery(this);
			var otherEvents = jQuery('.eventful', currentCalendar).not( clickedElm).removeClass('open');
			
						
			jQuery('.abtCal-popupContent', mainElement).html( clickedElm.find('.abtCal-content').html());
			var clickedElmOffset = clickedElm.position();
			switch (settings.popupLocation.toLowerCase())
			{
				case 'lefttop' : popupElement
									.css({ 'top': (clickedElmOffset.top - popupElement.height() + clickedElm.height()) + 'px' })
									.css({ 'left': (clickedElmOffset.left - popupElement.width()) + 'px' }); 
									break;
				case 'leftcenter' : popupElement
									.css({ 'top': (clickedElmOffset.top - Math.round(popupElement.height() / 2) + Math.round(clickedElm.height()/2)) + 'px' })
									.css({ 'left': (clickedElmOffset.left - popupElement.width()) + 'px' }); 
									break;
				case 'leftbottom' : popupElement
									.css({ 'top': (clickedElmOffset.top) + 'px' })
									.css({ 'left': (clickedElmOffset.left - popupElement.width()) + 'px' }); 
									break;	
				case 'topleft' : popupElement
									.css({ 'top': (clickedElmOffset.top - popupElement.height()) + 'px' })
									.css({ 'left': (clickedElmOffset.left - popupElement.width() + clickedElm.width()) + 'px' }); 
									break;
				case 'topcenter' : popupElement
									.css({ 'top': (clickedElmOffset.top - popupElement.height()) + 'px' })
									.css({ 'left': (clickedElmOffset.left - Math.round(popupElement.width() / 2) + Math.round(clickedElm.width() /2)) + 'px' }); 
									break;
				case 'topright' : popupElement
									.css({ 'top': (clickedElmOffset.top - popupElement.height()) + 'px' })
									.css({ 'left': (clickedElmOffset.left) + 'px' }); 
									break;
				case 'righttop' : popupElement
									.css({ 'top': (clickedElmOffset.top - popupElement.height() + clickedElm.height()) + 'px' })
									.css({ 'left': (clickedElmOffset.left + clickedElm.width()) + 'px' }); 
									break;
				case 'rightcenter' : popupElement
									.css({ 'top': (clickedElmOffset.top - Math.round(popupElement.height() / 2) + Math.round(clickedElm.height()/2)) + 'px' })
									.css({ 'left': (clickedElmOffset.left + clickedElm.width()) + 'px' }); 
									break;
				case 'rightbottom' : popupElement
									.css({ 'top': (clickedElmOffset.top) + 'px' })
									.css({ 'left': (clickedElmOffset.left + clickedElm.width()) + 'px' }); 
									break;	
				case 'bottomleft' : popupElement
									.css({ 'top': (clickedElmOffset.top + clickedElm.height()) + 'px' })
									.css({ 'left': (clickedElmOffset.left - popupElement.width() + clickedElm.width()) + 'px' }); 
									break;
				case 'bottomcenter' : popupElement
									.css({ 'top': (clickedElmOffset.top + clickedElm.height()) + 'px' })
									.css({ 'left': (clickedElmOffset.left - Math.round(popupElement.width() / 2) + Math.round(clickedElm.width() /2)) + 'px' }); 
									break;
				case 'bottomright' : popupElement
									.css({ 'top': (clickedElmOffset.top + clickedElm.height()) + 'px' })
									.css({ 'left': (clickedElmOffset.left) + 'px' }); 								
			}
			
			if (clickedElm.hasClass('open'))
			{
				clickedElm.removeClass('open');
				popupElement.fadeOut();
			}
			else
			{
				clickedElm.addClass('open');
				popupElement.hide();
				popupElement.fadeIn();
			}
		});
	}
	
	function SetListeners()
	{
		jQuery('.abtCal-controls .prev', mainElement).click( function() {	
		
			popupElement.hide();
			currentMonth = (currentMonth + 11) % 12;
			if ( currentMonth == 11) currentYear--;
			SetFirstAndLast();
						
			calendarElement.prepend( BuildCalendar());
			var newCalendar =  jQuery( '.abtCal-' + ShortMonths[currentMonth] + (currentYear % 100), calendarElement);	
			
			jQuery('.abtCal-monthView', calendarElement).hide();
			newCalendar.fadeIn(400);
			
		});
		jQuery('.abtCal-controls .goto-today', mainElement).click( function() {
		
			popupElement.hide();		
			currentMonth = settings.today.getMonth();
			currentYear = settings.today.getFullYear();
			SetFirstAndLast();
			
			calendarElement.append( BuildCalendar());
			var newCalendar =  jQuery( '.abtCal-' + ShortMonths[currentMonth] + (currentYear % 100), calendarElement);	
			
			jQuery('.abtCal-monthView', calendarElement).hide();
			newCalendar.fadeIn(400);
		});
		jQuery('.abtCal-controls .next', mainElement).click( function() {	
		
			popupElement.hide();
			currentMonth = (currentMonth + 13) % 12;
			if ( currentMonth == 0) currentYear++;
			SetFirstAndLast();
			
			calendarElement.append( BuildCalendar());
			var newCalendar =  jQuery( '.abtCal-' + ShortMonths[currentMonth] + (currentYear % 100), calendarElement);	
			
			jQuery('.abtCal-monthView', calendarElement).hide();
			newCalendar.fadeIn(400);
			
		});
	}
};

Object.isArray = function(obj) {
   if (obj.constructor.toString().indexOf("Array") == -1)
      return false;
   else
      return true;

}

Date.RFC3339 = function( date )
{
	var stamp = new Date( date );
	return String.Format( '{0}-{1}-{2}T{3}:{4}:{5}Z',
							stamp.getUTCFullYear(),
							String.Right( '00' + (stamp.getUTCMonth() + 1), 2),
							String.Right( '00' + stamp.getUTCDate(), 2),
							String.Right( '00' + (stamp.getUTCHours() + 1), 2),
							String.Right( '00' + (stamp.getUTCMinutes() + 1), 2),
							String.Right( '00' + (stamp.getUTCSeconds() + 1), 2)
						);
};

Date.toRFC3339 = function( date )
{
	var year = date.substring(0,4);
	var month = parseInt( date.substring(5,7), 10) - 1;
	var day = date.substring(8,10);
	var hours = date.substring(11,13);
	var minutes = date.substring(14,16);
	var seconds = date.substring(17,19);
	return new Date(year, month, day, hours, minutes, seconds, 0);
};

String.Format = function( text )
{
    if ( arguments.length <= 1 ) { return text; }
    var tokenCount = arguments.length - 2;
    for( var token = 0; token <= tokenCount; token++ )
    { text = text.replace( new RegExp( '\\{' + token + '\\}', 'gi' ), arguments[ token + 1 ] ); }
    return text;
};

String.Right = function( text, number)
{
    if (number <= 0)
       return "";
    else if (number > String(text).length)
       return text;
    else {
       var iLen = String(text).length;
       return String(text).substring(iLen, iLen - number);
    }
};


