/**
 * @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') {
			options = controller.onclick();
			controller.onclick = null;
		}
		
		var self = this;
		cm.addEvent(controller, 'click', function(e) {
			if (self.isOpened(this.id))  {
				self.close();
				return;
			}
			self.open(this.id);
		});

		cm.addEvent(controller, 'keydown', function(e) {
			cm.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');
		
		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><td><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></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();
				}
			};

			document.getElementById('calendar-close').onclick = function() {
				self.close();
			};

			document.getElementById('calendar-reset').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 =
			this.getBox().elShadow.style.display = 'block';
		this._setCalendarPosition();

	},
	
	/**
	 * @return void
	 */
	_setCalendarPosition: function()
	{
		var offset = cm.getOffset(this.getCurrent().controller);
		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()
	{
		var inputs = document.getElementsByTagName('input'), filtered = [];
		for (var i=0; i < inputs.length; i++) {
			if (/date\-field/.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));
			}
		}

		var self = this;
		cm.addEvent(document, 'click', function (e) {
			var el = e.target || e.srcElement;
			while (el.className !== 'date-field' && el.className !== 'date-calendar-box' && el.nodeName !== 'HTML') {
				el = el.parentNode;
			}
			if (el.nodeName === 'HTML' && self._box) {
				self.close();
			}
		});
	}
};

//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);
};

calendarManager.run();