/*
 Class:
 DatePicker
 
 Requirements:
 prototype 1.6.0.3
 scriptaculous >= 1.8.1
 Copyright (c) 2008 Ralph Senger

 Author: Ralph Senger

 DatePicker is freely distributable under the terms of an MIT-style license.

*/

Date.prototype.toFormat = function(options){
	
	options = Object.extend({ format: 'd.m.Y H:i:s' }, options || {});
	
	var myDay = this.getDate() < 10 ? '0'.concat(this.getDate()) : this.getDate();
	var myMonth = (this.getMonth() + 1) < 10 ? '0'.concat(this.getMonth() + 1) : (this.getMonth() + 1);
	var myYear = this.getFullYear().toString();
	
	var myHour = this.getHours() < 10 ? '0'.concat(this.getHours()) : this.getHours();
	var myMin = this.getMinutes() < 10 ? '0'.concat(this.getMinutes()) : this.getMinutes();
	var mySec = this.getSeconds() < 10 ? '0'.concat(this.getSeconds()) : this.getSeconds();
	  
	var retStr = options.format;									// Default: "d.m.Y H:i:s"
	
	retStr = retStr.replace(/U/, this.getTime() / 1000);			// Seconds since the Unix Epoch (January 1 1970 00:00:00 GMT)
	
	retStr = retStr.replace(/d/, myDay);							// Day of the month, 2 digits with leading zeros
	retStr = retStr.replace(/j/, this.getDate());					// Day of the month without leading zeros
	retStr = retStr.replace(/m/, myMonth);							// Numeric representation of a month, with leading zeros
	retStr = retStr.replace(/n/, this.getMonth() + 1);				// Numeric representation of a month, without leading zeros
	retStr = retStr.replace(/Y/, myYear);							// A full numeric representation of a year, 4 digits
	retStr = retStr.replace(/y/, myYear.slice(2, 4));				// A two digit representation of a year
	 
	retStr = retStr.replace(/H/, myHour);							// 24-hour format of an hour with leading zeros
	retStr = retStr.replace(/i/, myMin);							// Minutes with leading zeros
	retStr = retStr.replace(/s/, mySec);							// Seconds, with leading zeros

	return retStr;
	}


if(!DatePicker) {
	
	var DatePicker = Class.create({
		
		initialize: function(_eElement, options) {
		
			this.parent = $(_eElement);
			this.now	= new Date();
			
			// for calculating later the correct position
			this.parentMeasures = Object.extend(this.parent.cumulativeOffset(), this.parent.getDimensions());
			this.viewportMeasures = Object.extend(document.viewport.getScrollOffsets(), document.viewport.getDimensions());
			
			// default options
			this.options = Object.extend({
				name: 'myCalendar',
				onOpen: Prototype.emptyFunction,		// callback on opening
				onClose: Prototype.emptyFunction,		// callback on close
				type: 'element',						// element, center, string
				yearStart: -5,
				yearEnd: 5,
				date: null,
				format: 'd.m.Y',
				text: {
					header: $w('Mo Di Mi Do Fr Sa So'),
					days: $w('Montag Dienstag Mittwoch Donnerstag Freitag Samstag Sonntag'),
					months: $w('Januar Februar März April Mai Juni Juli August September Oktober November Dezember'),
					labels: {
						today: 'Heute',
						submit: 'OK'
						}
					},
				_ePrev: new Element('div', { 'class': 'dpPrevMonth' }).update('<'),
				_eNext: new Element('div', { 'class': 'dpNextMonth' }).update('>')
				}, options || {});
			
			// get all texts for the labels
			this.labels = this.options.text;
			
			// not forget which date was choosen before
			var selection = this._getDateFromString(this._getValueFromElement(this.parent));
			
			// our current date object to work with and saving the choosen date for delivery
			// get the selection, default is now
			if (selection != null)
				this.selectedDate = new Date(selection);
			else
				if (this.options.date != null && this.options.date > 0)
					this.selectedDate = new Date(this.options.date);
				else
					this.selectedDate = new Date(this.now);
			
			// open the calendar where we choosed the date
			this.calendarDate = new Date(this.selectedDate);
			
			// Container for the list of refreshable HTML-Elements
			this.containerDays = new Array();
			
			// for correct stopObserving store that function in a variable
			this.clickOutsideListener = this._checkClickOutside.bindAsEventListener(this);
			
			// show the calender
			this.openCalendar();
			},
			
			
			
		createCalendar: function(){
			// the Container for the Calendar
			var _eContainer = new Element('div', { 'class': 'dpContainer', 'id': this.options.name.concat('ID') });
			var _eFormular = new Element('form', { 'action': 'javascript:void(0);', 'name': this.options.name });
			var _eSelectors = new Element('div', { 'class': 'dpSelectors' });
			
			// SelectBox for the years
			var _eYears = new Element('select', { 'name': 'myYear', 'id': 'myYear' });
			// create range from the absolute start to absolute end
			var start = this.calendarDate.getFullYear() + this.options.yearStart;
			var end = this.calendarDate.getFullYear() + this.options.yearEnd;
			this._fillSelectbox(_eYears, $R(Math.min(start, end), Math.max(start, end)), this.calendarDate.getFullYear(), 'iterator');
			
			// SelectBox for the months
			var _eMonths = new Element('select', { 'name': 'myMonth', 'id': 'myMonth' });
			this._fillSelectbox(_eMonths, this.labels.months, this.calendarDate.getMonth());
			
			// adding the go-prev/next-month-buttons
			_eFormular.insert(this.options._ePrev.observe('click', this._prevMonth.bindAsEventListener(this)));
			_eFormular.insert(this.options._eNext.observe('click', this._nextMonth.bindAsEventListener(this)));
			
			// adding and observing the two SelectBoxes
			_eFormular.insert(_eMonths.observe('change', this._selectMonth.bindAsEventListener(this)));
			_eFormular.insert(_eYears.observe('change', this._selectYear.bindAsEventListener(this)));
			
			// making a workable form
			_eFormular.insert(new Element('input', { 'type': 'hidden', 'name': this.options.name.concat('Date'), 'id': this.options.name.concat('Date'), 'value': '' }));
			_eSelectors.insert(_eFormular);
			
			// Header with weekdays shortcuts
			var _eTHeader = new Element('div', { 'class': 'dpHeader' });
			this.labels.header.each(
				function(day){
					_eTHeader.insert(new Element('div', { 'class': 'dpHead' }).update(day));
					}.bind(this));
			
			// Body with weekdays
			var _eTBody = new Element('ul', { 'class': 'dpBody' });
			
			// now we need a date object where we can iterate over
			this.iteratorDate = new Date(this.calendarDate);
			this.iteratorDate.setDate(1);
			this.iteratorDate.setHours(12);				// Prevent daylight savings time boundaries from showing a duplicate day
			
			var preDays =  this.iteratorDate.getDay();	// draw some days before the fact
			if (preDays < 3)
				preDays += 7;
			
			this.iteratorDate.setDate(1 - preDays + 1);
			
			// every displayed day:
			$R(0, 41).each(
				function(number){
					
					var currentDate = new Date(this.iteratorDate);
					
					// displaying the calendar day and collect the reference for later updating
					var day = currentDate.toFormat({ format: 'd' });
					var _eDay = new Element('li').update(day);
					
					// update the day
					this._createCalendarDay(_eDay, currentDate);
					
					// remind the day-element for later changing
					this.containerDays.push(_eDay);
					
					// insert the element an bind event to it
					_eTBody.insert(_eDay);
					_eDay.observe('click', this._dayObserver.bindAsEventListener(this, _eDay));
					_eDay.observe('mouseover', this._dayObserver.bindAsEventListener(this, _eDay));
					_eDay.observe('mouseout', this._dayObserver.bindAsEventListener(this, _eDay));
					
					// preparing date object for the next iteration step
					this.iteratorDate.setDate(currentDate.getDate() + 1);
					}.bind(this));
			
			// link for 'today'
			var _eToday = new Element('a', { 'href': 'javascript:void(0);', 'class': 'dpLinkToday' }).update(this.options.text.labels.today).observe('click', this._selectToday.bindAsEventListener(this));
			
			// link for 'Okay'
			var _eSubmit = new Element('a', { 'href': 'javascript:void(0);', 'class': 'dpLinkSubmit' }).update(this.options.text.labels.submit).observe('click', this._submitDate.bindAsEventListener(this));
			
			// canvas for hover result
			var _eCanvas = new Element('div', { 'id': this.options.name.concat('Canvas'), 'class': 'dpCanvas' }).update('&nbsp;');
			
			// Composing all elements
			_eContainer.insert(_eSelectors);
			_eContainer.insert(_eTHeader);
			_eContainer.insert(_eTBody);
			_eContainer.insert(_eToday);
			_eContainer.insert(_eSubmit);
			_eContainer.insert(_eCanvas);
			
			// update the Canvas
			_eCanvas.innerHTML = this.calendarDate.toFormat({ format: this.options.format });
			
			// watch the outside click for closing
			Event.observe(document, 'mousedown', this.clickOutsideListener);
			
			return _eContainer;
			},
			
			
			
		openCalendar: function(){
		
			// ie likes that more
			var body = $(document.body);
		
			// getting the calendar html object
			this._eCalendar = this.createCalendar();
			
			// call the callback
			this.options.onOpen.bind(this)(this.calendarDate);
			
			// showing the calendar considering the wished display mode
			switch (this.options.type){
			
				case 'element':
					// insert the html object without showing it
					body.insert({ bottom: this._eCalendar.setOpacity(0) });
					
					// to avoid layout problems
					this._eCalendar.absolutize();
					
					// never position the element out of the viewport
					var top = Math.max(this.viewportMeasures.top, Math.min(this.parentMeasures.top + this.parentMeasures.height, this.viewportMeasures.height - this._eCalendar.getHeight()));
					var left = Math.max(this.viewportMeasures.left, Math.min(this.parentMeasures.left, this.viewportMeasures.width - this._eCalendar.getWidth()));
					
					// set the div at position
					this._eCalendar.setStyle({ top: top + 'px', left: left + 'px' });
					
					// let's start the calendar
					new Effect.Appear(this._eCalendar, { delay: 0, duration: 0.5 });
					break;
				
				
				case 'center':
					// insert the html object without showing it
					body.insert({ bottom: this._eCalendar.setOpacity(0) });
					
					// to avoid layout problems and to centering it on the page
					this._eCalendar.absolutize();
					
					// calculate the center position
					myViewPortCenter = this._getCenter(document.viewport.getDimensions());
					myCalendarCenter = this._getCenter({ width: this._eCalendar.getWidth(), height: this._eCalendar.getHeight() });
					
					// set the div at position
					this._eCalendar.setStyle({ top: myViewPortCenter.y - myCalendarCenter.y + 'px', left: myViewPortCenter.x - myCalendarCenter.x + 'px' });
					
					// let's start the calendar
					new Effect.Appear(this._eCalendar, { delay: 0, duration: 0.5 });
					break;
				
				
				default:
					// nothing to do
				}
				
			},
			
			
			
		shutdownCalendar: function(){
		
			// call the callback
			this.options.onClose.bind(this)(this.calendarDate);
			
			// delivering the freight
			switch (this.parent.tagName){
				case 'INPUT':
				case 'SELECT':
				case 'OPTION':
				case 'TEXTAREA':
					$(this.parent).value = this.calendarDate.toFormat({ format: this.options.format });
					break;
				
				default:
					this.parent.innerHTML = this.calendarDate.toFormat({ format: this.options.format });
					break;
				}
			
			this.closeCalendar();
			},
			
			
			
		closeCalendar: function(){
			
			// stop EventListeners
			Event.stopObserving(document, 'mousedown', this.clickOutsideListener);
		
			// create the calender in the background
			switch (this.options.type){
				
				case 'element':
					new Effect.Fade(this._eCalendar, { delay: 0, duration: 0.5, afterFinish: function(){ this._eCalendar.remove() }.bind(this) });
					break;
				
				case 'center':
					new Effect.Fade(this._eCalendar, { delay: 0, duration: 0.5, afterFinish: function(){ this._eCalendar.remove() }.bind(this) });
					break;
				
				default:
					// nothing
				}
			},
			
			
			
		_dayObserver: function(event){
			// getting the argumesnts
			var args = $A(arguments);
			args.shift();
			
			var _eDay = args[0];
			var canvasDate = new Date(this.calendarDate);
			
			switch (event.type){
				case 'click':
					this._selectDay(_eDay.myDate);
					this.shutdownCalendar();
					break;
					
				case 'mouseover':
					_eDay.addClassName('dpDayHover');
					break;
					
				case 'mouseout':
					_eDay.removeClassName('dpDayHover');
					break;
				}
			
			},
			
			
		_checkClickOutside: function(event){
			if (!Event.element(event).descendantOf(this._eCalendar))
				this.closeCalendar();
			},
			
			
			
		_refreshCalendar: function(){
			
			this.iteratorDate = new Date(this.calendarDate);
			this.iteratorDate.setDate(1);
			this.iteratorDate.setHours(12);				// Prevent daylight savings time boundaries from showing a duplicate day
			
			var preDays =  this.iteratorDate.getDay();	// draw some days before the fact
			if (preDays < 3)
				preDays += 7;
			
			this.iteratorDate.setDate(1 - preDays + 1);
			
			// every displayed day:
			this.containerDays.each(
				function(_eDay){
					
					var currentDate = new Date(this.iteratorDate);
					
					// update the day
					this._createCalendarDay(_eDay, currentDate);
					
					// preparing for the next iteration
					this.iteratorDate.setDate(currentDate.getDate() + 1);
					}.bind(this));
				
			// setting up the actual date
			$(this.options.name.concat('Date')).value = this.calendarDate.toFormat({ format: this.options.format });
			
			// update the Canvas
			$(this.options.name.concat('Canvas')).innerHTML = this.calendarDate.toFormat({ format: this.options.format });
			},
			
			
			
		_createCalendarDay: function(_eDay, currentDate){
			var styleClasses = 'dpDay';
			var weekDay = currentDate.getDay();
			
			// detecting dates from previews/next month
			if (currentDate.getMonth() != this.calendarDate.getMonth())
				styleClasses = styleClasses.concat(' dpNotInActualMonth');
			
			// detecting weekends
			if (weekDay % 7 == 6 || weekDay % 7 == 0)
				styleClasses = styleClasses.concat(' dpWeekend');
			
			if (weekDay % 7 == 0)
				styleClasses = styleClasses.concat(' dpSunday');
			
			// detecting today
			if (currentDate.toFormat({ format: 'd.m.Y' }) == this.now.toFormat({ format: 'd.m.Y' }))
				styleClasses = styleClasses.concat(' dpToday');
			
			
			// detecting selected day
			if (currentDate.toFormat({ format: 'd.m.Y' }) == this.selectedDate.toFormat({ format: 'd.m.Y' }))
				styleClasses = styleClasses.concat(' dpSelectedDay');
			
			// displaying the calendar day and collect the reference for later updating
			var day = currentDate.toFormat({ format: 'd' });
			_eDay.update(day);
			_eDay.removeClassName('dpNotInActualMonth');
			_eDay.removeClassName('dpWeekend');
			_eDay.removeClassName('dpSunday');
			_eDay.removeClassName('dpToday');
			_eDay.removeClassName('dpDay');
			_eDay.removeClassName('dpSelectedDay');
			
			_eDay.addClassName(styleClasses);
			
			// remind the date which is shown in this element
			_eDay.myDate = new Date(currentDate);
			},
			
			
						
		_selectYear: function(){
			this.calendarDate.setFullYear($F('myYear'));
			
			// is the user reaching the selectboxes upper or lower end?
			if ($F('myYear') >= this.selectedDate.getMonth() + this.options.yearEnd - 1){
				_eOptions = $('myYear').childElements();
				_eOptions.each( function(element){ element.remove(); } );
				var start = this.calendarDate.getFullYear() + this.options.yearStart;
				var end = this.calendarDate.getFullYear() + this.options.yearEnd;
				this._fillSelectbox($('myYear'), $R(Math.min(start, end), Math.max(start, end)), this.calendarDate.getFullYear(), 'iterator');
				}
			else if ($F('myYear') <= this.selectedDate.getMonth() + this.options.yearStart + 1){
				_eOptions = $('myYear').childElements();
				_eOptions.each( function(element){ element.remove(); } );
				var start = this.calendarDate.getFullYear() + this.options.yearStart;
				var end = this.calendarDate.getFullYear() + this.options.yearEnd;
				this._fillSelectbox($('myYear'), $R(Math.min(start, end), Math.max(start, end)), this.calendarDate.getFullYear(), 'iterator');
				} 
			
			this._refreshCalendar();
			},
			
			
			
		_selectMonth: function(){
			this.calendarDate.setMonth($F('myMonth'));
			this._refreshCalendar();
			},
			
			
			
		_prevMonth: function(){
			var newMonth = this.calendarDate.getMonth() - 1;
			
			// are we in the next year?
			if (newMonth > this.calendarDate.getMonth())
				this.calendarDate.setFullYear(this.calendarDate.getFullYear() - 1); 

			this.calendarDate.setMonth(newMonth);
			
			// after calculating, setting up the viewport
			$('myYear').value = this.calendarDate.getFullYear();
			$('myMonth').value = this.calendarDate.getMonth();
			
			this._refreshCalendar();
			},
			
			
			
		_nextMonth: function(){
			var newMonth = this.calendarDate.getMonth() + 1;
			
			// are we in the next year?
			if (newMonth < this.calendarDate.getMonth())
				this.calendarDate.setFullYear(this.calendarDate.getFullYear() + 1); 
			
			this.calendarDate.setMonth(newMonth);
			
			// after calculating, setting up the viewport
			$('myYear').value = this.calendarDate.getFullYear();
			$('myMonth').value = this.calendarDate.getMonth();
			
			this._refreshCalendar();
			},
			
			
			
		_selectDay: function(myDate){
			// setting the selected day
			this.calendarDate = new Date(myDate);
			
			// setting up the actual date
			$(this.options.name.concat('Date')).value = this.calendarDate.toFormat({ format: this.options.format });
			},
			
			
			
		_selectToday: function(){
			// setting today
			this.calendarDate = new Date(this.now);
			
			$('myYear').value = this.calendarDate.getFullYear();
			$('myMonth').value = this.calendarDate.getMonth();
			
			// close the calender
			this.shutdownCalendar();
			},
			
			
			
		_submitDate: function(){
			// close the calender
			this.shutdownCalendar();
			},
			
			
			
		_getCenter: function(options){
			options = Object.extend({ width: 0, height: 0 }, options || {});
			
			return Object({
				x: options.width * 0.5,
				y: options.height * 0.5
				});
			},
			
			
			
		_fillSelectbox: function(_eParent, entries, preSelected, selectMode){
			entries.each(
				function(iterator, index){
					var selection = new Object();
					
					switch (selectMode){
						case 'iterator':
							var value = iterator;
							if (iterator == preSelected)
								selection = { 'selected': 'selected' };	
							break;
						
						default:
							var value = index;
							if (index == preSelected)
								selection = { 'selected': 'selected' };
						}
					
					var attributes = Object.extend({ 'value': value }, selection);
					
					_eParent.insert(new Element('option', attributes).update(iterator));
					}.bind(this));
			},
			
			
			
		_getDateFromString: function(stringDate){
		
			if (stringDate != '') {
			
				var i = 0;
				var sp = 0;
				var splittedDate = $H({ 'd': 1, 'm': 1, 'Y': 1970, 'H': 0, 'i': 0, 's': 0 });
				
				// now we're trying to get a date object from the string
				for (i = 0; i < this.options.format.length; i++){
					switch (this.options.format[i]){
						case 'U':
							var ud = new Date(stringDate.substr(sp, 10));
							sp += 10;
							break;
						
						case 'd':
							splittedDate.set('d', stringDate.substr(sp, 2));
							sp += 2;
							break;
						
						case 'j':
							splittedDate.set('d', '0'.concat(stringDate.substr(sp, 1)));
							sp += 1;
							break;
						
						case 'm':
							splittedDate.set('m', stringDate.substr(sp, 2) - 1);
							sp += 2;
							break;
						
						case 'n':
							splittedDate.set('m', stringDate.substr(sp, 1));
							sp += 1;
							break;
						
						case 'Y':
							splittedDate.set('Y', stringDate.substr(sp, 4));
							sp += 4;
							break;
						
						case 'y':
							splittedDate.set('Y', stringDate.substr(sp, 2));
							sp += 2;
							break;
						
						case 'H':
							splittedDate.set('H', stringDate.substr(sp, 2));
							sp += 2;
							break;
						
						case 'i':
							splittedDate.set('i', stringDate.substr(sp, 2));
							sp += 2;
							break;
						
						case 's':
							splittedDate.set('s', stringDate.substr(sp, 2));
							sp += 2;
							break;
						
						default:
							// setting the string pointer one char forward
							sp += 1;
						}
					}
					
					return new Date(splittedDate.get('Y'), splittedDate.get('m'), splittedDate.get('d'), splittedDate.get('H'), splittedDate.get('i'), splittedDate.get('s'));
				}
				else
					return null;
			},
			
			
			
		_getValueFromElement: function(_eElement){
			var retVal = null;
			_eElement = $(_eElement);
			
			switch (_eElement.tagName){
				case 'INPUT':
				case 'SELECT':
				case 'OPTION':
				case 'TEXTAREA':
					retVal = _eElement.value;
					break;
				
				default:
					retVal = _eElement.innerHTML;
					break;
				}
			
				return retVal;
			}
		});
	}
