/**
 * Add date selection from calendar handling for input elements.
 *
 * The `.uiDatepicker()` method add validate handling to a input elements.
 *
 * The `.uiDatepicker()` method is a validation input also and using the validation type ''date'' from
 * the ep.validate module. Have a look at the additional options, some options are only available for
 * a special validation types.
 *
 * ### Examples
 * Apply .uiDatepicker() to input element named date.
 *
 * JavaScript:
 *
 *     ep(':input[name=date]')
 *         .uiDatepicker({
 *             type:       'date',
 *             format:     'd',
 *             time:       false
 *         });
 *
 * HTML:
 *
 *     <form>
 *       <input type="text" name="date" />
 *     </form>
 *
 *
 * @class jQuery.ui.uiDatepicker
 * @extends jQuery.ui.uiValidate
 *
 * @uses ep
 * @uses ep.date
 * @uses ep.sprite
 * @uses ep.fn.busy
 * @uses ep.ui.spinner
 * @uses jQuery.tmpl
 * @since 6.11.0
 */

/**
 * Call up a previously attached date picker.
 *
 * @method show
 * @member jQuery.ui.uiDatepicker
 *
 * @since 6.11.0
 */

/**
 * Close a previously opened date picker.
 *
 * @method hide
 * @member jQuery.ui.uiDatepicker
 *
 * @since 6.11.0
 */

/**
 * @cfg {String} type The value respectively validation type must be 'date'.
 */

/**
 * @cfg {String} format A date formatter [look at the jQuery.i18n plugin].
 */

/**
 * @cfg {Integer} min A local timestamp (not UTC) as minimum valid date.
 */

/**
 * @cfg {Integer} max A local timestamp (not UTC) as maximum valid date.
 */

/**
 * @cfg {String} showOn A handle indication, how to show the datepicker. Supported handles: 'focus', 'button', 'both' (button and focus) or 'inline'.
 */

/**
 * @cfg {Integer,String} showOn A duration for show and hide effect. [look at jQuery.show() or jQuery.hide() for supported durations].
 */

/**
 * @cfg {String} show One off the supported show effects: 'show', 'fadeIn' or 'slideIn'.
 */

/**
 * @cfg {String} hide One off the supported hide effects: 'hide', 'fadeOut' or 'slideOut'.
 */

/**
 * @cfg {Array} weekDaysAvailable An array of 7 weekdays with indications wether a day is available. Week starts on sunday.
 */

/**
 * @cfg {Integer} numberOfMonths Set how many months to show at once.
 */

/**
 * @cfg {Boolean} showOtherMonth Display dates in other months (non-selectable) at the start or end of the current month. To make these days selectable use selectOtherMonths.
 */

/**
 * @cfg {Boolean} selectOtherMonth When true days in other months shown before or after the current month are selectable. This only applies if showOtherMonths is also true.
 */

/**
 * @cfg {Boolean} changeMonth Allows you to change the month by selecting from a drop-down list. You can enable this feature by setting the attribute to true.
 */

/**
 * @cfg {Boolean} changeYear Allows you to change the year by selecting from a drop-down list. You can enable this feature by setting the attribute to true. Use the yearRange option to control which years are made available for selection.
 */

/**
 * @cfg {Boolean} time Allows you to select time also. Requires a format which support time.
 */

/**
 * @cfg {Boolean} timeStep A difference to next/previous step in minutes.
 */

/**
 * See `jQuery.ui.uiDatepicker` for details.
 *
 * @param {Object} [options] A map of additional options pass to the method.
 * @param {String} type The value respectively validation type must be 'date'.
 * @param {String} format A date formatter [look at the jQuery.i18n plugin].
 * @param {Integer} min A local timestamp (not UTC) as minimum valid date.
 * @param {Integer} max A local timestamp (not UTC) as maximum valid date.
 * @param {String} showOn A handle indication, how to show the datepicker. Supported handles: 'focus', 'button', 'both' (button and focus) or 'inline'.
 * @param {Integer,String} showOn A duration for show and hide effect. [look at jQuery.show() or jQuery.hide() for supported durations].
 * @param {String} show One off the supported show effects: 'show', 'fadeIn' or 'slideIn'.
 * @param {String} hide One off the supported hide effects: 'hide', 'fadeOut' or 'slideOut'.
 * @param {Array} weekDaysAvailable An array of 7 weekdays with indications wether a day is available. Week starts on sunday.
 * @param {Integer} numberOfMonths Set how many months to show at once.
 * @param {Boolean} showOtherMonth Display dates in other months (non-selectable) at the start or end of the current month. To make these days selectable use selectOtherMonths.
 * @param {Boolean} selectOtherMonth When true days in other months shown before or after the current month are selectable. This only applies if showOtherMonths is also true.
 * @param {Boolean} changeMonth Allows you to change the month by selecting from a drop-down list. You can enable this feature by setting the attribute to true.
 * @param {Boolean} changeYear Allows you to change the year by selecting from a drop-down list. You can enable this feature by setting the attribute to true. Use the yearRange option to control which years are made available for selection.
 * @param {Boolean} time Allows you to select time also. Requires a format which support time.
 * @param {Boolean} timeStep A difference to next/previous step in minutes.
 *
 * @method uiDatepicker
 * @member jQuery
 *
 * @since 6.11.0
 */

/*
 * @copyright		© Copyright 2006-2010, epages GmbH, All Rights Reserved.
 *
 * @module			ep.ui.datepicker
 *
 * @revision		$Revision: 1.13 $
 */

define("ep/ui/datepicker", [
	"jquery",
	"ep",
	"$dict!ep/dict",
	"$tmpl!ep/ui/datepicker",

	"ep/date",
	"ep/sprite",
	"ep/fn/busy",
	"ep/ui/spinner"
], function ($, ep, epDict, tmplDatepicker) {

/*
 * @dictionary		ep.dict
 *
 * @translation		{Today}
 *					{Done}
 */

	$.widget('ui.uiDatepicker',$.ui.uiValidate,{

		options: {
	// ep.ui.Input
		//	big:				true,
		//	autofocus:			false,
		//	placeholder:		'text',
		//	info:				'text',

	// ep.ui.Validate
		//	accept:				'',
		//	min:				1,
		//	max:				20,
		//	minlength:			1,
		//	maxlength:			4,
		//	pattern:			[\w]+,
		//	required:			true,
		//	valid:				true,
		//	type:				'basic',
		//	format:				'',

	// ep.ui.Datepicker
		//	onAfterSetValue:	undefined					// callback called after value was set
		//	bubble: 			false,
			type:				'date',
			format:				'l',						// format for i18n
			showOn:				'button',					// show datepicker on 'focus' || 'button' || 'both' or 'inline'
			duration:			'normal',					// duration for show and hide effect
			show:				'fadeIn',					// show effect 'show' || 'fadeIn' || 'slideIn'
			hide:				'fadeOut',					// hide effect 'hide' || 'fadeOut' || 'slideOut'
			weekDaysAvailable:	[1,1,1,1,1,1,1],			// which days ar avaible boolean in array
			numberOfMonths:		1,							// display number of month
			showOtherMonth:		true,						// show days from other month
			selectOtherMonth:	false,						// days from other month ar clickable
			changeMonth:		true,						// enable select month
			changeYear:			true,						// enable select year
			region:				$.i18n.settings.region,		// region for i18n
			time:				true,						// use as date and time picker
			timeStep:			15,							// step for time spinner in minutes
			min:				undefined,					// min for range miliseconds || dateObj
			max:				undefined					// max for range miliseconds || dateObj
		},

		_create: function(){
			this._superApply(arguments);

			// get instance of dict
			this.dict = epDict;

			var currentDate = this.options.currentDate,
				date;

			date = currentDate ? new ep.Date(currentDate.year, currentDate.month, currentDate.day) : new ep.Date();
			date
				.setMinutes(30)
				.setSeconds(0)
				.setMilliseconds(0);

			this._date =			date.clone().setHours(0,0,0,0);
			this._dateNow =			this._date.clone();
			this._dateSelected =	this._date.clone();
			this._timeSelected =	date.clone().setFullYear(0,0,0);

			this.domCache = $('<div>');
			this.cal = $.i18n.regions[ this.options.region ].calendar;

			this.elem.on( 'changeValue.uiDatepicker', $.proxy(this, '_changeElemVal') );

			this.showButton = $('<span>')
				.attr({
					'class':	'ep-uiInput ep-uiInput-base ep-uiInput-button ep-uiDatepicker-showButton'
				})
				.html(
					ep.sprite('calendar','s').addClass('ep-uiInput-buttonSpriteOnly')
				)
				.on( 'click.uiDatepicker', $.proxy(this, 'show') )
				.appendTo( this.domCache );

			this.stack.push( this.domCache[0], this.showButton[0] );

			this._changeElemVal();
		},

		_init: function(){
			this._superApply(arguments);

			var o = this.options;

			if( (/^(focus|both)$/).test(o.showOn) ){
				this.elem
					.off('focus.uiDatepicker')
					.on( 'focus.uiDatepicker', $.proxy(this, 'show') );
			}
			if( (/^(button|both)$/).test(o.showOn) ){
				this.showButton
					.insertAfter( this.placeholder || this.elem );
			}
			else{
				this.showButton
					.appendTo( this.domCache );
			}

			if( this.container ){
				this._initCalCache();

				if( o.time && !this.timeBar ){
					this._createTimeBar();
				}
			}

			this._tooltipInit();

			if( o.showOn === 'inline' ){
				this.show();
			}
		},

		_initCalCache: function(){
			this._calCache = {
				weekDays: arrayRotate( $.merge([],this.cal.days.namesShort), this.cal.firstDay),
				months: $.merge([],this.cal.months.names)
			};
		},

		_tooltipInit: function(){
			this._superApply(arguments);
			if( this.tooltip && (/^(button|both)$/).test(this.options.showOn) ){
				this.tooltip.uiTooltip( 'option', 'offsetAdjust', [30,-1] );
			}
			else if( this.tooltip ){
				this.tooltip.uiTooltip( 'option', 'offsetAdjust', [3,-2] );
			}
		},

		_createBase: function(){
			var self = this;
			// the datepicker
			this.container = ep('<form>')
				.addClass('epDialog ep-uiDatepicker-container ui-dialog ui-front')
				.hide();
			// go to previous month
			this.prevButton = ep('<button type="button">')
				.append( ep.sprite('arrow-l','s').addClass('ep-uiInput-buttonSpriteOnly') )
				.addClass('ep-uiDatepicker-prev')
				.on( 'click.uiDatepicker', function(){
					self._changeMonth(-1);
				});
			// go to next month
			this.nextButton = ep('<button type="button">')
				.append( ep.sprite('arrow-r','s').addClass('ep-uiInput-buttonSpriteOnly') )
				.addClass('ep-uiDatepicker-next')
				.on( 'click.uiDatepicker', function(){
					self._changeMonth(1);
				});
			// bar on the bottom
			this.buttonBar = $('<div>')
				.addClass('ep-uiDatepicker-buttonBar')
				.appendTo( this.container );
			// select the current day
			this.todayButton = ep('<button type="button">')
				.text( this.dict.translate('Today') )
				.appendTo( this.buttonBar )
				.addClass('ep-uiDatepicker-today')
				.on( 'click.uiDatepicker', function(){
					self._date = self._dateNow.clone().setHours(0,0,0,0);
					self._changeDay( self._date.getTime() );
					self._createCalendars();
				});
			// close the datepicker
			this.doneButton = ep('<button type="button">')
				.text( this.dict.translate('Done') )
				.appendTo( this.buttonBar )
				.addClass('ep-uiDatepicker-done')
				.on( 'click.uiDatepicker', $.proxy(this, 'hide') );
			// live click handler to select a day on click
			this.container
				.on( 'click', 'a.ep-uiDatepicker-dayClick', $.proxy(this, '_selectDay') )
				.on( 'change', 'select.ep-uiDatepicker-monthSelect,select.ep-uiDatepicker-yearSelect', function(){
					self._changeMonth(parseInt(this.value,10));
				});
			// stack for calendars
			this.calendars = $();
			// reinit
			this._init();
			// add to DOM
			this.container.appendTo('body');
			// save in stack
			this.stack.push(
				this.container[0],
				this.prevButton[0],
				this.nextButton[0],
				this.doneButton[0],
				this.todayButton[0],
				this.buttonBar[0]
			);
		},

		_createTimeBar: function(){
			this.timeBar = $('<div>')
				.addClass('ep-uiDatepicker-timeBar')
				.insertBefore( this.buttonBar );

			this.stack.push( this.timeBar[0] );

			this._createTimeSelect();
		},

		_createTimeSelect: function(){
			this.timeSelect = ep('<input>')
				.attr({
					'class': 'epWidth100',
					'placeholder': '00:00'
				})
				.appendTo( this.timeBar )
				.uiSpinner({
					type:		'date',
					step:		15,
					stepType:	'minutes',
					format:		't',
					region:		this.options.region
				})
				.uiInput('addClass','ep-uiDatepicker-time')
				.on( 'changeValue.uiDatepicker', $.proxy(this, '_changeTimeVal') );

			this._setTimeVal();

			this.stack.push( this.timeSelect[0]	);
		},

		_createCalendars: function(){
			// set busy layer
			this.container.busy('show');
			// vars
			var o				= this.options,
				calendarsData	= this._calendarData(),
				calendars;
			// build weeks
			$.each( calendarsData, function( i, calendarData){
				while( calendarData.days.length >= 7 ){
					calendarData.weeks.push({days:calendarData.days.splice(0,7)});
				}
			});
			// build calendars
			calendars = tmplDatepicker(calendarsData);
			calendars
				.not(':first,:last')
				.addClass('ep-uiDatepicker-calendarMiddle');
			calendars
				.not(':last')
				.first()
				.addClass('ep-uiDatepicker-calendarFirst');
			calendars
				.not(':first')
				.last()
				.addClass('ep-uiDatepicker-calendarLast');
			// place prev button to new calendars and handle range
			this.prevButton
				.attr( 'disabled', !inRange( o.min, this._date.clone().setFirstMonthDay().setHours(0,0,0,-1).getTime() ) )
				.prependTo( calendars.filter(':first').find('.ep-uiDatepicker-titleBar') );
			// place next button to new calendars and handle range
			this.nextButton
				.attr( 'disabled', !inRange( undefined, this._date.clone().setLastMonthDay().setHours(0,0,0,0).addDate(1).getTime(), o.max ) )
				.appendTo( calendars.filter(':last').find('.ep-uiDatepicker-titleBar') );
			// remove old calendars
			this.calendars
				.html('')
				.remove();
			// show new calendars in datepicker
			this.container.prepend(calendars)
				.busy('hide');
			// set new calendars
			this.calendars = calendars;
		},

		_calendarData: function(){
			var o				= this.options,
				baseDate		= this._date.clone().addMonth( - Math.ceil(o.numberOfMonths/2) ),
				nowTime			= this._dateNow.getTime(),
				selectedTime	= this._dateSelected.getTime(),
				region			= {region: this.options.region},
				baseMonth		= this._date.getMonth(),
				changeMonth		= null,
				changeYear		= null,
				data			= [],
				i;

			if( o.changeMonth ){
				changeMonth = [];
				$.each( this._calCache.months, function( i, name ){
					if( name ){
						changeMonth.push({ value: (baseMonth-i)*-1, name: name, currentMonth: baseMonth===i });
					}
				});
			}

			if( o.changeYear ){
				var yDate = this._date.clone();

				changeYear = [{ value: 0, name: yDate.getFormat( "yyyy", region ), currentYear: true }];

				for( i=1 ; i<=10 ; i++ ){
					yDate.addFullYear(-1);
					changeYear.unshift({ value: i*-12, name: yDate.getFormat( "yyyy", region ), currentYear: false });
				}
				yDate.addFullYear(10);
				for( i=1 ; i<=10 ; i++ ){
					yDate.addFullYear(1);
					changeYear.push({ value: i*12, name: yDate.getFormat( "yyyy", region ), currentYear: false });
				}
			}

			for( i=0,iLength=o.numberOfMonths ; i<iLength ; i++ ){
				var date =			baseDate.addMonth(1),
					iDate =			date.clone().setDate(0),
					iFirstDate =	date.clone().setFirstMonthDay(),
					iFirstTime =	iFirstDate.getTime(),
					iLastTime =		date.clone().setLastMonthDay().getTime(),
					iDays =			iFirstDate.getMonthLength(),
					iWeekDay =		iFirstDate.getDay() - this.cal.firstDay,
					add =			{
										year:		iFirstDate.getFormat( "yyyy", region ),
										years:		changeYear,
										month:		iFirstDate.getFormat( "MMMM", region ),
										months:		changeMonth,
										weekDays:	this._calCache.weekDays,
										weeks:		[],
										days:		[]
									};

				if( iWeekDay<0 ){
					iWeekDay = iWeekDay+7;
				}

				iDate.addDate( - iWeekDay );

				for( var j=1,jLength=(Math.ceil( (iWeekDay+iDays) / 7) * 7 ) ; j<=jLength ; j++ ){
					var jDate =			iDate.addDate(1),
						jTime =			jDate.getTime(),
						currentMonth =	inRange( iFirstTime, jTime, iLastTime ),
						clickable =		inRange( o.min, jTime, o.max ) && o.weekDaysAvailable[ jDate.getDay() ];

					add.days.push({
						'date':				jDate.clone(),
						'tstamp':			jTime,
						'day':				jDate.getDate(),
						'currentMonth':		!!currentMonth,
						'clickable':		(currentMonth || o.selectOtherMonth) && clickable,
						'visible':			!!(currentMonth || o.selectOtherMonth || o.showOtherMonth),
						'selected':			jTime===selectedTime,
						'now':				jTime===nowTime
					});
				}

				data.push(add);
			}

			return data;
		},

		_setElemVal: function( noBubbling ){
			var val = new ep.Date(
							this._dateSelected.getFullYear(),
							this._dateSelected.getMonth(),
							this._dateSelected.getDate(),
							isNaN(this._timeSelected.getHours()) ? 0 : this._timeSelected.getHours(),
							isNaN(this._timeSelected.getMinutes()) ? 0 : this._timeSelected.getMinutes(),
							isNaN(this._timeSelected.getSeconds()) ? 0 : this._timeSelected.getSeconds()
						)
						.getFormat(
							this.options.format,
							{region:this.options.region}
						);

			// Strange case: datepicker only works with attr('value', ...) although elem is a form element
			// if( noBubbling ){
			// 	this.elem.val( val );
			// }
			// else{
			// 	this.elem.attr( 'value', val );
			// }

			if(this.options.bubble){
				this.elem.val( val );
			}
			this.elem.attr( 'value', val );

			// callback called after value was set
			if(this.options.onAfterSetValue !== undefined){
				this.options.onAfterSetValue(this.elem);
			}
		},

		_getElemVal: function(){
			var date = $.i18n.parseDate( this.elem.val(), this.options.format, {region:this.options.region} );
			return date ? new ep.Date( date.getTime() ) : null;
		},

		_changeElemVal: function(){
			var o = this.options,
				date = this._getElemVal();

			if( date && inRange( o.min, date.clone().setLastMonthDay().getTime() ) && inRange( undefined, date.clone().setFirstMonthDay().getTime(), o.max ) ){
				if( this.elem.is(':focus') ){
					this._date = date.clone().setHours(0,0,0,0);
				}
				this._dateSelected = date.clone().setHours(0,0,0,0);
				this._timeSelected = date.clone().setFullYear(0,0,0);

				if( this.timeSelect ){
					this._setTimeVal();
				}
			}
			if( this.container && this.container.filter(':visible').length ){
				this._createCalendars();
			}
		},

		_setTimeVal: function(){
			if( !this.timeSelect.is(':focus') ){
				this.timeSelect.val( this._timeSelected.getFormat( 't', {region:this.options.region} ) );
			}
		},

		_getTimeVal: function(){
			var date = $.i18n.parseDate( this.timeSelect.val(), 't', {region:this.options.region} );
			return date ? new ep.Date( date.getTime() ) : null;
		},

		_changeTimeVal: function(event){
			var date = this._getTimeVal();

			if( date ){
				this._timeSelected = date.setFullYear(0,0,0);
				this._setElemVal();
			}
		},

		_changeMonth: function( inc ){
			this._date.addMonth(inc);
			this._createCalendars();
		},

		_changeDay: function( tstamp ){
			this._dateSelected = new ep.Date(tstamp);
			this._setElemVal();
		},

		_selectDay: function( event ){
			this._changeDay( parseInt( $(event.target).attr('data-tstamp'), 10 ) );
		},

		_show: function(){
			var o = this.options;

			if( o.showOn !== 'inline' ){
				this.container.contextOrientation( this.elem, 'bottom', [-1,1] ).focus();
				this.container
					.stop(true,true)
					[ $.fn[o.show] ? o.show : 'show' ](o.duration);

				$('html').on( 'keyup', $.proxy(this,'_hide') );
			}
			else{
				this.buttonBar
					.hide();
				this.container
					.insertAfter(this.elem)
					.css('position','relative')
					.show();
			}
			this.elem.blur();
		},

		show: function(){
			var self = this;

			if( this.elem.is(":disabled") ){
				return;
			}
			if( !this.container ){
				this._createBase();
			}

			this._show();
			this._changeElemVal();
			this._setValidStatus(true);
		},

		_hide: function( event ){
			if( event.keyCode===27 ){
				this.hide();
				this._validate(event);
			}
		},

		hide: function(){
			var o = this.options;

			if( o.showOn !== 'inline' ){
				this.container
					.stop(true,true)
					[ $.fn[o.hide] ? o.hide : 'hide' ](o.duration);

				$('html').off( 'keyup', $.proxy(this,'_hide') );
			}
		},

		/**
		 * Returns the current date for the datepicker as `ep.Date()` object.
		 *
		 * @method getDate
		 * @member jQuery.ui.uiDatepicker
		 *
		 * @since 6.11.0
		 */
		getDate: function(){
			return new ep.Date(
							this._dateSelected.getFullYear(),
							this._dateSelected.getMonth(),
							this._dateSelected.getDate(),
							this._timeSelected.getHours(),
							this._timeSelected.getMinutes(),
							this._timeSelected.getSeconds()
						);
		}
	});

	var arrayRotate = function( array, size ){
			var tmp = array.splice( 0, size );

			return array.concat( tmp );
		},

		inRange = function( min, val, max ){
			min = new ep.Date(typeof min === 'object' && min.getTime ? min.getTime() : min).setHours(0,0,0,0).getTime();
			max = new ep.Date(typeof max === 'object' && max.getTime ? max.getTime() : max).setHours(0,0,0,0).getTime();
			return !min || val >= min ? !max || val <= max ? true : false : false;
		};

	return ep;

});