/**
 * Bootstrap based calendar full view.
 *
 * https://github.com/Serhioromano/bootstrap-calendar
 *
 * User: Sergey Romanov <serg4172@mail.ru>
 */
"use strict";

// Date extension ------------------------------------------------------------------------------------------------------

/** Constants used for time computations */
Date.SECOND = 1000 /* milliseconds */;
Date.MINUTE = 60 * Date.SECOND;
Date.HOUR   = 60 * Date.MINUTE;
Date.DAY    = 24 * Date.HOUR;
Date.WEEK   =  7 * Date.DAY;

Date.prototype.getWeek = function() {
	var onejan = new Date(this.getFullYear(), 0, 1);
	return Math.ceil((((this.getTime() - onejan.getTime()) / 86400000) + onejan.getDay() + 1) / 7);
};
Date.prototype.getMonthFormatted = function() {
	var month = this.getMonth() + 1;
	return month < 10 ? '0' + month : month;
};
Date.prototype.getDateFormatted = function() {
	var date = this.getDate();
	return date < 10 ? '0' + date : date;
};

Date.prototype.getJalaliFullYear = function() {
	var gd = this.getDate();
	var gm = this.getMonth();
	var gy = this.getFullYear();
	var j = JalaliDate.gregorianToJalali(gy, gm+1, gd);
	return j[0];
}

Date.prototype.getJalaliMonth = function() {
	var gd = this.getDate();
	var gm = this.getMonth();
	var gy = this.getFullYear();
	var j = JalaliDate.gregorianToJalali(gy, gm+1, gd);
	return j[1];
}

Date.prototype.getJalaliDate = function() {
	var gd = this.getDate();
	var gm = this.getMonth();
	var gy = this.getFullYear();
	var j = JalaliDate.gregorianToJalali(gy, gm+1, gd);
	return j[2];
};

Date.prototype.getJalaliWeek = function() {
	var j = JalaliDate.jalaliToGregorian(this.getJalaliFullYear(), 1, 1);

	//First Saturday of the year
	var d = new Date(j[0], j[1]-1, j[2], 0, 0, 0);

	//Number of days after the first Saturday of the year
	var days = this.getJalaliDayOfYear() - ((7 - d.getJalaliDay()) % 7) - 1;

	if (days < 0) return new Date(this - this.getJalaliDay()*Date.DAY).getJalaliWeek();
	return Math.floor(days / 7) + 1;
};

Date.prototype.getJalaliDayOfYear = function() {
	var now = new Date(this.getFullYear(), this.getMonth(), this.getDate(), 0, 0, 0);
	var j = JalaliDate.jalaliToGregorian(this.getJalaliFullYear(), 1, 0);
	var then = new Date(j[0], j[1]-1, j[2], 0, 0, 0);
	var time = now - then;
	return Math.floor(time / Date.DAY);
};

Date.prototype.getJalaliDay = function() {
	var day = this.getDay();
	day = (day + 1) % 7;
	return day;
};

Date.prototype.setJalaliMonth = function(m, d) {
	var gd = this.getDate();
	var gm = this.getMonth();
	var gy = this.getFullYear();
	var j = JalaliDate.gregorianToJalali(gy, gm+1, gd);
	if (m > 12) {
		j[0] += Math.floor(m / 12);
		m = m % 12;
	}
	j[1] = m;
	if (d != undefined) j[2] = d;
	var g = JalaliDate.jalaliToGregorian(j[0], j[1], j[2]);
	return this.setFullYear(g[0], g[1]-1, g[2]);
};

Date.prototype.setJalaliFullYear = function(y, m, d) {
	var gd = this.getDate();
	var gm = this.getMonth();
	var gy = this.getFullYear();
	var j = JalaliDate.gregorianToJalali(gy, gm+1, gd);
	if (y < 100) y += 1300;
	j[0] = y;
	if (m != undefined) {
		if (m > 11) {
			j[0] += Math.floor(m / 12);
			m = m % 12;
		}
		j[1] = m + 1;
	}
	if (d != undefined) j[2] = d;
	var g = JalaliDate.jalaliToGregorian(j[0], j[1], j[2]);
	return this.setFullYear(g[0], g[1]-1, g[2]);
};

Date.prototype.setJalaliDate = function(d) {
	var gd = this.getDate();
	var gm = this.getMonth();
	var gy = this.getFullYear();
	var j = JalaliDate.gregorianToJalali(gy, gm+1, gd);
	j[2] = d;
	var g = JalaliDate.jalaliToGregorian(j[0], j[1], j[2]);
	return this.setFullYear(g[0], g[1]-1, g[2]);
};

// Date extension END --------------------------------------------------------------------------------------------------

// Jalali Date utils ---------------------------------------------------------------------------------------------------

var JalaliDate = {
	g_days_in_month: [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31],
	j_days_in_month: [31, 31, 31, 31, 31, 31, 30, 30, 30, 30, 30, 29]
};

JalaliDate.gregorianToJalali = function(g_y, g_m, g_d) {
	g_y = parseInt(g_y);
	g_m = parseInt(g_m);
	g_d = parseInt(g_d);
	var gy = g_y-1600;
	var gm = g_m-1;
	var gd = g_d-1;

	var g_day_no = 365*gy+parseInt((gy+3) / 4)-parseInt((gy+99)/100)+parseInt((gy+399)/400);

	for (var i=0; i < gm; ++i) //{
		g_day_no += JalaliDate.g_days_in_month[i];
	if (gm>1 && ((gy%4==0 && gy%100!=0) || (gy%400==0))) //{
	/* leap and after Feb */
		++g_day_no;
	g_day_no += gd;

	var j_day_no = g_day_no-79;

	var j_np = parseInt(j_day_no/ 12053);
	j_day_no %= 12053;

	var jy = 979+33*j_np+4*parseInt(j_day_no/1461);

	j_day_no %= 1461;

	if (j_day_no >= 366) {
		jy += parseInt((j_day_no-1)/ 365);
		j_day_no = (j_day_no-1)%365;
	}

	for (var i = 0; i < 11 && j_day_no >= JalaliDate.j_days_in_month[i]; ++i) {
		j_day_no -= JalaliDate.j_days_in_month[i];
	}
	var jm = i+1;
	var jd = j_day_no+1;


	return [jy, jm, jd];
	//}
	//}
};

JalaliDate.jalaliToGregorian = function(j_y, j_m, j_d) {

	j_y = parseInt(j_y);
	if (j_m > 12) j_y++;
	j_m = parseInt(j_m);
	j_d = parseInt(j_d);
	var jy = j_y-979;
	var jm = j_m-1;
	var jd = j_d-1;

	var j_day_no = 365*jy + parseInt(jy / 33)*8 + parseInt((jy%33+3) / 4);
	for (var i=0; i < jm; ++i) j_day_no += JalaliDate.j_days_in_month[i];

	j_day_no += jd;

	var g_day_no = j_day_no+79;

	var gy = 1600 + 400 * parseInt(g_day_no / 146097); /* 146097 = 365*400 + 400/4 - 400/100 + 400/400 */
	g_day_no = g_day_no % 146097;

	var leap = true;
	if (g_day_no >= 36525) /* 36525 = 365*100 + 100/4 */
	{
		g_day_no--;
		gy += 100*parseInt(g_day_no/  36524); /* 36524 = 365*100 + 100/4 - 100/100 */
		g_day_no = g_day_no % 36524;

		if (g_day_no >= 365)
			g_day_no++;
		else
			leap = false;
	}

	gy += 4*parseInt(g_day_no/ 1461); /* 1461 = 365*4 + 4/4 */
	g_day_no %= 1461;

	if (g_day_no >= 366) {
		leap = false;

		g_day_no--;
		gy += parseInt(g_day_no/ 365);
		g_day_no = g_day_no % 365;
	}

	for (var i = 0; g_day_no >= JalaliDate.g_days_in_month[i] + (i == 1 && leap); i++)
		g_day_no -= JalaliDate.g_days_in_month[i] + (i == 1 && leap);
	var gm = i+1;
	var gd = g_day_no+1;

	return [gy, gm, gd];
}

// Jalali Date utils  END ----------------------------------------------------------------------------------------------

if(!String.prototype.format) {
	String.prototype.format = function() {
		var args = arguments;
		return this.replace(/{(\d+)}/g, function(match, number) {
			return typeof args[number] != 'undefined' ? args[number] : match;
		});
	};
}
if(!String.prototype.formatNum) {
	String.prototype.formatNum = function(decimal) {
		var r = "" + this;
		while(r.length < decimal)
			r = "0" + r;
		return r;
	};
}

(function($) {

	var defaults = {
		// Width of the calendar
		width:              '100%',
		// Initial view (can be 'month', 'week', 'day')
		view:               'month',
		// Initial date. No matter month, week or day this will be a starting point. Can be 'now' or a date in format 'yyyy-mm-dd'
		day:                'now',
		// Day Start time and end time with time intervals. Time split 10, 15 or 30.
		time_start:         '06:00',
		time_end:           '22:00',
		time_split:         '30',
		// Source of events data. It can be one of the following:
		// - URL to return JSON list of events in special format.
		//   {success:1, result: [....]} or for error {success:0, error:'Something terrible happened'}
		//   events: [...] as described in events property description
		//   The start and end variables will be sent to this url
		// - A function that received the start and end date, and that
		//   returns an array of events (as described in events property description)
		// - An array containing the events
		events_source:      '',
		// Path to templates should end with slash /. It can be as relative
		// /component/bootstrap-calendar/tmpls/
		// or absolute
		// http://localhost/component/bootstrap-calendar/tmpls/
		tmpl_path:          'tmpls/',
		tmpl_cache:         true,
		classes:            {
			months: {
				inmonth:  'cal-day-inmonth',
				outmonth: 'cal-day-outmonth',
				saturday: 'cal-day-weekend',
				sunday:   'cal-day-weekend',
				holidays: 'cal-day-holiday',
				today:    'cal-day-today'
			},
			week:   {
				workday:  'cal-day-workday',
				saturday: 'cal-day-weekend',
				sunday:   'cal-day-weekend',
				holidays: 'cal-day-holiday',
				today:    'cal-day-today'
			}
		},
		// ID of the element of modal window. If set, events URLs will be opened in modal windows.
		modal:              null,
		//	modal handling setting, one of "iframe", "ajax" or "template"
		modal_type:         "iframe",
		//	function to set modal title, will be passed the event as a parameter
		modal_title:        null,
		views:              {
			year:  {
				slide_events: 1,
				enable:       1
			},
			month: {
				slide_events: 1,
				enable:       1
			},
			week:  {
				enable: 1
			},
			day:   {
				enable: 1
			}
		},
		merge_holidays:     false,
		// ------------------------------------------------------------
		// CALLBACKS. Events triggered by calendar class. You can use
		// those to affect you UI
		// ------------------------------------------------------------
		onAfterEventsLoad:  function(events) {
			// Inside this function 'this' is the calendar instance
		},
		onBeforeEventsLoad: function(next) {
			// Inside this function 'this' is the calendar instance
			next();
		},
		onAfterViewLoad:    function(view) {
			// Inside this function 'this' is the calendar instance
		},
		onAfterModalShown:  function(events) {
			// Inside this function 'this' is the calendar instance
		},
		onAfterModalHidden: function(events) {
			// Inside this function 'this' is the calendar instance
		},
		// -------------------------------------------------------------
		// INTERNAL USE ONLY. DO NOT ASSIGN IT WILL BE OVERRIDDEN ANYWAY
		// -------------------------------------------------------------
		events:             [],
		templates:          {
			year:  '',
			month: '',
			week:  '',
			day:   ''
		},
		stop_cycling:       false,
		type: 'Gregorian'
	};

	var defaults_extended = {
		first_day: 2,
		holidays:  {
			// January 1
			'01-01':  "New Year's Day",
			// Third (+3*) Monday (1) in January (01)
			'01+3*1': "Birthday of Dr. Martin Luther King, Jr.",
			// Third (+3*) Monday (1) in February (02)
			'02+3*1': "Washington's Birthday",
			// Last (-1*) Monday (1) in May (05)
			'05-1*1': "Memorial Day",
			// July 4
			'04-07':  "Independence Day",
			// First (+1*) Monday (1) in September (09)
			'09+1*1': "Labor Day",
			// Second (+2*) Monday (1) in October (10)
			'10+2*1': "Columbus Day",
			// November 11
			'11-11':  "Veterans Day",
			// Fourth (+4*) Thursday (4) in November (11)
			'11+4*4': "Thanksgiving Day",
			// December 25
			'25-12':  "Christmas"
		}
	};

	var strings = {
		error_noview:     'Calendar: View {0} not found',
		error_dateformat: 'Calendar: Wrong date format {0}. Should be either "now" or "yyyy-mm-dd"',
		error_loadurl:    'Calendar: Event URL is not set',
		error_where:      'Calendar: Wrong navigation direction {0}. Can be only "next" or "prev" or "today"',
		error_timedevide: 'Calendar: Time split parameter should divide 60 without decimals. Something like 10, 15, 30',

		no_events_in_day: 'No events in this day.',

		title_year:  '{0}',
		title_month: '{0} {1}',
		title_week:  'week {0} of {1}',
		title_day:   '{0} {1} {2}, {3}',

		week:        'Week {0}',
		all_day:     'All day',
		time:        'Time',
		events:      'Events',
		before_time: 'Ends before timeline',
		after_time:  'Starts after timeline',


		m0:  'January',
		m1:  'February',
		m2:  'March',
		m3:  'April',
		m4:  'May',
		m5:  'June',
		m6:  'July',
		m7:  'August',
		m8:  'September',
		m9:  'October',
		m10: 'November',
		m11: 'December',

		ms0:  'Jan',
		ms1:  'Feb',
		ms2:  'Mar',
		ms3:  'Apr',
		ms4:  'May',
		ms5:  'Jun',
		ms6:  'Jul',
		ms7:  'Aug',
		ms8:  'Sep',
		ms9:  'Oct',
		ms10: 'Nov',
		ms11: 'Dec',

		jm0:  'Farvardin',
		jm1:  'Ordibehest',
		jm2:  'Khordad',
		jm3:  'Tir',
		jm4:  'Mordad',
		jm5:  'Shahrivar',
		jm6:  'Mehr',
		jm7:  'Aban',
		jm8:  'Azar',
		jm9:  'Day',
		jm10: 'Bahman',
		jm11: 'Esfand',

		jms0:  'Farvardin',
		jms1:  'Ordibehest',
		jms2:  'Khordad',
		jms3:  'Tir',
		jms4:  'Mordad',
		jms5:  'Shahrivar',
		jms6:  'Mehr',
		jms7:  'Aban',
		jms8:  'Azar',
		jms9:  'Day',
		jms10: 'Bahman',
		jms11: 'Esfand',

		d0: 'Sunday',
		d1: 'Monday',
		d2: 'Tuesday',
		d3: 'Wednesday',
		d4: 'Thursday',
		d5: 'Friday',
		d6: 'Saturday'
	};

	var browser_timezone = '';
	try {
		if($.type(window.jstz) == 'object' && $.type(jstz.determine) == 'function') {
			browser_timezone = jstz.determine().name();
			if($.type(browser_timezone) !== 'string') {
				browser_timezone = '';
			}
		}
	}
	catch(e) {
	}

	function buildEventsUrl(events_url, data) {
		var separator, key, url;
		url = events_url;
		separator = (events_url.indexOf('?') < 0) ? '?' : '&';
		for(key in data) {
			url += separator + key + '=' + encodeURIComponent(data[key]);
			separator = '&';
		}
		return url;
	}

	function getExtentedOption(cal, option_name) {
		var fromOptions = (cal.options[option_name] != null) ? cal.options[option_name] : null;
		var fromLanguage = (cal.locale[option_name] != null) ? cal.locale[option_name] : null;
		if((option_name == 'holidays') && cal.options.merge_holidays) {
			var holidays = {};
			$.extend(true, holidays, fromLanguage ? fromLanguage : defaults_extended.holidays);
			if(fromOptions) {
				$.extend(true, holidays, fromOptions);
			}
			return holidays;
		}
		else {
			if(fromOptions != null) {
				return fromOptions;
			}
			if(fromLanguage != null) {
				return fromLanguage;
			}
			return defaults_extended[option_name];
		}
	}

	function getHolidays(cal, year) {
		var hash = [];
		var holidays_def = getExtentedOption(cal, 'holidays');
		for(var k in holidays_def) {
			hash.push(k + ':' + holidays_def[k]);
		}
		hash.push(year);
		hash = hash.join('|');
		if(hash in getHolidays.cache) {
			return getHolidays.cache[hash];
		}
		var holidays = [];
		$.each(holidays_def, function(key, name) {
			var firstDay = null, lastDay = null, failed = false;
			$.each(key.split('>'), function(i, chunk) {
				var m, date = null;
				if(m = /^(\d\d)-(\d\d)$/.exec(chunk)) {
					date = new Date(year, parseInt(m[2], 10) - 1, parseInt(m[1], 10));
				}
				else if(m = /^(\d\d)-(\d\d)-(\d\d\d\d)$/.exec(chunk)) {
					if(parseInt(m[3], 10) == year) {
						date = new Date(year, parseInt(m[2], 10) - 1, parseInt(m[1], 10));
					}
				}
				else if(m = /^easter(([+\-])(\d+))?$/.exec(chunk)) {
					date = getEasterDate(year, m[1] ? parseInt(m[1], 10) : 0);
				}
				else if(m = /^(\d\d)([+\-])([1-5])\*([0-6])$/.exec(chunk)) {
					var month = parseInt(m[1], 10) - 1;
					var direction = m[2];
					var offset = parseInt(m[3]);
					var weekday = parseInt(m[4]);
					switch(direction) {
						case '+':
							var d = new Date(year, month, 1 - 7);
							while(d.getDay() != weekday) {
								d = new Date(d.getFullYear(), d.getMonth(), d.getDate() + 1);
							}
							date = new Date(d.getFullYear(), d.getMonth(), d.getDate() + 7 * offset);
							break;
						case '-':
							var d = new Date(year, month + 1, 0 + 7);
							while(d.getDay() != weekday) {
								d = new Date(d.getFullYear(), d.getMonth(), d.getDate() - 1);
							}
							date = new Date(d.getFullYear(), d.getMonth(), d.getDate() - 7 * offset);
							break;
					}
				}
				if(!date) {
					warn('Unknown holiday: ' + key);
					failed = true;
					return false;
				}
				switch(i) {
					case 0:
						firstDay = date;
						break;
					case 1:
						if(date.getTime() <= firstDay.getTime()) {
							warn('Unknown holiday: ' + key);
							failed = true;
							return false;
						}
						lastDay = date;
						break;
					default:
						warn('Unknown holiday: ' + key);
						failed = true;
						return false;
				}
			});
			if(!failed) {
				var days = [];
				if(lastDay) {
					for(var date = new Date(firstDay.getTime()); date.getTime() <= lastDay.getTime(); date.setDate(date.getDate() + 1)) {
						days.push(new Date(date.getTime()));
					}
				}
				else {
					days.push(firstDay);
				}
				holidays.push({name: name, days: days});
			}
		});
		getHolidays.cache[hash] = holidays;
		return getHolidays.cache[hash];
	}

	getHolidays.cache = {};

	function warn(message) {
		if($.type(window.console) == 'object' && $.type(window.console.warn) == 'function') {
			window.console.warn('[Bootstrap-Calendar] ' + message);
		}
	}

	function Calendar(params, context) {
		this.options = $.extend(true, {position: {start: new Date(), end: new Date()}}, defaults, params);
		this.setLanguage(this.options.language);
		this.context = context;

		context.css('width', this.options.width).addClass('cal-context');

		this.view();
		return this;
	}

	Calendar.prototype.setOptions = function(object) {
		$.extend(this.options, object);
		if('language' in object) {
			this.setLanguage(object.language);
		}
		if('modal' in object) {
			this._update_modal();
		}
	}

	Calendar.prototype.setLanguage = function(lang) {
		if(window.calendar_languages && (lang in window.calendar_languages)) {
			this.locale = $.extend(true, {}, strings, calendar_languages[lang]);
			this.options.language = lang;
		} else {
			this.locale = strings;
			delete this.options.language;
		}
	}

	Calendar.prototype._render = function() {
		this.context.html('');
		this._loadTemplate(this.options.view);
		this.stop_cycling = false;

		var data = {};
		data.cal = this;
		data.day = 1;

		// Getting list of days in a week in correct order. Works for month and week views
		if(getExtentedOption(this, 'first_day') == 1) {
			data.days_name = [this.locale.d1, this.locale.d2, this.locale.d3, this.locale.d4, this.locale.d5, this.locale.d6, this.locale.d0]
		} else {
			data.days_name = [this.locale.d0, this.locale.d1, this.locale.d2, this.locale.d3, this.locale.d4, this.locale.d5, this.locale.d6]
		}

		// Get all events between start and end
		var start = parseInt(this.options.position.start.getTime());
		var end = parseInt(this.options.position.end.getTime());

		data.events = this.getEventsBetween(start, end);

		switch(this.options.view) {
			case 'month':
				break;
			case 'week':
				this._calculate_hour_minutes(data);
				break;
			case 'day':
				this._calculate_hour_minutes(data);
				break;
		}

		data.start = new Date(this.options.position.start.getTime());
		data.lang = this.locale;

		this.context.append(this.options.templates[this.options.view](data));
		this._update();
	};

	Calendar.prototype._calculate_hour_minutes = function(data) {
		var $self = this;
		var time_split = parseInt(this.options.time_split);
		var time_split_count = 60 / time_split;
		var time_split_hour = Math.min(time_split_count, 1);

		if(((time_split_count >= 1) && (time_split_count % 1 != 0)) || ((time_split_count < 1) && (1440 / time_split % 1 != 0))) {
			$.error(this.locale.error_timedevide);
		}

		var time_start = this.options.time_start.split(":");
		var time_end = this.options.time_end.split(":");
        var time_hour_duration = parseInt(time_end[0]) - parseInt(time_start[0]);

		data.hours = ((time_hour_duration == 0) ? 24 : time_hour_duration) * time_split_hour;
		var lines = data.hours * time_split_count - parseInt(time_start[1]) / time_split;
		var ms_per_line = (60000 * time_split);

		var start = new Date(this.options.position.start.getTime());
		start.setHours(time_start[0]);
		start.setMinutes(time_start[1]);
		var end = new Date(this.options.position.end.getTime());
		end.setHours(time_end[0]);
		end.setMinutes(time_end[1]);

		data.all_day = [];
		data.by_hour = [];
		data.after_time = [];
		data.before_time = [];
		$.each(data.events, function(k, e) {
			var s = new Date(parseInt(e.start));
			var f = new Date(parseInt(e.end));

			e.start_hour = s.getHours().toString().formatNum(2) + ':' + s.getMinutes().toString().formatNum(2);
			e.end_hour = f.getHours().toString().formatNum(2) + ':' + f.getMinutes().toString().formatNum(2);

			if(e.start < start.getTime()) {
				e.start_hour = s.getDate() + ' ' + $self.locale['ms' + s.getMonth()] + ' ' + e.start_hour;
			}

			if(e.end > end.getTime()) {
				e.end_hour = f.getDate() + ' ' + $self.locale['ms' + f.getMonth()] + ' ' + e.end_hour;
			}

			if(e.start < start.getTime() && e.end > end.getTime()) {
				data.all_day.push(e);
				return;
			}

			if(e.end < start.getTime()) {
				data.before_time.push(e);
				return;
			}

			if(e.start > end.getTime()) {
				data.after_time.push(e);
				return;
			}

			var event_start = start.getTime() - e.start;

			if(event_start >= 0) {
				e.top = 0;
			} else {
				e.top = Math.abs(event_start) / ms_per_line;
			}

			var lines_left = Math.abs(lines - e.top);
			var lines_in_event = (e.end - e.start) / ms_per_line;
			if(event_start >= 0) {
				lines_in_event = (e.end - start.getTime()) / ms_per_line;
			}


			e.lines = lines_in_event;
			if(lines_in_event > lines_left) {
				e.lines = lines_left;
			}

			data.by_hour.push(e);
		});

		//var d = new Date('2013-03-14 13:20:00');
		//warn(d.getTime());
	};

	Calendar.prototype._hour_min = function(hour) {
		var time_start = this.options.time_start.split(":");
		var time_split = parseInt(this.options.time_split);
		var in_hour = 60 / time_split;
		return (hour == 0) ? (in_hour - (parseInt(time_start[1]) / time_split)) : in_hour;
	};

	Calendar.prototype._hour = function(hour, part) {
		var time_start = this.options.time_start.split(":");
		var time_split = parseInt(this.options.time_split);
		var h = "" + (parseInt(time_start[0]) + hour * Math.max(time_split / 60, 1));
		var m = "" + (time_split * part + ((hour == 0) ? parseInt(time_start[1]) : 0));

		return h.formatNum(2) + ":" + m.formatNum(2);
	};

	Calendar.prototype._week = function(event) {
		this._loadTemplate('week-days');

		var t = {};
		var start = parseInt(this.options.position.start.getTime());
		var end = parseInt(this.options.position.end.getTime());
		var events = [];
		var self = this;
		var first_day = getExtentedOption(this, 'first_day');

		$.each(this.getEventsBetween(start, end), function(k, event) {
			event.start_day = new Date(parseInt(event.start)).getDay();
			if(first_day == 1) {
				event.start_day = (event.start_day + 6) % 7;
			}
			if((event.end - event.start) <= 86400000) {
				event.days = 1;
			} else {
				event.days = ((event.end - event.start) / 86400000);
			}

			if(event.start < start) {

				event.days = event.days - ((start - event.start) / 86400000);
				event.start_day = 0;
			}

			event.days = Math.ceil(event.days);

			if(event.start_day + event.days > 7) {
				event.days = 7 - (event.start_day);
			}

			events.push(event);
		});
		t.events = events;
		t.cal = this;
		return self.options.templates['week-days'](t);
	}

	Calendar.prototype._month = function(month) {
		this._loadTemplate('year-month');

		var t = {cal: this};
		var newmonth = month + 1;
		t.data_day = this.options.position.start.getFullYear() + '-' + (newmonth < 10 ? '0' + newmonth : newmonth) + '-' + '01';
		t.month_name = this.locale['m' + month];

		var curdate = new Date(this.options.position.start.getFullYear(), month, 1, 0, 0, 0);
		t.start = parseInt(curdate.getTime());
		t.end = parseInt(new Date(this.options.position.start.getFullYear(), month + 1, 1, 0, 0, 0).getTime());
		t.events = this.getEventsBetween(t.start, t.end);
		return this.options.templates['year-month'](t);
	}

	Calendar.prototype._day = function(week, day) {
		this._loadTemplate('month-day');

		var t = {tooltip: '', cal: this};
		var cls = this.options.classes.months.outmonth;

		var firstday = this.options.position.start.getDay();
		if(getExtentedOption(this, 'first_day') == 2) {
			firstday++;
		} else {
			firstday = (firstday == 0 ? 7 : firstday);
		}

		day = (day - firstday) + 1;
		var curdate = new Date(this.options.position.start.getFullYear(), this.options.position.start.getMonth(), day, 0, 0, 0);

		// if day of the current month
		if(day > 0) {
			cls = this.options.classes.months.inmonth;
		}
		// stop cycling table rows;
		var daysinmonth = (new Date(this.options.position.end.getTime() - 1)).getDate();
		if((day + 1) > daysinmonth) {
			this.stop_cycling = true;
		}
		// if day of the next month
		if(day > daysinmonth) {
			day = day - daysinmonth;
			cls = this.options.classes.months.outmonth;
		}

		cls = $.trim(cls + " " + this._getDayClass("months", curdate));

		if(day <= 0) {
			var daysinprevmonth = (new Date(this.options.position.start.getFullYear(), this.options.position.start.getMonth(), 0)).getDate();
			day = daysinprevmonth - Math.abs(day);
			cls += ' cal-month-first-row';
		}

		var holiday = this._getHoliday(curdate);
		if(holiday !== false) {
			t.tooltip = holiday;
		}

		t.data_day = curdate.getFullYear() + '-' + curdate.getMonthFormatted() + '-' + (day < 10 ? '0' + day : day);
		t.cls = cls;
		t.day = day;

		t.start = parseInt(curdate.getTime());
		t.end = parseInt(t.start + 86400000);
		t.events = this.getEventsBetween(t.start, t.end);
		return this.options.templates['month-day'](t);
	}

	Calendar.prototype._getHoliday = function(date) {
		var result = false;
		$.each(getHolidays(this, date.getFullYear()), function() {
			var found = false;
			$.each(this.days, function() {
				if(this.toDateString() == date.toDateString()) {
					found = true;
					return false;
				}
			});
			if(found) {
				result = this.name;
				return false;
			}
		});
		return result;
	};

	Calendar.prototype._getHolidayName = function(date) {
		var holiday = this._getHoliday(date);
		return (holiday === false) ? "" : holiday;
	};

	Calendar.prototype._getDayClass = function(class_group, date) {
		var self = this;
		var addClass = function(which, to) {
			var cls;
			cls = (self.options.classes && (class_group in self.options.classes) && (which in self.options.classes[class_group])) ? self.options.classes[class_group][which] : "";
			if((typeof(cls) == "string") && cls.length) {
				to.push(cls);
			}
		};
		var classes = [];
		if(date.toDateString() == (new Date()).toDateString()) {
			addClass("today", classes);
		}
		var holiday = this._getHoliday(date);
		if(holiday !== false) {
			addClass("holidays", classes);
		}
		switch(date.getDay()) {
			case 0:
				addClass("sunday", classes);
				break;
			case 6:
				addClass("saturday", classes);
				break;
		}

		addClass(date.toDateString(), classes);

		return classes.join(" ");
	};

	Calendar.prototype.setType = function (type) {
		if (type === 'Jalali' && this.options.type !== 'Jalali') {
			this.options.type = 'Jalali';
			jalalize();
			this.view(this.options.view);
		} else if (type === 'Gregorian' && this.options.type !== 'Gregorian') {
			this.options.type = 'Gregorian';
			unjalalize();
			this.view(this.options.view);
		}
	}

	Calendar.prototype.view = function(view) {
		if(view) {
			if(!this.options.views[view].enable) {
				return;
			}
			this.options.view = view;
		}


		this._init_position();
		this._loadEvents(function(calendar){
			calendar._render();
			calendar.options.onAfterViewLoad.call(this, calendar.options.view);
		});
		this._render();

		this.options.onAfterViewLoad.call(this, this.options.view);
	};

	Calendar.prototype.navigate = function(where, next) {
		var to = $.extend({}, this.options.position);
		if(where == 'next') {
			switch(this.options.view) {
				case 'year':
					to.start.setFullYear(this.options.position.start.getFullYear() + 1);
					break;
				case 'month':
					to.start.setMonth(this.options.position.start.getMonth() + 1);
					break;
				case 'week':
					to.start.setDate(this.options.position.start.getDate() + 7);
					break;
				case 'day':
					to.start.setDate(this.options.position.start.getDate() + 1);
					break;
			}
		} else if(where == 'prev') {
			switch(this.options.view) {
				case 'year':
					to.start.setFullYear(this.options.position.start.getFullYear() - 1);
					break;
				case 'month':
					to.start.setMonth(this.options.position.start.getMonth() - 1);
					break;
				case 'week':
					to.start.setDate(this.options.position.start.getDate() - 7);
					break;
				case 'day':
					to.start.setDate(this.options.position.start.getDate() - 1);
					break;
			}
		} else if(where == 'today') {
			to.start.setTime(new Date().getTime());
		}
		else {
			$.error(this.locale.error_where.format(where))
		}
		this.options.day = to.start.getFullYear() + '-' + to.start.getMonthFormatted() + '-' + to.start.getDateFormatted();
		this.view();
		if(_.isFunction(next)) {
			next();
		}
	};

	Calendar.prototype._init_position = function() {
		var year, month, day;

		if(this.options.day == 'now') {
			var date = new Date();
			year = date.getFullYear();
			month = date.getMonth();
			day = date.getDate();
		} else if(this.options.day.match(/^\d{4}-\d{2}-\d{2}$/g)) {
			var list = this.options.day.split('-');
			year = parseInt(list[0], 10);
			month = parseInt(list[1], 10) - 1;
			day = parseInt(list[2], 10);
		}
		else {
			$.error(this.locale.error_dateformat.format(this.options.day));
		}

		switch(this.options.view) {
			case 'year':
				this.options.position.start.setTime(new Date(year, 0, 1).getTime());
				this.options.position.end.setTime(new Date(year + 1, 0, 1).getTime());
				break;
			case 'month':
				this.options.position.start.setTime(new Date(year, month, 1).getTime());
				this.options.position.end.setTime(new Date(year, month + 1, 1).getTime());
				break;
			case 'day':
				this.options.position.start.setTime(new Date(year, month, day).getTime());
				this.options.position.end.setTime(new Date(year, month, day + 1).getTime());
				break;
			case 'week':
				var curr = new Date(year, month, day);
				var first;
				if(getExtentedOption(this, 'first_day') == 1) {
					first = curr.getDate() - ((curr.getDay() + 6) % 7);
				}
				else {
					first = curr.getDate() - curr.getDay();
				}
				this.options.position.start.setTime(new Date(year, month, first).getTime());
				this.options.position.end.setTime(new Date(year, month, first + 7).getTime());
				break;
			default:
				$.error(this.locale.error_noview.format(this.options.view))
		}
		return this;
	};

	Calendar.prototype.getTitle = function() {
		var p = this.options.position.start;
		switch(this.options.view) {
			case 'year':
				return this.locale.title_year.format(p.getFullYear());
				break;
			case 'month':
				return this.locale.title_month.format(this.locale['m' + p.getMonth()], p.getFullYear());
				break;
			case 'week':
				return this.locale.title_week.format(p.getWeek(), p.getFullYear());
				break;
			case 'day':
				return this.locale.title_day.format(this.locale['d' + p.getDay()], p.getDate(), this.locale['m' + p.getMonth()], p.getFullYear());
				break;
		}
		return;
	};

	Calendar.prototype.isToday = function() {
		var now = new Date().getTime();

		return ((now > this.options.position.start) && (now < this.options.position.end));
	}

	Calendar.prototype.getStartDate = function() {
		return this.options.position.start;
	}

	Calendar.prototype.getEndDate = function() {
		return this.options.position.end;
	}

	Calendar.prototype._loadEvents = function(callback) {
		var self = this;
		var source = null;
		if('events_source' in this.options && this.options.events_source !== '') {
			source = this.options.events_source;
		}
		else if('events_url' in this.options) {
			source = this.options.events_url;
			warn('The events_url option is DEPRECATED and it will be REMOVED in near future. Please use events_source instead.');
		}
		var loader;
		switch($.type(source)) {
			case 'function':
				loader = function(callback) {
					return callback(source(self.options.position.start, self.options.position.end, browser_timezone));
				};
				break;
			case 'array':
				loader = function(callback) {
					return callback([].concat(source));
				};
				break;
			case 'string':
				if(source.length) {
					loader = function(callback) {
						var events = [];
						var params = {from: self.options.position.start.getTime(), to: self.options.position.end.getTime()};
						if(browser_timezone.length) {
							params.browser_timezone = browser_timezone;
						}
						$.ajax({
							url:      buildEventsUrl(source, params),
							dataType: 'json',
							type:     'GET',
							async:    true
						}).done(function(json) {
							if(!json.success) {
								$.error(json.error);
							}
							if(json.result) {
								events = json.result;
							}
							callback(events);
						});
					};
				}
				break;
		}
		if(!loader) {
			$.error(this.locale.error_loadurl);
		}
		this.options.onBeforeEventsLoad.call(this, function() {
			loader(function(events){
				self.options.events = events;

				self.options.events.sort(function(a, b) {
					var delta;
					delta = a.start - b.start;
					if(delta == 0) {
						delta = a.end - b.end;
					}
					return delta;
				});
				self.options.onAfterEventsLoad.call(self, self.options.events);
				callback(self);
			});
		});
	};

	Calendar.prototype._templatePath = function(name) {
		if(typeof this.options.tmpl_path == 'function') {
			return this.options.tmpl_path(name)
		}
		else {
			return this.options.tmpl_path + name + '.html';
		}
	};

	Calendar.prototype._loadTemplate = function(name) {
		if(this.options.templates[name]) {
			return;
		}
		var self = this;
		$.ajax({
			url:      self._templatePath(name),
			dataType: 'html',
			type:     'GET',
			async:    false,
			cache:    this.options.tmpl_cache
		}).done(function(html) {
			self.options.templates[name] = _.template(html);
		});
	};

	Calendar.prototype._update = function() {
		var self = this;

		$('*[data-toggle="tooltip"]').tooltip({container: 'body'});

		$('*[data-cal-date]').click(function() {
			var view = $(this).data('cal-view');
			self.options.day = $(this).data('cal-date');
			self.view(view);
		});
		$('.cal-cell').dblclick(function() {
			var view = $('[data-cal-date]', this).data('cal-view');
			self.options.day = $('[data-cal-date]', this).data('cal-date');
			self.view(view);
		});

		this['_update_' + this.options.view]();

		this._update_modal();

	};

	Calendar.prototype._update_modal = function() {
		var self = this;


		$('a[data-event-id]', this.context).unbind('click');

		if (!$('a[data-event-id]', this.context).attr("data-event-class") == "XiboFormButton")
			return;

		$('a[data-event-id]', this.context).on('click', function(event) {
			event.preventDefault();
			event.stopPropagation();

			XiboFormRender($(this).attr('href'));
		});
	};

	Calendar.prototype._update_day = function() {
		$('#cal-day-panel').height($('#cal-day-panel-hour').height());
	};

	Calendar.prototype._update_week = function() {
	};

	Calendar.prototype._update_year = function() {
		this._update_month_year();
	};

	Calendar.prototype._update_month = function() {
		this._update_month_year();

		var self = this;

		var week = $(document.createElement('div')).attr('id', 'cal-week-box');
		var start = this.options.position.start.getFullYear() + '-' + this.options.position.start.getMonthFormatted() + '-';
		$('.cal-month-box .cal-row-fluid')
			.on('mouseenter', function() {
				var p = new Date(self.options.position.start);
				var child = $('.cal-cell1:first-child .cal-month-day', this);
				var day = (child.hasClass('cal-month-first-row') ? 1 : $('[data-cal-date]', child).text());
				p.setDate(parseInt(day));
				day = (day < 10 ? '0' + day : day);
				week.html(self.locale.week.format(p.getWeek()));
				week.attr('data-cal-week', start + day).show().appendTo(child);
			})
			.on('mouseleave', function() {
				week.hide();
			})
		;

		week.click(function() {
			self.options.day = $(this).data('cal-week');
			self.view('week');
		});

		$('a.event').mouseenter(function() {
			$('a[data-event-id="' + $(this).data('event-id') + '"]').closest('.cal-cell1').addClass('day-highlight dh-' + $(this).data('event-class'));
		});
		$('a.event').mouseleave(function() {
			$('div.cal-cell1').removeClass('day-highlight dh-' + $(this).data('event-class'));
		});
	};

	Calendar.prototype._update_month_year = function() {
		if(!this.options.views[this.options.view].slide_events) {
			return;
		}
		var self = this;
		var activecell = 0;
		var downbox = $(document.createElement('div')).attr('id', 'cal-day-tick').html('<i class="icon-chevron-down glyphicon glyphicon-chevron-down"></i>');

		$('.cal-month-day, .cal-year-box .span3')
			.on('mouseenter', function() {
				if($('.events-list', this).length == 0) return;
				if($(this).children('[data-cal-date]').text() == self.activecell) return;
				downbox.show().appendTo(this);
			})
			.on('mouseleave', function() {
				downbox.hide();
			})
			.on('click', function(event) {
				if($('.events-list', this).length == 0) return;
				if($(this).children('[data-cal-date]').text() == self.activecell) return;
				showEventsList(event, downbox, slider, self);
			})
		;

		var slider = $(document.createElement('div')).attr('id', 'cal-slide-box');
		slider.hide().click(function(event) {
			event.stopPropagation();
		});

		this._loadTemplate('events-list');

		downbox.click(function(event) {
			showEventsList(event, $(this), slider, self);
		});
	};

	Calendar.prototype.getEventsBetween = function(start, end) {
		var events = [];
		$.each(this.options.events, function() {
			if(this.start == null) {
				return true;
			}
			var event_end = this.end || this.start;
			if((parseInt(this.start) < end) && (parseInt(event_end) >= start)) {
				events.push(this);
			}
		});
		return events;
	};

	// Jalali Calendar extension -----------------------------------------------------------------------------------------

	var stash = {};
	function jalalize () {

		stash['getTitle'] = Calendar.prototype.getTitle;
		Calendar.prototype.getTitle = function () {
			var p = this.options.position.start;
			switch(this.options.view) {
				case 'year':
					return this.locale.title_year.format(p.getJalaliFullYear());
					break;
				case 'month':
					return this.locale.title_month.format(this.locale['jm' + (p.getJalaliMonth() - 1)], p.getJalaliFullYear());
					break;
				case 'week':
					return this.locale.title_week.format(p.getWeek() + 40, p.getJalaliFullYear());
					break;
				case 'day':
					return this.locale.title_day.format(this.locale['d' + p.getDay()], p.getJalaliDate(), this.locale['jm' + (p.getJalaliMonth() - 1)], p.getJalaliFullYear());
					break;
			}
			return;
		};

		stash['_month'] = Calendar.prototype._month;
		Calendar.prototype._month = function(month) {
			this._loadTemplate('year-month');

			var t = {cal: this};
			t.month_name = this.locale['jm' + month];

			var jStart = JalaliDate.jalaliToGregorian(this.options.position.start.getJalaliFullYear(), month +1, 1);
			var jStop = JalaliDate.jalaliToGregorian(this.options.position.start.getJalaliFullYear(), month + 2, 1);
			t.data_day = jStart[0] + '-' + (jStart[1] < 10 ? '0' + jStart[1] : jStart[1]) + '-' + (jStart[2] < 10 ? '0' + jStart[2] : jStart[2]);
			var curdate = new Date(jStart[0], jStart[1] - 1, jStart[2], 0, 0, 0);
			t.start = parseInt(curdate.getTime());
			t.end = parseInt(new Date(jStop[0], jStop[1] - 1, jStop[2], 0, 0, 0).getTime());
			t.events = this.getEventsBetween(t.start, t.end);
			return this.options.templates['year-month'](t);
		};

		stash['_day'] = Calendar.prototype._day;
		Calendar.prototype._day = function(week, day) {
			this._loadTemplate('month-day');

			var t = {tooltip: '', cal: this};
			var cls = this.options.classes.months.outmonth;

			var firstday = this.options.position.start.getDay();
			if(getExtentedOption(this, 'first_day') == 2) {
				firstday++;
			} else {
				firstday = (firstday == 0 ? 7 : firstday);
			}

			day = (day - firstday) + 1;
			var curdate = new Date(this.options.position.start.getFullYear(),
				this.options.position.start.getMonth(),
				this.options.position.start.getDate() + day - 1);

			// if day of the current month
			if(day > 0) {
				cls = this.options.classes.months.inmonth;
			}
			// stop cycling table rows;
			var daysinmonth = (new Date(this.options.position.end.getTime() - 1)).getJalaliDate();
			if((day + 1) > daysinmonth) {
				this.stop_cycling = true;
			}
			// if day of the next month
			if(day > daysinmonth) {
				day = day - daysinmonth;
				cls = this.options.classes.months.outmonth;
			}

			cls = $.trim(cls + " " + this._getDayClass("months", curdate));
			//console.log(curdate);

			if(day <= 0) {
				var daysinprevmonth = JalaliDate.j_days_in_month[new Date(this.options.position.start.getTime() - 1).getJalaliMonth() - 1];
				day = daysinprevmonth - Math.abs(day);
				cls += ' cal-month-first-row';
			}

			var holiday = this._getHoliday(curdate);
			if(holiday !== false) {
				t.tooltip = holiday;
			}

			t.data_day = curdate.getFullYear() + '-' + curdate.getMonthFormatted() + '-' + (curdate.getDate() < 10 ? '0' + curdate.getDate() : curdate.getDate());
			t.cls = cls;
			t.day = day;

			t.start = parseInt(curdate.getTime());
			t.end = parseInt(t.start + 86400000);
			t.events = this.getEventsBetween(t.start, t.end);
			return this.options.templates['month-day'](t);
		};

		stash['_init_position'] = Calendar.prototype._init_position;
		Calendar.prototype._init_position = function () {

			var year, month, day;

			if(this.options.day == 'now') {
				var date = new Date();
				year = date.getFullYear();
				month = date.getMonth();
				day = date.getDate();

			} else if(this.options.day.match(/^\d{4}-\d{2}-\d{2}$/g)) {
				var list = this.options.day.split('-');
				year = parseInt(list[0], 10);
				month = parseInt(list[1], 10);// - 1;
				day = parseInt(list[2], 10);
			}
			else {
				$.error(this.locale.error_dateformat.format(this.options.day));
			}

			var todayJal = JalaliDate.gregorianToJalali(year, month, day);
			var startJal, stopJal, startJalInGreg, stopJalInGreg;
			switch(this.options.view) {
				case 'year':
					startJal = [todayJal[0], 1, 1];
					stopJal = [todayJal[0] + 1, 1, 1];
					startJalInGreg = JalaliDate.jalaliToGregorian(startJal[0], startJal[1], startJal[2]);
					stopJalInGreg = JalaliDate.jalaliToGregorian(stopJal[0], stopJal[1], stopJal[2]);
					this.options.position.start.setTime(new Date(startJalInGreg[0], startJalInGreg[1] - 1, startJalInGreg[2]).getTime());
					this.options.position.end.setTime(new Date(stopJalInGreg[0], stopJalInGreg[1] - 1, stopJalInGreg[2]).getTime());
					//console.log('start: ' + this.options.position.start + ', stop:' + this.options.position.end);
					break;
				case 'month':
					startJal = [todayJal[0], todayJal[1], 1];
					stopJal = [todayJal[0], todayJal[1] + 1, 1];
					startJalInGreg = JalaliDate.jalaliToGregorian(startJal[0], startJal[1], startJal[2]);
					stopJalInGreg = JalaliDate.jalaliToGregorian(stopJal[0], stopJal[1], stopJal[2]);
					this.options.position.start.setTime(new Date(startJalInGreg[0], startJalInGreg[1] - 1, startJalInGreg[2]).getTime());
					this.options.position.end.setTime(new Date(stopJalInGreg[0], stopJalInGreg[1] - 1, stopJalInGreg[2]).getTime());

					break;
				case 'day':
					this.options.position.start.setTime(new Date(year, month - 1, day).getTime());
					this.options.position.end.setTime(new Date(year, month - 1, day + 1).getTime());
					break;
				case 'week':
					var curr = new Date(year, month - 1, day);
					var first;
					if(getExtentedOption(this, 'first_day') == 1) {
						first = curr.getDate() - ((curr.getDay() + 6) % 7);
					} else {
						first = curr.getDate() - curr.getDay();
					}
					this.options.position.start.setTime(new Date(year, month - 1, first).getTime());
					this.options.position.end.setTime(new Date(year, month - 1, first + 7).getTime());
					break;
				default:
					$.error(this.locale.error_noview.format(this.options.view))
			}
			return this;
		};

		stash['navigate'] = Calendar.prototype.navigate;
		Calendar.prototype.navigate = function(where, next) {
			var to = $.extend({}, this.options.position);
			if(where == 'next') {
				switch(this.options.view) {
					case 'year':
						to.start.setJalaliFullYear(this.options.position.start.getJalaliFullYear() + 1);
						break;
					case 'month':
						to.start.setJalaliMonth(this.options.position.start.getJalaliMonth() + 1);
						break;
					case 'week':
						to.start.setDate(this.options.position.start.getDate() + 7);
						break;
					case 'day':
						to.start.setDate(this.options.position.start.getDate() + 1);
						break;
				}
			} else if(where == 'prev') {
				switch(this.options.view) {
					case 'year':
						to.start.setJalaliFullYear(this.options.position.start.getJalaliFullYear() - 1);
						break;
					case 'month':
						var prevMonth = this.options.position.start.getJalaliMonth() - 1;
						to.start.setJalaliMonth(prevMonth ? prevMonth : 12);
						if (!prevMonth) {
							to.start.setJalaliFullYear(to.start.getJalaliFullYear() - 1);
						}
						break;
					case 'week':
						to.start.setDate(this.options.position.start.getDate() - 7);
						break;
					case 'day':
						to.start.setDate(this.options.position.start.getDate() - 1);
						break;
				}
			} else if(where == 'today') {
				to.start.setTime(new Date().getTime());
			} else {
				$.error(this.locale.error_where.format(where))
			}
			this.options.day = to.start.getFullYear() + '-' + to.start.getMonthFormatted() + '-' + to.start.getDateFormatted();
			this.view();
			if(_.isFunction(next)) {
				next();
			}
		};

		stash['_update_month'] = Calendar.prototype._update_month;
		Calendar.prototype._update_month = function() {
			this._update_month_year();

			var self = this;

			var week = $(document.createElement('div')).attr('id', 'cal-week-box');

			// gregorian string representation of statr of jalali week
			var start;

			$('.cal-month-box .cal-row-fluid')

				.on('mouseenter', function() {
					var p = new Date(self.options.position.start); // clone month start date
					//console.log('p1: ' + p);
					var child = $('.cal-cell1:first-child .cal-month-day', this);

					// jalali date of first day in hovered week
					var day = (child.hasClass('cal-month-first-row') ? 1 : $('[data-cal-date]', child).text());
					//console.log('day: ' + day);

					p.setJalaliDate(parseInt(day));
					//console.log('p2: ' + p);


					week.html(self.locale.week.format(p.getJalaliWeek()));

					// set data-cal-week to gregorian representation of start of jalali week
					var jalaliWeekStartInGregorian = JalaliDate.jalaliToGregorian(p.getJalaliFullYear(), p.getJalaliMonth(), day);

					//console.log('weel start source: ' + p.getJalaliFullYear() + ' ' + p.getJalaliMonth() + ' ' + day);
					//console.log('week start: ' + jalaliWeekStartInGregorian);
					week.attr('data-cal-week', jalaliWeekStartInGregorian[0] + '-' +
					(jalaliWeekStartInGregorian[1] < 10 ? '0' + (jalaliWeekStartInGregorian[1]) : jalaliWeekStartInGregorian[1]) + '-' +
					(jalaliWeekStartInGregorian[2] < 10 ? '0' + jalaliWeekStartInGregorian[2] : jalaliWeekStartInGregorian[2]))
						.show().appendTo(child);
				})
				.on('mouseleave', function() {
					week.hide();
				})
			;

			week.click(function() {
				self.options.day = $(this).data('cal-week');
				self.view('week');
			});

			$('a.event').mouseenter(function() {
				$('a[data-event-id="' + $(this).data('event-id') + '"]').closest('.cal-cell1').addClass('day-highlight dh-' + $(this).data('event-class'));
			});
			$('a.event').mouseleave(function() {
				$('div.cal-cell1').removeClass('day-highlight dh-' + $(this).data('event-class'));
			});
		};

		stash['_calculate_hour_minutes'] = Calendar.prototype._calculate_hour_minutes;
		Calendar.prototype._calculate_hour_minutes = function(data) {
			var $self = this;
			var time_split = parseInt(this.options.time_split);
			var time_split_count = 60 / time_split;
			var time_split_hour = Math.min(time_split_count, 1);

			if(((time_split_count >= 1) && (time_split_count % 1 != 0)) || ((time_split_count < 1) && (1440 / time_split % 1 != 0))) {
				$.error(this.locale.error_timedevide);
			}

			var time_start = this.options.time_start.split(":");
			var time_end = this.options.time_end.split(":");

			data.hours = (parseInt(time_end[0]) - parseInt(time_start[0])) * time_split_hour;
			var lines = data.hours * time_split_count - parseInt(time_start[1]) / time_split;
			var ms_per_line = (60000 * time_split);

			var start = new Date(this.options.position.start.getTime());
			start.setHours(time_start[0]);
			start.setMinutes(time_start[1]);
			var end = new Date(this.options.position.end.getTime());
			end.setHours(time_end[0]);
			end.setMinutes(time_end[1]);

			data.all_day = [];
			data.by_hour = [];
			data.after_time = [];
			data.before_time = [];
			$.each(data.events, function(k, e) {
				var s = new Date(parseInt(e.start));
				var f = new Date(parseInt(e.end));

				e.start_hour = s.getHours().toString().formatNum(2) + ':' + s.getMinutes().toString().formatNum(2);
				e.end_hour = f.getHours().toString().formatNum(2) + ':' + f.getMinutes().toString().formatNum(2);

				if(e.start < start.getTime()) {
					e.start_hour = s.getJalaliDate() + ' ' + $self.locale['jms' + (s.getJalaliMonth() - 1)] + ' ' + e.start_hour;
				}

				if(e.end > end.getTime()) {
					e.end_hour = f.getJalaliDate() + ' ' + $self.locale['jms' + (s.getJalaliMonth() - 1)] + ' ' + e.end_hour;
				}

				if(e.start < start.getTime() && e.end > end.getTime()) {
					data.all_day.push(e);
					return;
				}

				if(e.end < start.getTime()) {
					data.before_time.push(e);
					return;
				}

				if(e.start > end.getTime()) {
					data.after_time.push(e);
					return;
				}

				var event_start = start.getTime() - e.start;

				if(event_start >= 0) {
					e.top = 0;
				} else {
					e.top = Math.abs(event_start) / ms_per_line;
				}

				var lines_left = Math.abs(lines - e.top);
				var lines_in_event = (e.end - e.start) / ms_per_line;
				if(event_start >= 0) {
					lines_in_event = (e.end - start.getTime()) / ms_per_line;
				}


				e.lines = lines_in_event;
				if(lines_in_event > lines_left) {
					e.lines = lines_left;
				}

				data.by_hour.push(e);
			});

			//var d = new Date('2013-03-14 13:20:00');
			//warn(d.getTime());
		};
	}

	function unjalalize (calendar) {
		for (var f in stash) {
			Calendar.prototype[f] = stash[f];
		}
	}

	// Jalali Calendar extension end -------------------------------------------------------------------------------------

	function showEventsList(event, that, slider, self) {

		event.stopPropagation();

		var that = $(that);
		var cell = that.closest('.cal-cell');
		var row = cell.closest('.cal-before-eventlist');
		var tick_position = cell.data('cal-row');

		that.fadeOut('fast');

		slider.slideUp('fast', function() {
			var event_list = $('.events-list', cell);
			slider.html(self.options.templates['events-list']({
				cal:    self,
				events: self.getEventsBetween(parseInt(event_list.data('cal-start')), parseInt(event_list.data('cal-end')))
			}));
			row.after(slider);
			self.activecell = $('[data-cal-date]', cell).text();
			$('#cal-slide-tick').addClass('tick' + tick_position).show();
			slider.slideDown('fast', function() {
				$('body').one('click', function() {
					slider.slideUp('fast');
					self.activecell = 0;
				});
			});
		});



		// Wait 400ms before updating the modal & attach the mouseenter&mouseleave(400ms is the time for the slider to fade out and slide up)
		setTimeout(function() {
			$('a.event-item').mouseenter(function() {
				$('a[data-event-id="' + $(this).data('event-id') + '"]').closest('.cal-cell1').addClass('day-highlight dh-' + $(this).data('event-class'));
			});
			$('a.event-item').mouseleave(function() {
				$('div.cal-cell1').removeClass('day-highlight dh-' + $(this).data('event-class'));
			});
			self._update_modal();
		}, 400);
	}

	function getEasterDate(year, offsetDays) {
		var a = year % 19;
		var b = Math.floor(year / 100);
		var c = year % 100;
		var d = Math.floor(b / 4);
		var e = b % 4;
		var f = Math.floor((b + 8) / 25);
		var g = Math.floor((b - f + 1) / 3);
		var h = (19 * a + b - d - g + 15) % 30;
		var i = Math.floor(c / 4);
		var k = c % 4;
		var l = (32 + 2 * e + 2 * i - h - k) % 7;
		var m = Math.floor((a + 11 * h + 22 * l) / 451);
		var n0 = (h + l + 7 * m + 114)
		var n = Math.floor(n0 / 31) - 1;
		var p = n0 % 31 + 1;
		return new Date(year, n, p + (offsetDays ? offsetDays : 0), 0, 0, 0);
	}

	$.fn.calendar = function(params) {
		if (params.type === 'Jalali') {
			jalalize(calendar);
		}
		return new Calendar(params, this);
	}
}(jQuery));
