/**
 * @author Vladimir Shushkov
 */

var calendarManager = {

	_locale: 'ru',

	_localeData: {
		days: {
			ru: ['Пн', 'Вт', 'Ср', 'Чт', 'Пт', 'Сб', 'Вс'],
			en: ['S', 'M', 'T', 'W', 'T', 'F', 'S']
		},
		month: {
			ru: ['Январь', 'Февраль', 'Март', 'Апрель', 'Май', 'Июнь', 'Июль', 'Август', 'Сентябрь', 'Октябрь', 'Ноябрь', 'Декабрь'],
			en: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December']
		},
		monthShort: {
			ru: ['Янв', 'Фев', 'Мар', 'Апр', 'Май', 'Июн', 'Июл', 'Авг', 'Сен', 'Окт', 'Ноя', 'Дек'],
			en: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
		},
		empty: {ru: 'ДД/ММ/ГГГГ', en: 'MM/DD/YYYY'},
		close: {ru: 'закрыть', en: 'close'},
		reset: {ru: 'сбросить', en: 'reset'}
	},

	_daysInMonth: [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31],

	_box: null,

	_controllers: {},

	_currentControllerId: null,

	/**
	 * @return object
	 */
	getCurrent: function()
	{
		return this._controllers[this._currentControllerId];
	},

	/**
	 * @return string
	 */
	generateId: function()
	{
		var count = 0, i;
		for (i in this._controllers) {
			count++;
		}
		return 'calendar-'+ count;
	},

	/**
	 * @return string
	 */
	getLocale: function()
	{
		return this._locale;
	},

	/**
	 * @return mixed
	 */
	getLocaleData: function(alias)
	{
		return this._localeData[alias][this.getLocale()];
	},

	/**
	 * @return mixed
	 */
	getOption: function(alias)
	{
		return this.getCurrent().options[alias] || null;
	},

	/**
	 * @return void
	 */
	setOption: function(alias, value)
	{
		this.getCurrent().options[alias] = value;
	},

	/**
	 * @return void
	 */
	switchCurrent: function(id)
	{
		this._currentControllerId = id;
	},

	/**
	 * @return object
	 */
	getController: function(id)
	{
		return this._controllers[id];
	},

	/**
	 * @return void
	 */
	addController: function(controller)
	{
		if (!controller.id) {
			controller.id = this.generateId();
		}

		var input = document.createElement('input');
		input.type = 'hidden';
		input.name = controller.name;
		input.value = controller.value;

		if (!!(window.attachEvent && !window.opera)) { // for IE
			input = document.createElement('<input type="hidden" name="'+ controller.name +'" value="'+ controller.value +'"/>');
		}

		controller.parentNode.appendChild(input);

		controller.name = '';
		controller.removeAttribute('name');

		var options = {};
		if (typeof controller.onclick == 'function') {
			try {
				options = controller.onclick();
			} catch (e) {
				console.log(e);
			}
			controller.onclick = null;
		}

		var self = this;
		addEvent(controller, 'click', function(e) {
			if (self.isOpened(this.id))  {
				self.close();
				return;
			}
			self.open(this.id);
		});

		addEvent(controller, 'keydown', function(e) {
			stopEvent(e);
			return false;
		});

		this._controllers[controller.id] = {
			id: controller.id,
			controller: controller,
			field: input,
			options: options,
			dateObject: new Date()
		};
	},

	/**
	 * @return Date
	 */
	parseSQLTimestamp: function(value)
	{
		var matches = /(\d{4})-(\d{2})-(\d{2})/.exec(value);
		return new Date(matches[1], matches[2]-1, matches[3]);
	},

	/**
	 * @return void
	 */
	setValue: function(date)
	{
		if (!date || date.getDay == undefined) {
			this.getCurrent().controller.value = this.getLocaleData('empty');
			this.getCurrent().field.value = '';
			return;
		}
		this._controllers[this._currentControllerId].dateObject = date;
		this.getCurrent().controller.value = date.strftime(this.getOption('formatLabel') || '%d.%m.%Y');
		this.getCurrent().field.value = date.strftime(this.getOption('formatValue') || '%Y-%m-%d');

		this.getCurrent().controller.focus();
		this.getCurrent().controller.blur();

		var relativeId = this.getOption('relativeCalendar') || false;
		if (relativeId) {
			var currentId = this.getCurrent().id,
				currentDate = this.getValue(),
				relativeDiff = parseInt(this.getOption('relativeDiff') || 0),
				minTtime = currentDate.getTime() + relativeDiff * 86400000;
				minDate = new Date(minTtime);

			this.switchCurrent(relativeId);
			this.setOption('minDate', minDate.strftime('%Y-%m-%d'));
			if (this.getValue().getTime() < minTtime) {
				this.setValue(minDate);
			}
			this.switchCurrent(currentId);
		}
	},

	/**
	 * @return Date
	 */
	getValue: function()
	{
		return this._controllers[this._currentControllerId].dateObject;
	},

	/**
	 * @return HTMLElement
	 */
	getBox: function()
	{
		if (this._box === null) {
			var string = '<div class="calendar-border"><table><tr><td style="white-space: nowrap"><select id="calendar-month">';
			for (var i=0, d=this.getLocaleData('month'), l=d.length; i < l; i++) {string += '<option value="'+ i +'" >'+ d[i] +'</option>';}
			string += '</select><select id="calendar-year">';
			var year = (new Date).getFullYear();
			for (i=year-1; i<=(year+1); i++) {string += "<option value='"+ i +"'>"+ i +"</option>";}
			string += '</td>';
			//string += '<td>';
			//string += '<img src="/images/template/ico_calendar_reset.gif" id="calendar-reset" title="'+ this.getLocaleData('reset') +'" alt="[r]"/>';
			//string += '<img src="/images/template/ico_calendar_close.gif" id="calendar-close"title="'+ this.getLocaleData('close') +'" alt="[x]"/>';
			//string += '</td>';
			string += '</tr><tr><td colspan="2" id="calendar-table"></td></tr></table></div>';


			var shadow = document.createElement('div');
			shadow.id = 'calendar-shadow';
			document.body.insertBefore(shadow, document.body.firstChild);
			this._box = document.createElement('div');
			document.body.insertBefore(this._box, shadow);

			this._box.className = 'date-calendar-box';
			this._box.innerHTML = string;

			var self = this;

			document.getElementById('calendar-month').onchange = function() {
				if (self.getValue().getMonth() !== this.options[this.selectedIndex].value) {
					self.setValue(new Date(self.getValue().getFullYear(),
						this.options[this.selectedIndex].value, self.getValue().getDate()), true);
					self.redrawCalendar();
				}
			};

			document.getElementById('calendar-year').onchange = function() {
				if (self.getValue().getFullYear() !== this.options[this.selectedIndex].value) {
					self.setValue(new Date(this.options[this.selectedIndex].value,
						self.getValue().getMonth(), self.getValue().getDate()), true);
					self.redrawCalendar();
				}
			};

			var closeBtn = document.getElementById('calendar-close');
			if (closeBtn) {
				closeBtn.onclick = function() {
					self.close();
				};
			}

			var resetBtn = document.getElementById('calendar-reset');
			if (resetBtn) {
				resetBtn.onclick = function() {
					self.setValue(null);
					self.close();
				};
			}

			this._box.elShadow = shadow;
			this._box.elTable = document.getElementById('calendar-table');
		}
		return this._box;
	},

	/**
	 * @return void
	 */
	redrawCalendar: function()
	{
		var box = this.getBox().elTable;

		while (box.firstChild) {
			box.removeChild(box.firstChild);
		}

		var table = box.appendChild(document.createElement('table'));
		table = table.appendChild(document.createElement('tbody'));
		var tr = table.appendChild(document.createElement('tr'));

		// рисуем шапку
		tr.className = 'header'; var td;
		for (var i=0, d=this.getLocaleData('days'), l=d.length; i < l; i++) {
			td = tr.appendChild(document.createElement('td'));
			td.innerHTML = d[i];
		}

		// уточняем кол-во дней в месяце
		if (this.isLeapYear(this.getValue().getFullYear())) {
			this._daysInMonth[1] = 29;
		}

		// узнаем каким днем недели будет первое число месяца
		var firstDay = new Date(this.getValue().getFullYear(), this.getValue().getMonth(), 1).getDay();
		if (this.getLocale() === 'ru') {
			firstDay = firstDay - 1;
			firstDay = firstDay < 0 ? 6 : firstDay;
		}

		var
			weekDay = 0,
			currentDate = 1,
			selectedDate = this.getValue().getDate(),
			daysTotal = this._daysInMonth[this.getValue().getMonth()];

		var minDate = this.getOption('minDate');
		if (minDate) {
			minDate = this.parseSQLTimestamp(minDate).getTime();
		}

		var daysLocale = this.getLocaleData('days');

		var self = this;
		var dayClickHandler = function() {
			var date = new Date(self.getValue().getFullYear(), self.getValue().getMonth(), this.innerHTML);
			self.setValue(date);
			self.close();
		};

		while (daysTotal >= currentDate) {

			tr = table.appendChild(document.createElement('TR'));
			tr.className = currentDate === 1 ? 'first' : '';

			for (weekDay = 0; weekDay < daysLocale.length; weekDay++) {
				td = tr.appendChild(document.createElement('TD'));

				if ((currentDate === 1 && firstDay > weekDay) || daysTotal < currentDate) {
					continue;
				}

				td.innerHTML = currentDate;

				td.className = currentDate === selectedDate ? 'selected' : '';

				if (this.getLocale() !== 'ru') {
					td.className += weekDay === 0 || weekDay === 6 ? ' weekend' : '';
				} else {
					td.className += weekDay === 5 || weekDay === 6 ? ' weekend' : '';
				}

				if (!minDate || minDate <= (new Date(this.getValue().getFullYear(), this.getValue().getMonth(), currentDate)).getTime()) {
					td.onclick = dayClickHandler;
				}
				else {
					td.className = i === 5 || i === 6 ? 'na-weekend' : 'na';
				}

				currentDate++;
			}
		}
		tr.className = 'last';

		this.getBox().elShadow.style.width = parseInt(this.getBox().style.width || 0) +'px';
		this.getBox().elShadow.style.height = parseInt(this.getBox().style.height || 0) +'px';

		document.getElementById('calendar-year').value = this.getValue().getFullYear();
		document.getElementById('calendar-month').value = this.getValue().getMonth();
	},

	/**
	 * @return boolean
	 */
	isLeapYear: function (year)
	{
		return (year%4)===0 ? ( (year%100===0) && (year%400!==0) ? false : true ) : false;
	},

	/**
	 * @return void
	 */
	open: function(id)
	{
		this.switchCurrent(id);
		this.redrawCalendar();
		this.getBox().style.display = 'block';
		this._setCalendarPosition();
	},

	/**
	 * @return void
	 */
	_setCalendarPosition: function()
	{
		var offset = getOffset(this.getCurrent().controller);

		if ((this.getBox().offsetWidth + offset.left) > document.body.offsetWidth) {
			offset.left = document.body.offsetWidth - this.getBox().offsetWidth - 10;
		}

		this.getBox().style.top =
			this.getBox().elShadow.style.top = (offset.top + (this.getCurrent().controller.offsetHeight || 0)) + 'px';
		this.getBox().style.left =
			this.getBox().elShadow.style.left = (offset.left) + 'px';
	},

	/**
	 * @return boolean
	 */
	isOpened: function(id)
	{
		if (id !== undefined) {
			return this.getBox().style.display === 'block' && id === this.getCurrent().controller.id;
		}
		return this.getBox().style.display === 'block';
	},

	/**
	 * @return void
	 */
	close: function()
	{
		if (!this._box) {
			return;
		}
		this.getBox().style.display =
			this.getBox().elShadow.style.display = 'none';
	},

	/**
	 * @return void
	 */
	run: function(container)
	{
		var inputs = (container || document).getElementsByTagName('input'), filtered = [];
		for (var i=0; i < inputs.length; i++) {
			if (/f\-calendar/.test(inputs[i].className)) {
				this.addController(inputs[i]);
				filtered.push(inputs[i].id);
			}
		}
		for (i=0; i < filtered.length; i++) {
			this.switchCurrent(filtered[i]);
			if (!this.getCurrent().field) {
				continue;
			}
			if (this.getCurrent().field.value) {
				this.setValue(this.parseSQLTimestamp(this.getCurrent().field.value));
			}
		}
	}
};

//http://stephencelis.com/projects/timeframe/
Date.parseToObject = function(string) {
	var date = Date.parse(string);
	if (!date) return null;
	date = new Date(date);
	return (date == 'Invalid Date' || date == 'NaN') ? null : date.neutral();
};

Date.prototype.strftime = function(format, utc) {
	var date = this;
	var day = date['get'+ (utc ? 'UTC' : '') +'Day'](),
		month = date['get'+ (utc ? 'UTC' : '') +'Month'](),
		hours = date['get'+ (utc ? 'UTC' : '') +'Hours'](),
		minutes = date['get'+ (utc ? 'UTC' : '') +'Minutes']();

	var pad = function(num) {
		return num.toPaddedString(2);
	};

	return format.gsub(/\%([aAbBcdHImMpSwyY])/, function(part) {
		switch(part[1]) {
			case 'a':return calendarManager.getLocaleData('days')[day].escapeHTML();break;
			case 'A':return calendarManager.getLocaleData('days')[day].escapeHTML();break;
			case 'b':return calendarManager.getLocaleData('monthShort')[month].escapeHTML();break;
			case 'B':return calendarManager.getLocaleData('month')[month].escapeHTML();break;
			case 'c':return date.toString();break;
			case 'd':return pad(date.getDate());break;
			case 'H':return pad(hours);break;
			case 'I':return (hours % 12 == 0) ? 12 : pad(hours % 12);break;
			case 'm':return pad(month + 1);break;
			case 'M':return pad(minutes);break;
			case 'p':return hours >= 12 ? 'PM' : 'AM';break;
			case 'S':return pad(date.getSeconds());break;
			case 'w':return day;break;
			case 'y':return pad(date.getFullYear() % 100);break;
			case 'Y':return date.getFullYear().toString();break;
		}
	});
};

Date.prototype.neutral = function() {
	return new Date(this.getFullYear(), this.getMonth(), this.getDate(), 12);
};

/**
 * times (prototypejs.org)
 */
String.prototype.times = function(count) {
    return count < 1 ? '' : new Array(count + 1).join(this);
};

/**
 * toPaddedString (prototypejs.org)
 */
Number.prototype.toPaddedString = function(length, radix) {
    var string = this.toString(radix || 10);
    return '0'.times(length - string.length) + string;
};

/**
 * gsub (prototypejs.org)
 */
String.prototype.gsub = function(pattern, replacement) {
	if (typeof replacement !== 'function') {
		return this;
	}

	var result = '', source = this, match, value;

	if (typeof pattern == 'string') {
		pattern = RegExp.escape(pattern);
	}
	if (!(pattern.length || pattern.source)) {
		replacement = replacement('');
		return replacement + source.split('').join(replacement) + replacement;
	}

	while (source.length > 0) {
		if ((match = source.match(pattern))) {
			result += source.slice(0, match.index);
			result += (value = replacement(match)) == null ? '' : value;
			source  = source.slice(match.index + match[0].length);
		} else {
			result += source, source = '';
		}
	}
	return result;
};


var userAgent = navigator.userAgent.toLowerCase();
var browser = {
	version: (userAgent.match(/.+(?:rv|it|ra|ie)[\/: ]([\d.]+)/) || [])[1],
	safari: /webkit/.test(userAgent),
	opera: /opera/.test(userAgent),
	msie: /msie/.test(userAgent) && !/opera/.test(userAgent),
	mozilla: /mozilla/.test(userAgent) && !/(compatible|webkit)/.test(userAgent)
};

if (typeof addEvent === 'undefined') {
	addEvent = function(el, type, fn) {
		if (el.attachEvent) {
			el['e'+ type + fn] = fn;
			el[type + fn] = function() {
				el['e'+ type + fn](window.event);
			};
			el.attachEvent('on'+ type, el[type+fn]);
		} else {
			el.addEventListener(type, fn, false);
		}
	};
}

if (typeof removeEvent === 'undefined') {
	removeEvent = function(el, type, fn) {
		if (el.detachEvent) {
			el.detachEvent('on'+ type, el[type + fn]);
			el[type + fn] = null;
		} else {
			el.removeEventListener(type, fn, false);
		}
	};
}

if (typeof stopEvent === 'undefined') {
	stopEvent = function(e) {

		if (document.all) {
			e.cancelBubble = false;
			e.returnValue = false;
			return;
		}

		e.preventDefault();
		e.stopPropagation();
		e.stopped = true;
	};
}

if (typeof getOffset === 'undefined') {
	getOffset = function(elem) {
		//~ if (!this[0]) error();
		var x = 0, y = 0, sl = 0, st = 0,
			parent = elem, op, parPos, elemPos = elem.style.position,
			mo = browser.mozilla, ie = browser.msie, oa = browser.opera,
			sf = browser.safari, sf3 = browser.safari && parseInt(browser.version) > 520,
			absparent = false, relparent = false;

		var num = function(el, prop) {
			return parseInt(el.style[prop])||0;
		};

		var handleOffsetReturn = function(elem, options, x, y, sl, st) {
			if ( !options.margin ) {
				x -= num(elem, 'marginLeft');
				y -= num(elem, 'marginTop');
			}

			// Safari and Opera do not add the border for the element
			if ( options.border && ((browser.safari && parseInt(browser.version) < 520) || browser.opera) ) {
				x += num(elem, 'borderLeftWidth');
				y += num(elem, 'borderTopWidth');
			} else if ( !options.border && !((browser.safari && parseInt(browser.version) < 520) || browser.opera) ) {
				x -= num(elem, 'borderLeftWidth');
				y -= num(elem, 'borderTopWidth');
			}

			if ( options.padding ) {
				x += num(elem, 'paddingLeft');
				y += num(elem, 'paddingTop');
			}

			// do not include scroll offset on the element ... opera sometimes reports scroll offset as actual offset
			if ( options.scroll && (!browser.opera || elem.offsetLeft != elem.scrollLeft && elem.offsetTop != elem.scrollLeft) ) {
				sl -= elem.scrollLeft;
				st -= elem.scrollTop;
			}

			return options.scroll ? {top: y - st, left: x - sl, scrollTop:  st, scrollLeft: sl}
								  : {top: y, left: x};
		};

		var options = arguments[1] || {};
		options = {
			margin: typeof options.margin == 'undefined' ? true : options.margin,
			border: typeof options.border == 'undefined' ? false : options.border,
			padding: typeof options.padding == 'undefined' ? false : options.padding,
			scroll: typeof options.scroll == 'undefined' ? true : options.scroll,
			relativeTo: typeof options.relativeTo == 'undefined' ? document.body : options.relativeTo
		}

		// Use offsetLite if lite option is true
		//~ if (options.lite) return this.offsetLite(options, returnObject);
		// Get the HTMLElement if relativeTo is a jquery collection
		//~ if (options.relativeTo.jquery) options.relativeTo = options.relativeTo[0];

		if (elem.tagName == 'BODY') {
			// Safari 2 is the only one to get offsetLeft and offsetTop properties of the body "correct"
			// Except they all mess up when the body is positioned absolute or relative
			x = elem.offsetLeft;
			y = elem.offsetTop;
			// Mozilla ignores margin and subtracts border from body element
			if (mo) {
				x += num(elem, 'marginLeft') + (num(elem, 'borderLeftWidth')*2);
				y += num(elem, 'marginTop')  + (num(elem, 'borderTopWidth') *2);
			} else
			// Opera ignores margin
			if (oa) {
				x += num(elem, 'marginLeft');
				y += num(elem, 'marginTop');
			} else
			// IE does not add the border in Standards Mode
			if ((ie && jQuery.boxModel)) {
				x += num(elem, 'borderLeftWidth');
				y += num(elem, 'borderTopWidth');
			} else
			// Safari 3 doesn't not include border or margin
			if (sf3) {
				x += num(elem, 'marginLeft') + num(elem, 'borderLeftWidth');
				y += num(elem, 'marginTop')  + num(elem, 'borderTopWidth');
			}
		} else {
			do {
				//~ parPos = $.css(parent, 'position');
				parPos = parent.style.position;

				x += parent.offsetLeft;
				y += parent.offsetTop;

				// Mozilla and IE do not add the border
				// Mozilla adds the border for table cells
				if ((mo && !parent.tagName.match(/^t[d|h]$/i)) || ie || sf3) {
					// add borders to offset
					x += num(parent, 'borderLeftWidth');
					y += num(parent, 'borderTopWidth');

					// Mozilla does not include the border on body if an element isn't positioned absolute and is without an absolute parent
					if (mo && parPos == 'absolute') absparent = true;
					// IE does not include the border on the body if an element is position static and without an absolute or relative parent
					if (ie && parPos == 'relative') relparent = true;
				}

				op = parent.offsetParent || document.body;
				if (options.scroll || mo) {
					do {
						if (options.scroll) {
							// get scroll offsets
							sl += parent.scrollLeft;
							st += parent.scrollTop;
						}

						// Opera sometimes incorrectly reports scroll offset for elements with display set to table-row or inline
						if (oa && (parent.style.display || '').match(/table-row|inline/)) {
							sl = sl - ((parent.scrollLeft == parent.offsetLeft) ? parent.scrollLeft : 0);
							st = st - ((parent.scrollTop == parent.offsetTop) ? parent.scrollTop : 0);
						}

						// Mozilla does not add the border for a parent that has overflow set to anything but visible
						if (mo && parent != elem && parent.style.overflow != 'visible') {
							x += num(parent, 'borderLeftWidth');
							y += num(parent, 'borderTopWidth');
						}

						parent = parent.parentNode;
					} while (parent != op);
				}
				parent = op;

				// exit the loop if we are at the relativeTo option but not if it is the body or html tag
				if (parent == options.relativeTo && !(parent.tagName == 'BODY' || parent.tagName == 'HTML'))  {
					// Mozilla does not add the border for a parent that has overflow set to anything but visible
					if (mo && parent != elem && arent.style.overflow != 'visible') {
						x += num(parent, 'borderLeftWidth');
						y += num(parent, 'borderTopWidth');
					}
					// Safari 2 and opera includes border on positioned parents
					if ( ((sf && !sf3) || oa) && parPos != 'static' ) {
						x -= num(op, 'borderLeftWidth');
						y -= num(op, 'borderTopWidth');
					}
					break;
				}
				if (parent.tagName == 'BODY' || parent.tagName == 'HTML') {
					// Safari 2 and IE Standards Mode doesn't add the body margin for elments positioned with static or relative
					if (((sf && !sf3) || (ie && (document.compatMode == "CSS1Compat"))) && elemPos != 'absolute' && elemPos != 'fixed') {
						x += num(parent, 'marginLeft');
						y += num(parent, 'marginTop');
					}
					// Safari 3 does not include the border on body
					// Mozilla does not include the border on body if an element isn't positioned absolute and is without an absolute parent
					// IE does not include the border on the body if an element is positioned static and without an absolute or relative parent
					if ( sf3 || (mo && !absparent && elemPos != 'fixed') ||
						 (ie && elemPos == 'static' && !relparent) ) {
						x += num(parent, 'borderLeftWidth');
						y += num(parent, 'borderTopWidth');
					}
					break; // Exit the loop
				}
			} while (parent);
		}

		var returnValue = handleOffsetReturn(elem, options, x, y, sl, st);
		return returnValue;
	};

}

calendarManager.run();

addEvent(document, 'click', function (e) {
	var el = e.target || e.srcElement;
	while (el && el.className !== 'f-calendar' && el.className !== 'date-calendar-box' && el.nodeName !== 'HTML') {
		el = el.parentNode;
	}
	if (el && el.nodeName === 'HTML' && calendarManager._box) {
		calendarManager.close();
	}
});

addEvent(window, 'resize', function (e) {
	calendarManager.close();
});
