/*
 * This file is part of the Arnapou jqCron package.
 *
 * (c) Arnaud Buathier <arnaud@arnapou.net>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Default settings
 */
var jqCronDefaultSettings = {
	texts: {
		fr : {
			empty: '-tout-',
			name_minute: 'minute',
			name_hour: 'heure',
			name_day: 'jour',
			name_week: 'semaine',
			name_month: 'mois',
			name_year: 'année',
			text_period: 'Chaque <b />',
			text_mins: 'à <b /> minutes',
			text_time: 'à <b />:<b />',
			text_dow: 'le <b />',
			text_month: 'de <b />',
			text_dom: 'le <b />',
			error1: 'La balise %s n\'est pas supportée !',
			error2: 'Mauvais nombre d\'éléments',
			error3: 'La propriété jquery_element doit être définie dans les paramètres jqCron',
			error4: 'Expression non reconnue',
			weekdays: ['lundi', 'mardi', 'mercredi', 'jeudi', 'vendredi', 'samedi', 'dimanche'],
			months: ['janvier', 'février', 'mars', 'avril', 'mai', 'juin', 'juillet', 'août', 'septembre', 'octobre', 'novembre', 'décembre']
		},
		en : {
			empty: '-all-',
			name_minute: 'minute',
			name_hour: 'hour',
			name_day: 'day',
			name_week: 'week',
			name_month: 'month',
			name_year: 'year',
			text_period: 'Every <b />',
			text_mins: 'at <b /> minutes past the hour',
			text_time: 'at <b />:<b />',
			text_dow: 'on <b />',
			text_month: 'of <b />',
			text_dom: 'on the <b />',
			error1: 'The tag %s is not supported !',
			error2: 'Bad number of elements',
			error3: 'The jquery_element should be set into jqCron settings',
			error4: 'Unrecognized expression',
			weekdays: ['monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sunday'],
			months: ['january', 'february', 'march', 'april', 'may', 'june', 'july', 'august', 'september', 'october', 'november', 'december']
		},
		de : {
			empty: '-Alle-',
			name_minute: 'Minute',
			name_hour: 'Stunde',
			name_day: 'Tag',
			name_week: 'Woche',
			name_month: 'Monat',
			name_year: 'Jahr',
			text_period: 'Jede <b />',
			text_mins: 'um <b /> minuten nach der Stunde',
			text_time: 'um <b />:<b />',
			text_dow: 'am <b />',
			text_month: 'im <b />',
			text_dom: 'am <b />',
			error1: 'Der Tag %s wird nicht unterst&uuml;tzt!',
			error2: 'Ung&uuml;ltige Anzahl an Werten!',
			error3: 'Das jquery_element sollte in den jqCron-Einstellungen gesetzt werden!',
			error4: 'Ung&uuml;ltiger Ausdruck',
			weekdays: ['Montag', 'Dienstag', 'Mittwoch', 'Donnerstag', 'Freitag', 'Samstag', 'Sonntag'],
			months: ['J&auml;nner', 'Februar', 'M&auml;rz', 'April', 'Mai', 'Juni', 'Juli', 'August', 'September', 'Oktober', 'November', 'Dezember']
		}
	},
	monthdays: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31],
	hours: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23],
	minutes: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59],
	lang: 'fr',
  readonly: false,
	enabled_minute: false,
	enabled_hour: true,
	enabled_day: true,
	enabled_week: true,
	enabled_month: true,
	enabled_year: true,
	multiple_dom: false,
	multiple_month: false,
	multiple_mins: false,
	multiple_dow: false,
	multiple_time_hours: false,
	multiple_time_minutes: false,
	numeric_zero_pad: false,
	default_period: 'day',
	default_value: '',
	no_reset_button: true,
	disabled: false,
	bind_to: null,
	bind_method: {
		set: function($element, value) {
			$element.is(':input') ? $element.val(value) : $element.data('jqCronValue', value);
		},
		get: function($element) {
			return $element.is(':input') ? $element.val() : $element.data('jqCronValue');
		}
	}
};

/**
 * Custom extend of json for jqCron settings.
 * We don't use jQuery.extend because simple extend does not fit our needs, and deep extend has a bad 
 * feature for us : it replaces keys of "Arrays" instead of replacing the full array.
 */
(function($){
	var extend = function(dst, src) {
		for(var i in src) {
			if($.isPlainObject(src[i])) {
				dst[i] = extend(dst[i] && $.isPlainObject(dst[i]) ? dst[i] : {}, src[i]);
			}
			else if($.isArray(src[i])) {
				dst[i] = src[i].slice(0);
			}
			else if(src[i] !== undefined) {
				dst[i] = src[i];
			}
		}
		return dst;
	};
	this.jqCronMergeSettings = function(obj) {
		return extend(extend({}, jqCronDefaultSettings), obj || {});
	};
}).call(this, jQuery);

/**
 * Shortcut to get the instance of jqCron instance from one jquery object
 */
(function($){
	$.fn.jqCronGetInstance = function() {
		return this.data('jqCron');
	};
}).call(this, jQuery);

/**
 * Main plugin
 */
(function($){
	$.fn.jqCron = function(settings) {
		var saved_settings = settings;
		return this.each(function() {
			var cron, saved;
			var $this = $(this);
			var settings = jqCronMergeSettings(saved_settings); // clone settings
			if(!settings.jquery_container) {
				if($this.is(':container')) {
					settings.jquery_element = $this.uniqueId('jqCron');
				}
				else if($this.is(':autoclose')) {
					// delete already generated dom if exists
					if($this.next('.jqCron').length == 1) {
						$this.next('.jqCron').remove();
					}
					// generate new
					settings.jquery_element = $('<span class="jqCron"></span>').uniqueId('jqCron').insertAfter($this);
				}
				else {
					console && console.error(settings.texts[settings.lang].error1.replace('%s', this.tagName));
					return;
				}
			}
			
			// autoset bind_to if it is an input
			if($this.is(':input')) {
				settings.bind_to = settings.bind_to || $this;
			}
			
			// init cron object
			if(settings.bind_to){
				if(settings.bind_to.is(':input')) {
					// auto bind from input to object if an input, textarea ...
					settings.bind_to.blur(function(){
						var value = settings.bind_method.get(settings.bind_to);
						$this.jqCronGetInstance().setCron(value);
					});
				}
				saved = settings.bind_method.get(settings.bind_to);
				cron = new jqCron(settings);
				cron.setCron(saved);
			}
			else {
				cron = new jqCron(settings);
			}
			$(this).data('jqCron', cron);
		});
	};
}).call(this, jQuery);

/**
 * jqCron class
 */
(function($){
	var jqCronInstances = [];
	
	function jqCron(settings) {
		var _initialized  = false;
		var _self         = this;
		var _$elt         = this;
		var _$obj         = $('<span class="jqCron-container"></span>');
		var _$blocks      = $('<span class="jqCron-blocks"></span>');
		var _$blockPERIOD = $('<span class="jqCron-period"></span>');
		var _$blockDOM    = $('<span class="jqCron-dom"></span>');
		var _$blockMONTH  = $('<span class="jqCron-month"></span>');
		var _$blockMINS   = $('<span class="jqCron-mins"></span>');
		var _$blockDOW    = $('<span class="jqCron-dow"></span>');
		var _$blockTIME   = $('<span class="jqCron-time"></span>');
		var _$cross       = $('<span class="jqCron-cross">&#10008;</span>');
		var _selectors    = [];
		var _selectorPeriod, _selectorMins, _selectorTimeH, _selectorTimeM, _selectorDow, _selectorDom, _selectorMonth;
		
		// instanciate a new selector
		function newSelector($block, multiple){
			var selector = new jqCronSelector(_self, $block, multiple);
			selector.$.bind('selector:open', function(){
				// we close all opened selectors of all other jqCron
				for(var n = jqCronInstances.length; n--; ){
					if(jqCronInstances[n] != _self) {
						jqCronInstances[n].closeSelectors();
					}
					else {
						// we close all other opened selectors of this jqCron
						for(var o = _selectors.length; o--; ){
							if(_selectors[o] != selector) {
								_selectors[o].close();
							}
						}
					}
				}
			});
			selector.$.bind('selector:change', function(){
				var boundChanged = false;
				// don't propagate if not initialized
				if(!_initialized) return;
				// bind data between two minute selectors (only if they have the same multiple settings)
				if(settings.multiple_mins == settings.multiple_time_minutes) {
					if(selector == _selectorMins) {
						boundChanged = _selectorTimeM.setValue(_selectorMins.getValue());
					}
					else if(selector == _selectorTimeM) {
						boundChanged = _selectorMins.setValue(_selectorTimeM.getValue());
					}
				}
				// we propagate the change event to the main object
				boundChanged || _$obj.trigger('cron:change', _self.getCron());
			});
			_selectors.push(selector);
			return selector;
		}
		
		// disable the selector
		this.disable = function(){
			_$obj.addClass('disable');
			settings.disable = true;
			_self.closeSelectors();
		};

    // make the selector readonly
		this.enableReadonly = function(){
			_$obj.addClass('readonly');
			settings.readonly = true;
			_self.closeSelectors();
		};
		
		// return if the selector is disabled
		this.isDisabled = function() {
			return settings.disable == true;
		};

    this.isReadonly = function() {
			return settings.readonly == true;
		};
		
		// enable the selector
		this.enable = function(){
			_$obj.removeClass('disable');
			settings.disable = false;
		};

		this.disableReadonly = function(){
			_$obj.removeClass('readonly');
			settings.readonly = false;
		};
		
		// get cron value
		this.getCron = function(){
			var period = _selectorPeriod.getValue();
			var items = ['*', '*', '*', '*', '*'];
			if(period == 'hour') {
				items[0] = _selectorMins.getCronValue();
			}
			if(period == 'day' || period == 'week' || period == 'month' || period == 'year') {
				items[0] = _selectorTimeM.getCronValue();
				items[1] = _selectorTimeH.getCronValue();
			}
			if(period == 'month' || period == 'year') {
				items[2] = _selectorDom.getCronValue();
			}
			if(period == 'year') {
				items[3] = _selectorMonth.getCronValue();
			}
			if(period == 'week') {
				items[4] = _selectorDow.getCronValue();
			}
			return items.join(' ');
		};
		
		// set cron (string like * * * * *)
		this.setCron = function(str) {
			if(!str) return;
			try {
				str = str.replace(/\s+/g, ' ').replace(/^ +/, '').replace(/ +$/, ''); // sanitize
				var mask = str.replace(/[^\* ]/g, '-').replace(/-+/g, '-').replace(/ +/g, '');
				var items = str.split(' ');
				if (items.length != 5) _self.error(_self.getText('error2'));
				if(mask == '*****') {						// 1 possibility
					_selectorPeriod.setValue('minute');
				}
				else if(mask == '-****') {					// 1 possibility
					_selectorPeriod.setValue('hour');
					_selectorMins.setCronValue(items[0]);
					_selectorTimeM.setCronValue(items[0]);
				}
				else if(mask.substring(2, mask.length) == '***') {			// 4 possibilities
					_selectorPeriod.setValue('day');
					_selectorMins.setCronValue(items[0]);
					_selectorTimeM.setCronValue(items[0]);
					_selectorTimeH.setCronValue(items[1]);
				}
				else if(mask.substring(2, mask.length) == '-**') {			// 4 possibilities
					_selectorPeriod.setValue('month');
					_selectorMins.setCronValue(items[0]);
					_selectorTimeM.setCronValue(items[0]);
					_selectorTimeH.setCronValue(items[1]);
					_selectorDom.setCronValue(items[2]);
				}
				else if(mask.substring(2, mask.length) == '**-') {			// 4 possibilities
					_selectorPeriod.setValue('week');
					_selectorMins.setCronValue(items[0]);
					_selectorTimeM.setCronValue(items[0]);
					_selectorTimeH.setCronValue(items[1]);
					_selectorDow.setCronValue(items[4]);
				}
				else if (mask.substring(3, mask.length) == '-*') {			// 8 possibilities
					_selectorPeriod.setValue('year');
					_selectorMins.setCronValue(items[0]);
					_selectorTimeM.setCronValue(items[0]);
					_selectorTimeH.setCronValue(items[1]);
					_selectorDom.setCronValue(items[2]);
					_selectorMonth.setCronValue(items[3]);
				}
				else {
					_self.error(_self.getText('error4'));
				}
				_self.clearError();
			} catch(e) {}
		};
		
		// close all child selectors
		this.closeSelectors = function(){
			for(var n = _selectors.length; n--; ){
				_selectors[n].close();
			}
		};
	
		// get the main element id
		this.getId = function(){
			return _$elt.attr('id');
		}
	
		// get the translated text
		this.getText = function(key) {
			var text = settings.texts[settings.lang][key] || null;
			if(typeof(text) == "string" && text.match('<b')){
				text = text.replace(/(<b *\/>)/gi, '</span><b /><span class="jqCron-text">');
				text = '<span class="jqCron-text">' + text + '</span>';
			}
			return text;
		};
		
		// get the human readable text
		this.getHumanText = function() {
		    var texts=[];
		    _$obj
		    .find('> span > span:visible')
		    .find('.jqCron-text, .jqCron-selector > span')
		    .each(function() {
		        var text = $(this).text().replace(/\s+$/g, '').replace(/^\s+/g, '');
		        text && texts.push(text);
		    });
		    return texts.join(' ').replace(/\s:\s/g, ':');
		}
	
		// get settings
		this.getSettings = function(){
			return settings;
		};
	
		// display an error
		this.error = function(msg) {
			console && console.error('[jqCron Error] ' + msg);
			_$obj.addClass('jqCron-error').attr('title', msg);
			throw msg;
		};
		
		// clear error
		this.clearError = function(){
			_$obj.attr('title', '').removeClass('jqCron-error');
		};
	
		// clear
		this.clear = function() {
			_selectorDom.setValue([]);
			_selectorDow.setValue([]);
			_selectorMins.setValue([]);
			_selectorMonth.setValue([]);
			_selectorTimeH.setValue([]);
			_selectorTimeM.setValue([]);
			_self.triggerChange();
		};
		
		// init (called in constructor)
		this.init = function(){
			var n,i,list;
			if(_initialized) return;
			
			settings = jqCronMergeSettings(settings);
			settings.jquery_element || _self.error(_self.getText('error3'));
			_$elt = settings.jquery_element;
			_$elt.append(_$obj);
			_$obj.data('id', settings.id);
			_$obj.data('jqCron', _self);
			_$obj.append(_$blocks);
			settings.no_reset_button || _$obj.append(_$cross);
			(!settings.disable) || _$obj.addClass('disable');
			(!settings.readonly) || _$obj.addClass('readonly');
			_$blocks.append(_$blockPERIOD);
			_$blocks.append(_$blockDOM);
			_$blocks.append(_$blockMONTH);
			_$blocks.append(_$blockMINS);
			_$blocks.append(_$blockDOW);
			_$blocks.append(_$blockTIME);
			
			// various binding
			_$cross.click(function(){
				_self.isDisabled() || _self.clear();
			});
			
			// binding from cron to target
			_$obj.bind('cron:change', function(evt, value){
				if(!settings.bind_to) return;
				settings.bind_method.set && settings.bind_method.set(settings.bind_to, value);
				_self.clearError();
			});
		
			// PERIOD
			_$blockPERIOD.append(_self.getText('text_period'));
			_selectorPeriod = newSelector(_$blockPERIOD, false);
			settings.enabled_minute && _selectorPeriod.add('minute', _self.getText('name_minute'));
			settings.enabled_hour   && _selectorPeriod.add('hour',   _self.getText('name_hour'));
			settings.enabled_day    && _selectorPeriod.add('day',    _self.getText('name_day'));
			settings.enabled_week   && _selectorPeriod.add('week',   _self.getText('name_week'));
			settings.enabled_month  && _selectorPeriod.add('month',  _self.getText('name_month'));
			settings.enabled_year   && _selectorPeriod.add('year',   _self.getText('name_year'));
			_selectorPeriod.$.bind('selector:change', function(e, value){
				_$blockDOM.hide();
				_$blockMONTH.hide();
				_$blockMINS.hide();
				_$blockDOW.hide();
				_$blockTIME.hide();
				if(value == 'hour') {
					_$blockMINS.show();
				}
				else if(value == 'day') {
					_$blockTIME.show();
				}
				else if(value == 'week') {
					_$blockDOW.show();
					_$blockTIME.show();
				}
				else if(value == 'month') {
					_$blockDOM.show();
					_$blockTIME.show();
				}
				else if(value == 'year') {
					_$blockDOM.show();
					_$blockMONTH.show();
					_$blockTIME.show();
				}
			});
			_selectorPeriod.setValue(settings.default_period);
		
			// MINS  (minutes)
			_$blockMINS.append(_self.getText('text_mins'));
			_selectorMins = newSelector(_$blockMINS, settings.multiple_mins);
			for(i=0, list=settings.minutes; i<list.length; i++){
				_selectorMins.add(list[i], list[i]);
			}
		
			// TIME  (hour:min)
			_$blockTIME.append(_self.getText('text_time'));
			_selectorTimeH = newSelector(_$blockTIME, settings.multiple_time_hours);
			for(i=0, list=settings.hours; i<list.length; i++){
				_selectorTimeH.add(list[i], list[i]);
			}
			_selectorTimeM = newSelector(_$blockTIME, settings.multiple_time_minutes);
			for(i=0, list=settings.minutes; i<list.length; i++){
				_selectorTimeM.add(list[i], list[i]);
			}
		
			// DOW  (day of week)
			_$blockDOW.append(_self.getText('text_dow'));
			_selectorDow = newSelector(_$blockDOW, settings.multiple_dow);
			for(i=0, list=_self.getText('weekdays'); i<list.length; i++){
				_selectorDow.add(i+1, list[i]);
			}
		
			// DOM  (day of month)
			_$blockDOM.append(_self.getText('text_dom'));
			_selectorDom = newSelector(_$blockDOM, settings.multiple_dom);
			for(i=0, list=settings.monthdays; i<list.length; i++){
				_selectorDom.add(list[i], list[i]);
			}
		
			// MONTH  (day of week)
			_$blockMONTH.append(_self.getText('text_month'));
			_selectorMonth = newSelector(_$blockMONTH, settings.multiple_month);
			for(i=0, list=_self.getText('months'); i<list.length; i++){
				_selectorMonth.add(i+1, list[i]);
			}
		
			// close all selectors when we click in body
			$('body').click(function(){
				var i, n = _selectors.length;
				for(i = 0; i < n; i++){
					_selectors[i].close();
				}
			});
			_initialized = true;
			
			// default value
			if(settings.default_value) {
				_self.setCron(settings.default_value);
			}
		};

		// trigger a change event
		this.triggerChange = function(){
			_$obj.trigger('cron:change', _self.getCron());
		};
		
		// store instance in array
		jqCronInstances.push(this);
		
		// expose main jquery object
		this.$ = _$obj;
		
		// init
		try {
			this.init();
			_self.triggerChange();
		} catch(e){}
	}
	this.jqCron = jqCron;
}).call(this, jQuery);


/**
 * jqCronSelector class
 */
(function($){
	function jqCronSelector(_cron, _$block, _multiple){
		var _self      = this;
		var _$list     = $('<ul class="jqCron-selector-list"></ul>');
		var _$title    = $('<span class="jqCron-selector-title"></span>');
		var _$selector = $('<span class="jqCron-selector"></span>');
		var _values    = {};
		var _value     = [];
		var _hasNumericTexts = true;
		var _numeric_zero_pad = _cron.getSettings().numeric_zero_pad;
		
		// return an array without doublon
		function array_unique(l){ 
			var i=0,n=l.length,k={},a=[];
			while(i<n) {
				k[l[i]] || (k[l[i]] = 1 && a.push(l[i]));
				i++;
			}
			return a;
		}
	
		// get the value (an array if multiple, else a single value)
		this.getValue = function(){
			return _multiple ? _value : _value[0];
		};
		
		// get a correct string for cron
		this.getCronValue = function(){
			if(_value.length == 0) return '*';
			var cron = [_value[0]], i, s = _value[0], c = _value[0], n = _value.length;
			for(i=1; i<n; i++) {
				if(_value[i] == c+1) {
					c = _value[i];
					cron[cron.length-1] = s+'-'+c;
				}
				else {
					s = c = _value[i];
					cron.push(c);
				}
			}
			return cron.join(',');
		};
		
		// set the cron value 
		this.setCronValue = function(str) {
			var values = [], m ,i, n;
			if(str !== '*') {
				while(str != '') {
					// test "*/n" expression
					m = str.match(/^\*\/([0-9]+),?/);
					if(m && m.length == 2) {
						for(i=0; i<=59; i+=(m[1]|0)) {
							values.push(i);
						}
						str = str.replace(m[0], '');
						continue;
					}
					// test "a-b/n" expression
					m = str.match(/^([0-9]+)-([0-9]+)\/([0-9]+),?/);
					if(m && m.length == 4) {
						for(i=(m[1]|0); i<=(m[2]|0); i+=(m[3]|0)) {
							values.push(i);
						}
						str = str.replace(m[0], '');
						continue;
					}
					// test "a-b" expression
					m = str.match(/^([0-9]+)-([0-9]+),?/);
					if(m && m.length == 3) {
						for(i=(m[1]|0); i<=(m[2]|0); i++) {
							values.push(i);
						}
						str = str.replace(m[0], '');
						continue;
					}
					// test "c" expression
					m = str.match(/^([0-9]+),?/);
					if(m && m.length == 2) {
						values.push(m[1]|0);
						str = str.replace(m[0], '');
						continue;
					}
					// something goes wrong in the expression
					return ;
				}
			}
			_self.setValue(values);
		};
	
		// close the selector
		this.close = function(){
			_$selector.trigger('selector:close');
		};
		
		// open the selector
		this.open = function(){
			_$selector.trigger('selector:open');
		};
	
		// whether the selector is open
		this.isOpened = function() {
			return _$list.is(':visible');
		};
	
		// add a selected value to the list
		this.addValue = function(key) {
			var values = _multiple ? _value.slice(0) : []; // clone array
			values.push(key);
			_self.setValue(values);
		};
	
		// remove a selected value from the list
		this.removeValue = function(key) {
			if(_multiple) {
				var i, newValue = [];
				for(i=0; i<_value.length; i++){
					if(key != [_value[i]]) {
						newValue.push(_value[i]);
					}
				}
				_self.setValue(newValue);
			}
			else {
				_self.clear();
			}
		};
	
		// set the selected value(s) of the list
		this.setValue = function(keys){
			var i, newKeys = [], saved = _value.join(' ');
			if(!$.isArray(keys)) keys = [keys];
			_$list.find('li').removeClass('selected');
			keys = array_unique(keys);
			keys.sort(function(a, b){ 
				var ta = typeof(a);
				var tb = typeof(b);
				if(ta==tb && ta=="number") return a-b;
				else return String(a) == String(b) ? 0 : (String(a) < String(b) ? -1 : 1);
			});
			if(_multiple) {
				for(i=0; i<keys.length; i++){
					if(keys[i] in _values) {
						_values[keys[i]].addClass('selected');
						newKeys.push(keys[i]);
					}
				}
			}
			else {
				if(keys[0] in _values) {
					_values[keys[0]].addClass('selected');
					newKeys.push(keys[0]);
				}
			}
			// remove unallowed values
			_value = newKeys;
			if(saved != _value.join(' ')) {
				_$selector.trigger('selector:change', _multiple ? keys : keys[0]);
				return true;
			}
			return false;
		};
		
		// get the title text
		this.getTitleText = function(){
			var getValueText = function(key) {
				return (key in _values) ? _values[key].text() : key;
			};
			
			if(_value.length == 0) return _cron.getText('empty');
			var cron = [getValueText(_value[0])], i, s = _value[0], c = _value[0], n = _value.length;
			for(i=1; i<n; i++) {
				if(_value[i] == c+1) {
					c = _value[i];
					cron[cron.length-1] = getValueText(s)+'-'+getValueText(c);
				}
				else {
					s = c = _value[i];
					cron.push(getValueText(c));
				}
			}
			return cron.join(',');
		};
	
		// clear list
		this.clear = function() {
			_values = {};
			_self.setValue([]);
			_$list.empty();
		};
	
		// add a (key, value) pair
		this.add = function(key, value) {
			if(!(value+'').match(/^[0-9]+$/)) _hasNumericTexts = false;
			if(_numeric_zero_pad && _hasNumericTexts && value < 10) {
				value = '0'+value;
			}
			var $item = $('<li>' + value + '</li>');
			_$list.append($item);
			_values[key] = $item;
			$item.click(function(){
				if(_multiple && $(this).hasClass('selected')) {
					_self.removeValue(key);
				}
				else {
					_self.addValue(key);
					if(!_multiple) _self.close();
				}
			});
		};
		
		// expose main jquery object
		this.$ = _$selector;
	
		// constructor
		_$block.find('b:eq(0)').after(_$selector).remove();
		_$selector
		.addClass('jqCron-selector-' + _$block.find('.jqCron-selector').length)
		.append(_$title)
		.append(_$list)
		.bind('selector:open', function(){
			if(_hasNumericTexts) {
				var nbcols = 1, n = _$list.find('li').length;
				if(n > 5 && n <= 16) nbcols = 2;
				else if(n > 16 && n <= 23) nbcols = 3;
				else if(n > 23 && n <= 40) nbcols = 4;
				else if(n > 40) nbcols = 5;
				_$list.addClass('cols'+nbcols);
			}
			_$list.show();
		})
		.bind('selector:close', function(){
			_$list.hide();
		})
		.bind('selector:change', function(){
			_$title.html(_self.getTitleText());
		})
		.click(function(e){
			e.stopPropagation();
		})
		.trigger('selector:change')
		;
		$.fn.disableSelection && _$selector.disableSelection(); // only work with jQuery UI
		_$title.click(function(e){
			(_self.isOpened() || _cron.isDisabled() || _cron.isReadonly()) ? _self.close() : _self.open();
		});
		_self.close();
		_self.clear();
	}
	this.jqCronSelector = jqCronSelector;
}).call(this, jQuery);

/**
 * Generate unique id for each element.
 * Skip elements which have already an id.
 */
(function($){
	var jqUID = 0;
	var jqGetUID = function(prefix){
		var id;
		while(1) {
			jqUID++;
			id = ((prefix || 'JQUID')+'') + jqUID;
			if(!document.getElementById(id)) return id;
		}
	};
	$.fn.uniqueId =  function(prefix) {
		return this.each(function(){
			if($(this).attr('id')) return;
			var id = jqGetUID(prefix);
			$(this).attr('id', id);
		});
	};
}).call(this, jQuery);


/**
 * Extends jQuery selectors with new block selector
 */
(function($){
	$.extend($.expr[':'], {
		container: function(a) {
			return (a.tagName+'').toLowerCase() in {
				a:1,
				abbr:1,
				acronym:1,
				address:1,
				b:1,
				big:1,
				blockquote:1,
				button:1,
				cite:1,
				code:1,
				dd: 1,
				del:1,
				dfn:1,
				div:1,
				dt:1,
				em:1,
				fieldset:1,
				form:1,
				h1:1,
				h2:1,
				h3:1,
				h4:1,
				h5:1,
				h6: 1,
				i:1,
				ins:1,
				kbd:1,
				label:1,
				li:1,
				p:1,
				pre:1,
				q:1,
				samp:1,
				small:1,
				span:1,
				strong:1,
				sub: 1,
				sup:1,
				td:1,
				tt:1
			};
		},
		autoclose: function(a) {
			return (a.tagName+'').toLowerCase() in {
				area:1,
				base:1,
				basefont:1,
				br:1,
				col:1,
				frame:1,
				hr:1,
				img:1,
				input:1,
				link:1,
				meta:1,
				param:1
			};
		}
	});
}).call(this, jQuery);
