/**
 * SimpleFlame Content rotator
 * Version 0.2 (28.04.2009)
 * Possible effects to use :
 *  - if UI effects have been added: 'blind', 'bounce', 'clip', 'drop', 'explode', 'fold', 'highlight', 'puff', 'pulsate', 'scale', 'shake', 'size', 'slide', 'transfer'
 *  - basic effects from jQuery: fadeIn, fadeOut, show, hide, slideUp, slideDown
 */
(function(){
	
	var sfRotator = function(el, options){

		this.settings = {
			'item' : 'li', //rotated items
			'activeClass' : 'active',  //active item indicator
			'duration' : 5000, //duration cycle in miliseconds
			'autorotate' : true,
			
			'effectIn' : 'fadeIn', //effect triggered on showing new item
			'optionsIn' : {}, //additional options used in show transition			
			'speedIn' : 'normal', //effectIn speed			
			
			'effectOut' : 'fadeOut', //effect triggered on hiding old item
			'optionsOut' : {}, //additional options used in hide transition
			'speedOut' : 'normal' //effectOut speed
		};
		jQuery.extend(this.settings, options);

		this.$container = jQuery(el);
		this.build();
	};
	
	sfRotator.prototype.build = function(){
		
		this.$container.addClass('sf-items');
		
		this.$wrapper = jQuery('<div class="sf-rotator" />');
		this.$container.before(this.$wrapper);
		this.$wrapper.append(this.$container);
		
		this.$controls = jQuery('<ul class="sf-controls" />');
		this.$wrapper.append(this.$controls);
		
		this.$items = this.$container.children(this.settings.item);
		this.$current = this.$items.index(this.$items.filter('.'+this.settings.activeClass));
		
		//in case there are no items marked as active in the code
		if (this.$current < 0) {
			this.$current = 0;
		}
		
		var self = this;
		
		this.$items.addClass('sf-item').each(function(index, item){
			var trigger = jQuery('<li><a href="#">'+parseInt(index+1,10)+'</a></li>');
			self.$controls.append(trigger);
			//attach rotator item to controls trigger
			trigger.find('a').data('item',item).bind('click', { self : self }, self.trigger);
		});
		
		this.activate(this.$current, true);
		
		//auto rotation
		if (this.settings.autorotate) {
			this.autorotate();
		}
		
	};
	
	/**
	 * Function triggered by click on controls item
	 */
	sfRotator.prototype.trigger = function(event){
		event.preventDefault();
		var self = event.data.self;
		
		self.stopAutorotate();
		self.$rotationTerminated = true;
		
		var position = self.$items.index(jQuery(this).data('item'));
		self.activate(position);
	};
	
	/**
	 * Activate item identified by position
	 */
	sfRotator.prototype.activate = function(position) {
		
		var instant = arguments[1] || false;
		
		var activeClass = this.settings.activeClass;
		
		var oldItem = this.$items.eq(this.$current);
		var newItem = this.$items.eq(position);
		
		var onHide = function(){
			oldItem.removeClass(activeClass);
		};
		
		var onShow = function(){
			newItem.addClass(activeClass).css('zIndex', 10);
		};
		
		var effects = ['blind', 'bounce', 'clip', 'drop', 'explode', 'fold', 'highlight', 'puff', 'pulsate', 'scale', 'shake', 'size', 'slide', 'transfer'];
		
		//instant change
		if (instant === true) {
			oldItem.removeClass(activeClass).hide();
			newItem.addClass(activeClass).show();			
		}
		else {
			//advanced UI effect
			if (jQuery.inArray(this.settings.effectOut, effects) > -1) {
				oldItem.hide(this.settings.effectOut, this.settings.optionsOut, this.settings.speedOut, onHide);
			}
			//regular effect - fadeIn, fadeOut, slideUp, slideDown, show, hide
			else if(jQuery.isFunction(oldItem[this.settings.effectOut])) {		
				oldItem[this.settings.effectOut](this.settings.speedOut, onHide);
			}
			else {
				throw "Unsupported hide transition";
			}
			
			//place new item on top of the stack
			newItem.css('zIndex', 100);
			
			//advanced UI effect			
			if (jQuery.inArray(this.settings.effectIn, effects) > -1) {
				newItem.show(this.settings.effectIn, this.settings.optionsIn, this.settings.speedIn, onShow);
			}
			//regular effect - fadeIn, fadeOut, slideUp, slideDown, show, hide			
			else if(jQuery.isFunction(newItem[this.settings.effectIn])) {
				newItem[this.settings.effectIn](this.settings.speedIn, onShow);
			}
			else {
				throw "Unsupported show transition";
			}			
		}
		
		//update controls state
		this.$controls.find('a').removeClass('active').eq(position).addClass('active');
		
		this.$current = position;
	};

	/**
	 * enable autorotate on elements
	 */
	sfRotator.prototype.autorotate = function(){
		this.$rotationTerminated = false;
		
		var self = this;

		this.$container.mouseenter(function(){
			self.stopAutorotate();
		});
		
		this.$container.mouseleave(function(){									
			self.startAutorotate();
		});
		
		this.startAutorotate();
	};
	
	sfRotator.prototype.startAutorotate = function(){
		if (this.$rotationTerminated === true) {
			return;
		}

		var self = this;
		this.$rotationInterval = window.setInterval(function(){
			var next = self.$current + 1;
			if ( next === self.$items.length) {
				next = 0;	
			}
			self.activate(next);
		}, this.settings.duration);
	};
	
	
	sfRotator.prototype.stopAutorotate = function(){
		if (this.settings.autorotate) {
			window.clearInterval(this.$rotationInterval);
		}
	};
	
	//attach as jQuery plugin
	jQuery.fn.sfRotator = function(options){
		options = options || {};
		
		return this.each(function(){
			var r = new sfRotator(this, options);
		});
	};
})();

/**
 * sfSlider
 *
 * @version: 1.1
 * @author SimpleFlame http://www.simpleflame.com/
 *
 * Required settings:
 *  display 	- provide number of items displayed at once
 *
 * Other settings:
 *  time      - transition time
 *  easing    - easing for the transition
 *  width     - width of the scrolled area (by default visible area + right margin on the last visible item)
 *  previous  - previous link text
 *  next      - next link text
 *  wrap      - wrap container selector
 *  slider    - items container selector
 *  items     - items selector
 */ 

(function($) {
	jQuery.fn.sfSlider = function(options){
		var defaults = {
			width 		: 0,
			display		: 6,
			
			time		: 500,
			easing		: 'swing',
			
			previous	: 'Previous',
			next		: 'Next',
			wrap 		: 'div.wrap',
			slider 		: 'ul.items',
			items 		: 'ul.items li'
		};
		
		var settings = $.extend({}, defaults, options);
		
		return this.each(function(){
			var $root 		= $(this);
			var $wrap 		= $root.find(settings.wrap);
			var $slider 	= $root.find(settings.slider);
			var $items 		= $root.find(settings.items);
			
			var all			= $items.size();
			var pages		= Math.ceil(all/settings.display);
			
			$items.filter(':last').addClass('last');
			$items.filter(':first').addClass('first');
			
			// check is there enough items for paging
			if($items.size() <= settings.display) return false;
			
			// try to estimate visible area width if not set
			var width 		= settings.width;
			if(settings.width === 0) width = $wrap.width() + parseInt($items.css('margin-right'),10);
			
			// defaults
			var current 	= 1;
			
			// insert paging links
			$root.append('<ul class="index"><li class="prev"><a href="#previous" class="off">'+settings.previous+'</a></li><li class="next"><a href="#next">'+settings.next+'</a></li></ul>');
			var $controls 	= $root.find('ul.index');

			// check for first/last page and indicate that in controls
			var check = function(){
				if(current === 1){
					$controls.find('li.prev a').addClass('off');
				} else {
					$controls.find('li.prev a').removeClass('off');
				}
				
				if(current === pages){
					$controls.find('li.next a').addClass('off');
				} else {
					$controls.find('li.next a').removeClass('off');
				}
			};
			
			// add events
			$controls.find('a').click(function(){
				var direction = $(this).parent().attr('class');
				if($slider.is(':animated') || (current == 1 && direction == 'prev') || (current == pages && direction == 'next')) return false;
				
				if(direction == 'next'){
					move = '-='+width+'px';
					current++;
				} else {
					move = '+='+width+'px';
					current--;
				}					
				
				$slider.animate({'marginLeft': move}, settings.time, settings.easing);
				check();
				
				return false;
			});
		});
	};
})(jQuery);

(function($){

	var sfCalendar = function(el){
		this.$root = $(el);

		this.settings = {
			'label' : 'Events List',
			'months' : ['January','February','March','April','May','June','July','August','September','October','November','December'],
			'monthsShort' : ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'],
			'days' : ['Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturday'],
			'daysShort' : ['Sun','Mon','Tue','Wed','Thu','Fri','Sat']
		};
		
		
		var options = arguments[1] || {};
		this.settings = $.extend(this.settings, options);

		this.items = this.parseData();
		
		this.structure();
		this.listing();
		this.calendar( );

		this.navigation();	
		
		this.tagsList();
				
		//active today's date
		var now = this.normalizeDate(new Date());			
		$(document).trigger('eventbox.setDay',[ now ]);		
	};

	/*
	 * In order to compare dates we have to make sure that values match to to the millisecond,
	 * so every date has to be normalized.
	 * You can pass true as the second parameter to set to the end of the day
	 */
	sfCalendar.prototype.normalizeDate = function(date){
		if (!arguments[1]) {
			date.setHours(0);
			date.setMinutes(0);
			date.setSeconds(0);					
			date.setMilliseconds(0);		
		}
		else { //end of the day
			date.setHours(23);
			date.setMinutes(59);			
			date.setSeconds(59);			
			date.setMilliseconds(999);						
		}
		return date;
	};
	
	/*
	 * General structure of calendar listing
	 */
	sfCalendar.prototype.structure = function(){
		this.$main = $('<div class="main"/>');

		this.$period = $('<h3 />');		
		this.$events = $('<div class="wrapper"/>');		
		if (this.settings.label) {
			this.$main.append('<h2>'+this.settings.label+'</h2>');
		}
		
		this.$main.append(this.$period, this.$events);
		
		this.$aside = $('<div class="aside"/>');

		this.$root.append(this.$main, this.$aside);
	};
	
	/*
	 * Explodes a comma separated string into an array of trimmed strings
	 */
	sfCalendar.prototype.parseTags = function(str){
		var tags = str.split(',');
		return $.map(tags,function(tag){
			tag = $.trim(tag); //removes whitespace
			return tag ? tag : null; //removes empty tags
		});
	};
	
	/*
	 * Reads events data from html
	 */
	sfCalendar.prototype.parseData = function(){
		var 
			items = [],
			self = this,
			tags = [];
			
		var monthIndexes = {};	
		$.each(this.settings.monthsShort,function(index, item){
			monthIndexes[item] = index;
		});	
		
		this.$root.find('.data .item').each(function(eventIndex, item){
			
			
			var 
				location = $.trim($(this).find('.location').text()),
				itemTags = self.parseTags($(this).find('.tags').text()),
				description = $(this).find('.description').html(),
				time = $(this).find('.time').html();
				
			//append item tags to global array
			$.each(itemTags, function(index, item){
				if ($.inArray(item, tags) === -1) {
					tags[tags.length] = item;					
				}
			});
												
			var startDateArr = $(this).find('.date .start').text().split('-');
			var startDate = new Date();			
			startDate.setFullYear(startDateArr[2], monthIndexes[startDateArr[1]], startDateArr[0]);
			startDate = self.normalizeDate(startDate);
			
			var eventDates = [startDate];
			
			var endDate = null;
			
			if ($(this).find('.date .end').length > 0 && $.trim($(this).find('.date .end').text()) !== '') {
				var endDateArr = $(this).find('.date .end').text().split('-');
				endDate = new Date();			
				endDate.setFullYear(endDateArr[2], monthIndexes[endDateArr[1]], endDateArr[0]);
				endDate = self.normalizeDate(endDate);				
				
				var startTime = startDate.getTime();
				var endTime = endDate.getTime();
				var oneDayMilliseconds = 60*60*24*1000;				
				var diffDays = (endTime - startTime)/oneDayMilliseconds;
				
				var tDate;
				
				for (var i = 1; i <= diffDays; i++){
					tDate = new Date();					
					tDate.setTime(startTime + i * oneDayMilliseconds);
					eventDates[eventDates.length] = tDate;
				}
				
			}
			
			$.each(eventDates,function(index, date){
				var event = {
					id : eventIndex, //unique id for add - simply index in the array of all events  
					description : description,
					location : location,
					time : time,
					tags : itemTags,
					date : date,
					startDate : startDate,
					endDate : endDate
				};			

				items[items.length] = event;				
			});
		});	

		this.tags = tags;
		this.activeTags = [];
		
		//we no longer need data list
		this.$root.find('.data').remove();

		//sort items by date
		items.sort(function(a,b){
			if (a.date < b.date) {
				return -1;
			}
			if (a.date > b.date) {
				return 1;
			}
			return 0;
		});
		
		return items;
	};

	/* 
	 * Basically checks if any item tag is in the array of currently active tabs
	 */
	sfCalendar.prototype.isEventInActiveTags = function(item) {
		if (this.activeTags.length === 0) {
			return true;
		}
		
		var 
			self = this,
			flag = false;
			
		$.each(item.tags, function(index, tag){
			if ($.inArray(tag, self.activeTags) > -1) {
				flag = true;
				return false; //break out from the loop
			}
		});
		
		return flag;
	};
	
	/*
	 * Filters item set to return only these from a given month
	 */
	sfCalendar.prototype.fetchEventsForMonth = function(date){
		var 
			self = this,
			month = date.getMonth(),
			year = date.getFullYear();

		return $.grep(this.items, function(item){
			if (self.isEventInActiveTags(item) === false) {
				return false;
			}
			return (item.date.getMonth() === month && item.date.getFullYear() === year);
		});
	};
	
	/* 
	 * Return events for a particular day
	 */	
	sfCalendar.prototype.fetchEventsForDay = function(date){
		var 
			self = this,
			month = date.getMonth(),
			year = date.getFullYear(),
			day = date.getDate();
					
		return $.grep(this.items, function(item){
			if (self.isEventInActiveTags(item) === false) {
				return false;
			}			
			return (item.date.getMonth() === month && item.date.getFullYear() === year && item.date.getDate() === day);
		});
	};

	/*
	 * Returns all events between a particular date
	 */
	sfCalendar.prototype.fetchEventsForPeriod = function(startDate, endDate){
		var 
			self = this;
		
		startDate = this.normalizeDate(startDate);
		endDate = this.normalizeDate(endDate, true); //second parameter this will set at the very last moment of the day		
		
		return $.grep(this.items, function(item){
			if (self.isEventInActiveTags(item) === false) {
				return false;
			}			
			return (item.date.valueOf() >= startDate.valueOf() && item.date.valueOf() <= endDate.valueOf());
		});
	};
	
	/**
	 * Builds month navigation
	 */
	sfCalendar.prototype.navigation = function(){
		var 
			self = this,
			months = self.settings.months,
			startDate = arguments[0] || new Date();			
			
		//tabs
		this.$navigation = $('<p class="nav"/>').click(function(e){
			if (e.target.nodeName.toLowerCase() !== 'a') {
				return true;
			}
			
			e.preventDefault();
			var date = $(e.target).data('date');
						
			//rebuild tabs with new Date as a starter						
			self.navigation(date);
			self.rebuildCalendar(date);
			self.repopulateCalendar(date);			
			
			//reactive current selection
			var event = self.$calendar.data('selection');			
			$(document).trigger(event.type, event.data);
		});

		//builds previous and next links 
		var createLink = function(offset, className){
			var
				newDate = new Date(),
				newMonth = startDate.getMonth() + offset,
				newYear = startDate.getFullYear();
				
			if (newMonth < 0) {
				newMonth = newMonth + 12;
				newYear = newYear - 1;
			}
			//next year
			else if (newMonth > 11){
				newMonth = newMonth - 12;
				newYear = newYear + 1;
			}				
			
			newDate.setFullYear(newYear,newMonth,1);
			return $('<a href="#" class="' + className + '">' + months[newMonth] + '</a>').data('date',newDate);
		};
		
		var 
			$prev = createLink(-1, 'prev'),
			$next = createLink(1, 'next');
			
		this.$navigation.append($prev, document.createTextNode(' '+months[startDate.getMonth()]+' '+startDate.getFullYear()), $next);			

		//cleanup from previous navigation
		this.$aside.find('.nav').unbind('click').remove();

		//readd tabs
		this.$aside.prepend(this.$navigation);	
	};

	/* 
	 * Initializes listing 
	 */
	sfCalendar.prototype.listing = function(){
		var self = this;

		var updateListing = function(dataItems) {
			var 
				days = self.settings.days,
				months = self.settings.monthsShort;

			self.$events.empty(); //remove previous items
			
			//no results for given month
			if (dataItems.length === 0) {
				self.$events.append('<p class="empty">No events to show</p>');
				return;
			}

			var previousDate = null;
			
			$.each(dataItems, function(index, item){
				
				var isNewDate = false;

				//create a new date wrapper
				if (previousDate === null || previousDate.toString() !== item.date.toString()) {
					isNewDate = true;
					
					var 
						$wrapper = $('<div class="day"/>'),
						day = item.date.getDate();
						
					if (day < 10) {
						day = '0'+day;
					}
					$wrapper.append('<h4>'+months[item.date.getMonth()]+' <span>'+day+'</span></h4>');
					$wrapper.append('<p class="weekday">'+days[item.date.getDay()]+'</p>');
					
					self.$events.append($wrapper);
					previousDate = item.date;
				}
				
				var el = $('<div class="event"/>');

				//check if first item
				if (isNewDate === true) {
					el.addClass('event-first');
				}
				//title
				el.append('<h5>' + item.time + '</h5>' + item.description);
				
				if (item.endDate !== null){
					el.find('h5').after('<p class="dates">' + self.settings.months[item.startDate.getMonth()] + ' ' + item.startDate.getDate() + ', ' + item.startDate.getFullYear() + ' to ' + self.settings.months[item.endDate.getMonth()] + ' ' + item.endDate.getDate() + ', ' + item.endDate.getFullYear() + '</p>');
				}
				
				self.$events.find('.day:last').append(el);
								
				if (item.location) {			
					
					var $mapTrigger = $('<a href="#event-map-'+item.id+'">Map it!</a>').fancybox({
						frameWidth : 640,
						frameHeight : 450,
						hideOnContentClick : false
					});
					
					var $mapWrapper = $('<p class="map"/>').append($mapTrigger);
					el.append($mapWrapper);
					
					el.append('<div id="event-map-'+item.id+'" class="gmap-wrap"><div class="gmap-container"/></div>');		
					
					var $mapContainer = $('#event-map-'+item.id).find('.gmap-container').css({ width: 640, height: 450 });
										
					var map = new google.maps.Map2($mapContainer.get(0));
					var geocoder = new GClientGeocoder();
					geocoder.getLatLng(item.location, function(latlng){
						map.setCenter(latlng, 14);			
						var marker = new GMarker(latlng);
		        map.addOverlay(marker);
						map.setUIToDefault();						
					});					
				}
			});			
		};
		
		//update listing when date is change in the calendar
		$(document).bind('eventbox.setDay', function(event,date){
			self.$period.html('<span>Day:</span> ' + self.settings.months[date.getMonth()] + ' ' + date.getDate() + ', ' + date.getFullYear());
			updateListing(self.fetchEventsForDay(date));
		});
		
		$(document).bind('eventbox.setMonth', function(event,date){
			self.$period.html('<span>Month:</span> ' + self.settings.months[date.getMonth()] + ' ' + date.getFullYear());			
			updateListing(self.fetchEventsForMonth(date));
		});
		
		$(document).bind('eventbox.setWeek', function(event,startDate,endDate){
			self.$period.html('<span>Week:</span> ' + self.settings.months[startDate.getMonth()] + ' ' + startDate.getDate() + ' - '+ self.settings.months[endDate.getMonth()] + ' ' + endDate.getDate() + ', ' + startDate.getFullYear());						
			updateListing(self.fetchEventsForPeriod(startDate,endDate));
		});						
	};

	/*
	 * Tags filter list
	 */
	sfCalendar.prototype.tagsList = function(){
		var 
			self = this,
			$tagsList = $('<ul class="filter" />');		
		
		//function launched on checkbox click
		var triggerClick = function(e){
			var data = [];
			
			$tagsList.find('input:checked').each(function(){
				data[data.length] = $(this).data('tag');
			});
		
			self.activeTags = data;
			
			//update calendar widget
			self.repopulateCalendar(self.$calendar.data('date'));

			//refresh current selection
			var event = self.$calendar.data('selection');			
			$(document).trigger(event.type, event.data);			
		};
		
		$.each(this.tags, function(index, item){
			var
				$li = $('<li><label for="event-filter-'+index+'"> '+item+'</label></li>'),
				$checkbox = $('<input type="checkbox" id="event-filter-'+index+'" />').data('tag',item);
			
			$checkbox.click(triggerClick);
			
			$li.find('label').prepend($checkbox);
			$tagsList.append($li);
		});
		
		this.$aside.append($tagsList);		
//		var $tagsTrigger = $('<button type="button">Filter Calendar</button>'),			
//		$tagsTrigger.click(triggerClick);
//		this.$aside.append($tagsTrigger);		
	};
	
	/*
	 * Builds calendar widget
	 */
	sfCalendar.prototype.calendar = function(){
		var self = this;
		this.$calendar = $('<div class="calendar"/>');
		
		//catches clicks on the whole calendar widget
		this.$calendar.click(function(e){
			if (e.target.nodeName.toLowerCase() !== 'a') {
				return;
			}
			e.preventDefault();
			
			var $target = $(e.target);
				
			//month cell click
			if ($target.hasClass('month')) {
				$(document).trigger('eventbox.setMonth',[$target.data('date')]);
			}
			//week cell click
			else if ($target.hasClass('week')) {
				$(document).trigger('eventbox.setWeek',[$target.data('date.start'), $target.data('date.end')]);
			}
			//day cell click
			else {
				$(document).trigger('eventbox.setDay',[$target.data('date')]);
			}
		});
		
		//selecting whole month
		$(document).bind('eventbox.setMonth',function(event, date){	
			date = self.normalizeDate(date);
						
			self.$calendar.data('selection',{
				'type' : 'eventbox.setMonth',
				'data' : [date]
			});						
			
			if (date.valueOf() === self.$calendar.data('date').valueOf()) {
				self.$calendar.find('td a').addClass('selected');
			}
		});
		
		//selecting only one week
		$(document).bind('eventbox.setWeek',function(event, startDate, finishDate){
			self.$calendar.data('selection',{
				'type' : 'eventbox.setWeek',
				'data' : [startDate, finishDate]
			});			
			
			self.$calendar.find('td a').removeClass('selected');
			
			self.$calendar.find('tbody th a').each(function(){
				if ($(this).data('date.start').valueOf() === startDate.valueOf()) {
					$(this).parents('tr').find('td a').addClass('selected');
				}
			});
		});		
		
		//selecting a particular day
		$(document).bind('eventbox.setDay', function(event, date){
			
			self.$calendar.data('selection',{
				'type' : 'eventbox.setDay',
				'data' : [date]
			});
						
			self.$calendar.find('td a').removeClass('selected');
			
			self.$calendar.find('tbody td a').each(function(){
				if ($(this).data('date').valueOf() === date.valueOf()) {
					$(this).addClass('selected');
				}
			});
		});
		
		var now = new Date();
		
		this.rebuildCalendar(now);
		this.repopulateCalendar(now);
		this.$aside.append(this.$calendar);		
	};
	
	/*
	 * Fetches events for current month and marks them in the calendar
	 */
	sfCalendar.prototype.repopulateCalendar = function(date){
		var 
			tdate, weekday,
			self = this,
			dataItems = self.fetchEventsForMonth(date);
	
		tdate = date;
		tdate.setDate(1);
		weekday = tdate.getDay();
						
		this.$calendar.find('a.event').removeClass('event');
		
		$.each(dataItems, function(){
			var day = this.date.getDate() + weekday - 1; //we add weekday as it's the offset at the beginning of the calendar
			self.$calendar.find('td').eq(day).find('a').addClass('event');
		});				
	};
	
	/*
	 * Creates HTML for calendar widget
	 */ 
	sfCalendar.prototype.rebuildCalendar = function(date){
		
		date.setDate(1); //reset to the first day of passed month
		date = this.normalizeDate(date);
		
		var 
			weekday, rows, $row, $cell, $trigger, daysLimit, dayDate,
			counter = 0,
			displayedDay = 1,
			daysInMonth = [31,28,31,30,31,30,31,31,30,31,30,31],

			$table = $('<table summary="Calendar for ' + parseInt(date.getMonth() + 1, 10) + '.'+ date.getFullYear() +'"><thead/><tbody/></table>'),
			selectWeekStartDate, selectWeekEndDate;

		//cleanup
		this.$calendar.empty(); //remove previous items
		this.$calendar.data('date',date);
		
		if (date.getFullYear() % 4 === 0) {
			daysInMonth[1] = 29;
		}
		
		//weekday of the first day of current month
		weekday = date.getDay();
		daysLimit = daysInMonth[date.getMonth()] + weekday;
		rows = Math.ceil(daysLimit / 7);
		
		//header row
		$row = $('<tr />');
		$trigger = $('<a href="#" class="month">M</a>').data('date',date); //month changing trigger
		$cell = $('<th scope="col"/>');
		$row.append($cell.append($trigger));
		
		//day names
		$.each(this.settings.daysShort, function(){
			$row.append('<th scope="col">'+this+'</th>');
		});
		
		$table.find('thead').append($row);
		
		var firstDayOfMonth = false;
		
		for (var i = 0; i < rows; i++) {
			//week selecting row
			$row = $('<tr />');

			//cells
			for (var j = 0; j < 7; j++) {
				$cell = $('<td class="col' + parseInt(j+1,10) + '"/>');
				
				//existing day in month
				if (counter >= weekday && counter < daysLimit) {
					
					dayDate = new Date();
					dayDate.setMonth(date.getMonth());
					dayDate.setDate(displayedDay);
					dayDate.setFullYear(date.getFullYear());
					dayDate = this.normalizeDate(dayDate);
					
					//look for the first day in month/week
					if (firstDayOfMonth === false || j === 0) {
						selectWeekStartDate = dayDate;
					}
					firstDayOfMonth = true;
					
					//look for the last day of the week
					if (j === 6 || counter + 1 === daysLimit) { 
						selectWeekEndDate = dayDate;
					}
					
					$trigger = $('<a href="#">'+ displayedDay +'</a>').data('date', dayDate);
					$cell.append($trigger);
					displayedDay++;
				}
				else {
					$cell.html('&nbsp;');
				}
				
				counter++;
				$row.append($cell);
			}

			//week selection trigger			
			$cell = $('<th scope="row"/>');
			$trigger = $('<a href="#" class="week">W</a>').data('date.start',selectWeekStartDate).data('date.end',selectWeekEndDate);			
			$row.prepend($cell.append($trigger));
			
			$table.find('tbody').append($row);
		}

		this.$calendar.append($table);
	};
	
	$.fn.sfCalendar = function(){
		return $(this).each(function(){
			return new sfCalendar(this);
		});
	};
	
})(jQuery);


jQuery(function($){
	var Engine = {
		slider : function(){
			$('.slider-related').sfSlider({ display: 3	, width: 900, slider : '.wrap ul', items : '.wrap ul li' })
		},
		rotator : function(){
			$('.events-intro .media').sfRotator({ duration: 3000 });
		}
	};
	
	Engine.slider();
	Engine.rotator();
});

google.load("maps", "2.x");
google.setOnLoadCallback(function(){
	$('#events-a').sfCalendar({
		label : null
	});	
})
