/** DatePicker : classe calendrier
	@fonctionnement : 
		Cette classe ne fait qu'initialiser un objet calendrier
			var calendar = new Calendar(options); //option est une hashmap d'options
		Si on souhaite l'afficher par rapport à un element
			calendar.show(elm); //element
	
		//on peut cumuler la creation du calendrier et son affichage
		new DatePicker().show('truc');
	
	@params : 
		forbiddenDays : {
				month	: [1,2,3,'thismonth'], //mettre le numero du mois à interdire, 'thismonth' interdit le mois en cours, 1=janvier, 2=fevrier
				weekday	: ['sat', 'sun'], //mettre les 3 premieres lettres du jour de la semaine en anglais : mon,tue,wed,thu,fri,sat,sun
				year	: [1900,2011,2010, "1998",'thisyear'], //mettre les années à interdire, 'thisyear' permet de prendre l'année en cours
				date	: ['14/07/2006', '25/12','today'], //mettre les jours à interdire, si on omet la l'année, les jours seront interdits sur toutes les années, si on met today on a le jour en cours
				period : [
					'12/03/2001 to 25/03/2001', //entre 2 dates avec le keyword 'to'
					'today to 10/02/2010',
					'before 12/04/1980', //avant une date avec le keyword 'before'
					'before today', //interdit les jours avant aujourd'hui
					'before today+3', //interdit les jours à partir de 3 jours après aujourd'hui aujourd'hui inclus, pour interdire aujourd'hui 
					'before today-3', //interdit les jours avant 3 jours avant aujourd'hui
					'after today', //interdit les jours après aujourd'hui
					'after 21/12/2012', //interdit les jours après le 21/12/2012
					]
			}
	
	
	@Modules : 
		- creer la fonction qui permettra d'ajouter des modules
		- module jours fériés ou plutot jours interdits : 
			- ce module pourras écraser la méthode events.click.eventsClass en checkant la classe qu'il y a en plus sur le jour, exemple : disabledDay (à ajouter sur les jours interdits par exemple)
			- dans la generation des jours, il faudra gerer l'execution d'une fonction ou plusieurs fonction qui retourneront à chaquefois le className etendu 
			- on pourra gerer dans un premier temps les jours interdits, puis ensuite via une fonction ou un autre module, ajouter les jours feries (le calcul sera effectue)
			- les interdictions devront se baser sur un masque de jours et etre faite via une fonction qui sera appellee lors de la generation d'un jour
			
		- module drag and drop :
			il faudra proposer a l'utilisateur de rajouter une classe qui gerera le drag and drop (gestion via mousedown et mouseup)
*/
var DatePicker = function() { 
	this.options = {
		defaultClassName	: 'datepicker',
		daysNames 			: ['dim', 'lun', 'mar', 'mer', 'jeu', 'ven', 'sam'],
		monthsNames 		: ['Janvier', 'F&eacute;vrier', 'Mars', 'Avril', 'Mai', 'Juin', 'Juillet', 'Aout', 'Septembre', 'Octobre', 'Novembre', 'D&eacute;cembre'],
		dateMask			: 'DD/MM/YYYY',
		destField			: null, //peut aussi etre un tableau d'ids ['inputday','inputMonth', 'inputYear']
		positionVertical	: 'bottom,top', //position vertical entre le coté top ou bottom du bouton et le coté top ou bottom du calendrier, ici le "top" (haut) du calendrier, sera callé avec le "bottom" (bas) du bouton
		positionHorizontal	: 'right,right', //idem mais pour left/right
		templateCal			: [
								'<div class="closeBtnContainer"><a href="#" class="closeBtn">'+libClose+'</a></div>',
								'__HEADER__',
								'<div class="calendarTable">',
									'<table>',
										'<thead>',
											'<tr>',
												'__DAYSNAME__',  // <th>MON</th> //entete des jours
											'</tr>',
										'</thead>',
										'<tbody>',
											'__DAYS__', //generation des jours du calendrier ici
										'</tbody>',
									'</table>',
								'</div>',
								'<div class="calendar_footer"></div>'
							].join('\n'),
		templateDay			: [
								'<td class="day __CLASSNAME__" date="__NUM__,__MONTH__,__YEAR__">',
									'<a href="#">__NUM__</a>',
								'</td>'
							].join('\n'),
		templateDayName		: '<th>__FIRSTLETTER__</th>',  // __DAY__
		templateHeader		: [
								'<div class="calendarHeader">',
									'<span class="previousBtn"><a class="previousYearBtn" href="#">&lt;&lt;</a> <a class="previousMonthBtn" href="#">&lt;</a></span>',
									'<span class="nextBtn"><a class="nextMonthBtn" href="#">&gt;</a> <a class="nextYearBtn" href="#">&gt;&gt;</a></span>',
									'<div class="dayTitle">',
										'__MONTH__ __YEAR__', //__DAYNAME__ __DATE__ 
									'</div>',
								'</div>'
							].join('\n'),
		firstWeekDay 		: 1, //premier jour de la semaine : dim=0, lun=1, mar=2....sam=6
		forbiddenDays		: {}, //objet de jours, se reporter en haut pour connaitre les possibilitées offertes
		dateChoosen			: function(date) {} //fonction de callback executee lorsqu'une date a été choisie
	};
	this.initialize.apply(this, arguments);
};
DatePicker.prototype = {
	constructor : DatePicker,
	initialize : function(options) {
		this.dom = {}; //objet contenant les references aux elements du dom
		this.days = {}; //objet contenant des informations sur les jours (important pour la generation du calendrier)
		this.setOptions(options);
	},
	
	/* show : affiche le calendrier et le genere s'il n'a jamais ete genere
		@params :
			- elm : element par rapport auquel le calendrier va s'affichier
	*/
	show : function(element, options) {
		var _self = this;
		
		this.setOptions(options);
		
		if (this.options.destField) {
			if(this.options.destField instanceof Array) {
				var strDate = this.options.dateMask;
				var dF = this.options.destField;
				this.z.each(dF, function(input, index) {
					dF[index] = _self.z.$(input);
				});
				strDate = strDate.replace(/DD/i,_self.getZero(parseInt(dF[2].value,10)))
								.replace(/MM/i,_self.getZero(parseInt(dF[1].value,10)))
								.replace(/YYYY/i,dF[0].value,10);
				this.options.dateFromDestField = strDate;
			} else {
				this.options.destField = this.z.$(this.options.destField);
				this.options.dateFromDestField = this.options.destField.value;
			}
		}
		
		
		this.dom.element = this.z.$(element);
		if (this.dom.element.calendarIsOpen) {
			return;
		}
		this.dom.element.calendarIsOpen = true;
		
		this.initForbiddenDays();
		
				
		//creation div conteneur
		var d = document.createElement('div');
		d.className = this.options.defaultClassName + ' ' + (this.options.className || '');
		d.style.display = 'none';
		document.body.appendChild(d);
		// ajout des events
		this.z.addEvent(d, 'click', function(e) {
			_self.z.stopEvent(e);
			_self.eventsManager(e);
		}, true);
		this.z.each(['mouseover','mouseout','focus','blur','mousedown','mouseup'], function(evt) {
			_self.z.addEvent(d, evt, function(e) {
				_self.eventsManager(e);
			});
		});
		this.dom.calendar = d;
		
		
		/* event sur document */
		setTimeout(function() { 
			if (!DatePicker.documentClickCloseEvents) {
				DatePicker.documentClickCloseEvents = [];
				_self.z.addEvent(document, 'click', function(e) {
					_self.documentClick(e);
				});
			}
			DatePicker.documentClickCloseEvents.push(function() { _self.close(); });
		},100);
		
		
		this.days.selectedDate = this.options.dateFromDestField ? this.getDateFromStr(this.options.dateFromDestField) : null;
		
		// generer le calendrier
		this.generate(this.days.selectedDate);
		//afficher
		this.dom.calendar.style.display = 'block';
		this.setPosition();
		
			
		if (document.all && window.print && /MSIE 6/.test(navigator.userAgent)) {
			var el = this.dom.calendar;
			var iframeStr = '<iframe style="filter:alpha(opacity=0); height:'+el.offsetHeight+'px;width:'+el.offsetWidth+'px;left:'+el.style.left+';top:'+el.style.top+';z-index:'+(el.currentStyle.zIndex-1)+';position:absolute;"></iframe>';
			this.dom.iframe = document.createElement(iframeStr);
			document.body.appendChild(this.dom.iframe);
		}
		
		return this;
	},
	
	close : function(fromDocument) {
		if (!fromDocument) {
			this.documentClick();
		}
		if (this.dom.calendar) {
			this.z.remove(this.dom.calendar);
			this.dom.calendar = null;
		}
		this.dom.element.calendarIsOpen = null;
		if(this.dom.iframe) {
			document.body.removeChild(this.dom.iframe);
			this.dom.iframe = null;
		}
	},
	
	documentClick : function() {
		while(DatePicker.documentClickCloseEvents.length>0) {
			DatePicker.documentClickCloseEvents.pop()();
		}
	},
	
	/* generate : genere le calendrier 
			generer les tableau et le header du tableau
	*/
	generate : function(date) {
		var t = new Date().getTime();
		date = date || new Date();
		
		var str = this.options.templateCal,
			td = this.days;
		this.initDates(date);
			
		
		/* entete du calendrier */
		str = str.replace(/__HEADER__/,
			this.options.templateHeader.replace(/__DAYNAME__/, this.options.daysNames[this.getWeekDayIndex(date.getDay())])
			.replace(/__DATE__/,date.getDate())
			.replace(/__MONTH__/, this.options.monthsNames[date.getMonth()])
			.replace(/__YEAR__/, date.getFullYear())
		);
		
		/* entete des jours */
		str = str.replace(/__DAYSNAME__/, this.getDaysName());
		
		var d = new Date(this.days.firstMonthDate);
		d.setDate(d.getDate()-this.getWeekDayIndex(d));
		
		var lines = [];
		for (var i=1; i<=6; i++) { //lignes (6 lignes max par mois)
			var days = [];
			for (var j=1; j<=7; j++) { //colonnes
				var num = d.getDate();
				var month = d.getMonth();
				var year = d.getFullYear();
				var className = '';
				// gestion des classes du jour
				if(month<date.getMonth()) {// mois precedent
					className += ' previousMonth';
				}
				if(month>date.getMonth()) { //mois suivant
					className += ' nextMonth';
				} else {  //mois en cours
					// jour actuellement selectionné dans l'input
					if(td.selectedDate && num==td.selectedDate.getDate() && month==td.selectedDate.getMonth() && year==td.selectedDate.getFullYear()) {
						className += ' currentDay';
					}
					// aujourd'hui
					if(num==td.today.getDate() && month==td.today.getMonth() && year==td.today.getFullYear()) {
						className += ' today';
					}
				}
				//jour interdit
				if (this.isForbiddenDay(d))
					className += ' forbiddenDay';
				days.push(
					this.options.templateDay
										.replace(/__CLASSNAME__/g,className)
										.replace(/__NUM__/g,num)
										.replace(/__MONTH__/g,month)
										.replace(/__YEAR__/g,d.getFullYear())
				);
				d.setDate(d.getDate()+1);
			}
			lines.push(days.join('\n'));
		//	if(d.getMonth()>date.getMonth()) break;
		}
		str =  str.replace(/__DAYS__/,'<tr>'+lines.join('</tr>\n<tr>')+'</tr>');

		/* cleanage du template */
		str = str.replace(/href=\"#?\"/g, 'href="javascript:;"'); //remplacement des href="#" ou href="" par un href qui ne mene nullepart afin de ne pas avoir de surprise lors du clic
	
		this.dom.calendar.innerHTML = str;
	},
	
	eventsManager : function(e) {
		var event = e || window.event,
			target = event.target || event.srcElement,
			eventType = event.type,
			actionsDones=0;
	
		// premier on attaque la target
		
		actionsDones = this.eventsDoActions(target, eventType, event);
		
		// si aucune action n'a ete effectue, on va chercher le parent avec un className sur lequel l'action aurait du etre faite (cas d'un element span dans le TD par exemple)
		if (actionsDones===0 &&  target!=this.dom.calendar) {
			do { 
				target = target.parentNode;
			} while(target.className.match(/^\s*$/) && target!=this.dom.calendar);
			if (target!=this.dom.calendar) {
				this.eventsDoActions(target, eventType, event);
			}
		}
		
	},
	
	eventsDoActions : function(target, eventType, event) {
		var _self = this;
		var actionsDones = 0;
		this.z.each(target.className.split(/\s+/), function(clN) {
			if (_self.eventsClass[eventType] && _self.eventsClass[eventType][clN]) {
				_self.eventsClass[eventType][clN].call(_self, event, target);
				actionsDones++;
			}
		});
		return actionsDones;
	},
	
	
	/* eventsClass : 
			objet contenant tous les evenement appliques sur le calendrier selon le type
			une action est associe a une classe
			Il ne peut pas y avoir 2 classes du meme nom sur 2 objets qui ont un comportement different
			ex : 
				pour <td class="day">, j'ai l'index 'day' associe dans l'objet eventsClass.click et eventsClass.mouseover
				
			chaque fonction accepte comme parametre : 
				elm : element sur lequel se passe l'event
				event : evenement 
			
			le this correspond à l'objet calendar
	*/
	
	eventsClass : {
		click : {
			'day' : function(e,elm) {
				if (elm.className.match(/forbiddenDay/)) //jour interdit, on ne fait rien
					return;
				var _self = this;
				var date = elm.getAttribute('date').split(',');
				
				if (this.options.destField instanceof Array) {
					this.options.destField[0].value = date[2];
					this.options.destField[1].value = this.getZero(parseInt(date[1])+1,10);
					this.options.destField[2].value = this.getZero(date[0],10);
				} else {
					this.options.destField.value = this.getFormatedDate(date[0],parseInt(date[1],10)+1,date[2]);
				}
				this.options.dateChoosen(this.getDateFromStr(this.options.destField.value));
				this.close();
			},
			'previousMonthBtn' : function(e,elm) {
				this.goMonth(-1);
			},
			'nextMonthBtn' : function(e,elm) {
				this.goMonth(1);
			},
			'previousYearBtn' : function(e,elm) {
				this.goYear(-1);
			},
			'nextYearBtn' : function(e,elm) {
				this.goYear(1);
			},
			'closeBtn' : function(e,elm) {
				this.close();
			}
		},
		mouseover : {
			'day' : function(e,elm) {
				elm.className += ' dayHover';
			}
		},
		mouseout : {
			'day' : function(e,elm) {
				elm.className = elm.className.replace(/dayHover/g, '');
			}
		}
	},
	
	goMonth : function(sens) {
		var d = new Date(this.days.currentDate);
		d.setDate(1);
		d.setMonth(d.getMonth()+sens);
		this.generate(d);
	},
	goYear : function(sens) {
		var d = new Date(this.days.currentDate);
		d.setFullYear(d.getFullYear()+sens);
		this.generate(d);
	},
	
	/* getDaysName : 
		retourne une liste des noms des jours 
	*/
	getDaysName : function() {
		var _self = this;
		var days = []; 
		
		this.z.each(this.options.daysNames, function(name, index) {
			index = _self.getWeekDayIndex(index);
			days[index] = _self.options.templateDayName
								.replace(/__DAY__/g, name)
								.replace(/__FIRSTLETTER__/, name.substr(0,1).toUpperCase());
		});
		return days.join('');
	},
	
	/* initDates : intialise les jours
			Permet de recuperer certains jours importants (jours interdits (si modules), premier jour du mois....)
	*/
	initDates : function(date) {
		var td = this.days;
		td.currentDate = date;
		td.today = new Date();
		td.firstMonthDate = new Date(date);
		td.firstMonthDate.setDate(1);
	},

	/* getWeekDayIndex :
			Retourne l'index du jour en fonction du jour de debut de semaine qui a ete defini dans les options
			@params : 
				date : Number ou Date, si c'est une Date on utiliser getDay()
	 */
	getWeekDayIndex : function(date) {
		var num = (date.constructor==Date ? date.getDay() : date) - this.options.firstWeekDay;
		return num>=0 ? num : 7+num;
	},
	
	
	/* getFormatedDate :
			Retourne une date formatee (string) selon le template (mask) en lui passant une date ou les 3 valeurs de la date en parametre
	 */
	getFormatedDate : function(/*date or day, month, year */) {
		var date,
			args = arguments;
		if (args.length==3) {
			date = new Date(parseInt(args[2],10), parseInt(args[1],10)-1, parseInt(args[0],10));
		} else {
			date = args[0];
		}	
		return this.options.dateMask.replace(/YYYY/gi, date.getFullYear())
					.replace(/MM/gi, this.getZero(date.getMonth()+1))
					.replace(/DD/gi, this.getZero(date.getDate()))
					.replace(/ww/gi, this.options.daysNames[date.getDay()]);
	},
	
	/* getDateFromStr :
			Retourne une date depuis une chaine de caractere en fonction du mask de date
	 */
	getDateFromStr : function(str, customMask) {
		var maskToUse = customMask ? customMask : this.options.dateMask;
		var mask 	= '^(' + maskToUse.replace(/\W+/gi, ').*(').replace(/[A-Z]/gi,'\\d') + ')$';
		var maskStr = maskToUse.replace(/\W+/gi,'');
		var d 		= str.match(new RegExp(mask));
		if (!d) {return null;}
		d = d.slice(1).join('');
		
		//var definedMask = 'DD/MM/YYYY';

		var yearLength = maskStr.toLowerCase().split('y').length - 1;
		
		var dPos = maskStr.indexOf('DD');
		var mPos = maskStr.indexOf('MM');
		var yPos = maskStr.indexOf('YY');
		
		var day 	= parseInt(d.substr(dPos,2),10); 
		var month 	= parseInt(d.substr(mPos,2),10);
		var year 	= parseInt(d.substr(yPos,yearLength),10);
		
		return new Date(year, month-1, day);
		
	},
	
	/* setPosition : 
		positionne le calendrier en fonction des parametres passes, par defaut il se positionne par rapport a l'element qui aura ete clique
	*/
	setPosition : function() {
		if (this.dom.element) {
			var pos = this.z.getPosition(this.dom.element, false);
			var dim = {w:this.dom.element.offsetWidth, h:this.dom.element.offsetHeight};
			var y = 0;
			var x = 0;
			
			var vertical = this.options.positionVertical.split(',');
			var horizontal = this.options.positionHorizontal.split(',');
			
			switch(vertical[0]) {
				case 'top':
					y = pos.y;
					break;
				case 'bottom':
					y = pos.y+dim.h;
					break;
			}
			switch(vertical[1]) { //useless for top
				case 'bottom' :
					y = y - this.dom.calendar.offsetHeight;
					break;
			}
			switch(horizontal[0]) {
				case 'left':
					x = pos.x;
					break;
				case 'right':
					x = pos.x+dim.w;
					break;
			}
			switch(horizontal[1]) { //useless for left
				case 'right' :
					x = x - this.dom.calendar.offsetWidth;
					break;
			}
			var s = this.dom.calendar.style;
				s.left = x + 'px';
				s.top = y + 'px';
		}
	},
	
	initForbiddenDays : function() {
		var _this = this;
		var map = this.arrayMap;
		var fd = this.options.forbiddenDays;
		
		var treatFunc = {
			'month'		: function(v){
							if (v == 'thismonth') return new Date().getMonth();
							else return v - 1;
						},
			'weekday'	: function(v){
							return 'sun,mon,tue,wed,thu,fri,sat'.split(',').indexOf(v);
						},
			'year'		: function(v){
							if (v == 'thisyear') return new Date().getFullYear();
							else return v*1;
						},
			'date'		: function(v){ //transforme toutes les dates du tableau en "DD/MM/YYYY", les dates en entrée peuvent être : new Date(2009,11,10),'25/02/1998','today','today+1','today-10'
							var d = v;
							if (typeof v == 'string') {
								if(d.match(/^\d{2}\/\d{2}$/)) {
									return d;
								}
								else {
									d = _this.strToDate(v);
									var strd = _this.dateToStr(d);
									d = _this.getDateFromStr(strd);
								}
							}
							else
								return d; //aucun traitement, on considere que la chaine est bien formée en DD/MM ou DD/MM/YYYY
							return _this.dateToStr(d);
						},
			'period'	: function(v) { //transforme une periode entre 2 dates : 'date1 to date2','before date','after date'. date peut etre formé comme les dates précedents today, today+4..., si un jour est utilisé, ce jour est inclu dans la periode
							
							var retP = {};
							var type = v.match(/(before|after|to)/)[0];
							var sp = v.split(/\s+/);
							retP.type = type;
							switch(retP.type) {
								case 'to' :
									retP.date1 = treatFunc.date(sp[0]); //_this.strToDate(treatFunc.date(sp[0]));
									retP.date2 = treatFunc.date(sp[2]); //_this.strToDate(treatFunc.date(sp[2]));
									break; 
								case 'before' :
									retP.date = _this.strToDate(treatFunc.date(sp[1]));
									break; 
								case 'after'  :
									retP.date = _this.strToDate(treatFunc.date(sp[1]), _this.options.dateMask);
									break; 
							}
							return retP;
						}
		}
		for (var i in fd) {
			fd[i] = map(fd[i], treatFunc[i]);
		}
	},
	
	// teste si une date est interdite en fonction des critères défini dans les options, et des objets generés à partir de ces criteres. cf:initForbiddenDays
	isForbiddenDay : function(date) {
		var fd = this.options.forbiddenDays;
		
		date.setHours(0); date.setMinutes(0); date.setSeconds(0), date.setMilliseconds(0);
		var year = date.getFullYear();
		var month = date.getMonth();
		var dateNum = date.getDate();
		var weekday = date.getDay();
		var dateStr = this.dateToStr(date);
		var dateStrDDMM = dateStr.split('/').slice(0,2).join('/');
		for (var i in fd) {
			var f = fd[i]; //f est le "forbidden type" ; month,weekday,year...
			switch(i) {
				case 'month' :
					if(f.indexOf(month)>-1) return true;					 
					break;
				case 'weekday' :
					if(f.indexOf(weekday)>-1) return true;
					break;
				case 'year' :
					if(f.indexOf(year)>-1) return true;
					break;
				case 'date' : 
					if(f.indexOf(dateStr)>-1 || f.indexOf(dateStrDDMM)>-1) return true;
					break;
				case 'period' :
					for (var pi=0; pi<f.length; pi++) {
						var p = f[pi];
						
						switch(p.type) {
							case 'to':
								var date1 = typeof p.date1=='string' ? this.strToDate(p.date1) : p.date1;
								var date2 = typeof p.date2=='string' ? this.strToDate(p.date2) : p.date2;
								if(date1.getTime()<=date.getTime() && date.getTime()<=date2.getTime())
									return true;
								break;
							case 'before' :
								var pdate = typeof p.date=='string' ? this.strToDate(p.date) : p.date;
								if(date.getTime()<=pdate.getTime()) return true;
								break;
							case 'after' :
								var pdate = typeof p.date=='string' ? this.strToDate(p.date, 'DD/MM/YYYY') : p.date;
								if(date.getTime()>=pdate.getTime()) return true;
								break;
						}
					} 
					break;
			}
		}
		return false;
	},
	
	// dateToStr : retourne une date sous la forme DD/MM/YYYY a partir d'une date
	dateToStr : function(d) {
		var m = this.options.dateMask;
		return m.replace('DD',this.getZero(d.getDate()))
				.replace('MM',this.getZero(d.getMonth()+1)) 
				.replace('YYYY',d.getFullYear());
		//return this.getZero(d.getDate())+'/'++'/'+d.getFullYear();
	},
	// strToDate : retourne date a partir d'une chaine sous la forme DD/MM/YYYY, DD/MM/YYYY+1, today+3
	strToDate : function(d, mask) {
		var r;
		
		if (d.match(/^\d{2}\/\d{2}$/))
			d+='/'+this.days.currentDate.getFullYear();
		
		r = d.match(/^(.{10}|today)([-+]\d+)*/);
		
		if (r[1] == 'today')
			var date = new Date();
		else {
			mask = r[1].indexOf("/") < 4 ? ( this.options.dateMask.indexOf("/") < 4 ? this.options.dateMask : 'DD/MM/YYYY' ) : mask;
			var date = this.getDateFromStr(r[1], mask);
		}
		
		if (r[2]!=null)
			date.setDate(date.getDate()+(r[2]*1));
		
		return date;
	},
	
	/* setOptions : initialize les options de l'objet en fonction des options passees en parametres */
	setOptions : function(options) {
		for (var i in options) this.options[i] = options[i];
	},
	
	getZero  : function(num) {
		return num<10 ? '0'+num : num;
	},
	
	arrayMap : function(array, fun/*, thisp*/) {
	    var len = array.length;
	    if (typeof fun != "function")
	      throw new TypeError();
	
	    var res = new Array(len);
	    var thisp = arguments[1];
	    for (var i = 0; i < len; i++)
	    {
	      if (i in array)
	        res[i] = fun.call(thisp, array[i], i, array);
	    }
	
	    return res;
	},
	
	/* Dom and current library functions
			Si vous voulez reduire la taille du calendrier, il faut modifier les fonctions ici, 
			ce sont des fonctions generiques qu'on retrouve dans la plupart des libraries (mootools, prototype)
	*/
	z : {
		$ : function(id) {
			return typeof(id)=='string' ? document.getElementById(id) : id;
		},
		//simule le each/forEach sur array
		each : function(array, fun) {
			var len = array.length;
			for (var i = 0; i < len; i++) {
				if (i in array) {
					fun.call(array, array[i], i, array);
				}
			}
			return array;
		},
		
		addEvent : function(obj, type, fn, useCapture) {
			if (obj.addEventListener) {
				obj.addEventListener( type, fn, useCapture || false);
			} else if (obj.attachEvent) {
				obj["e"+type+fn] = fn;
				obj[type+fn] = function() { obj["e"+type+fn]( window.event ); };
				obj.attachEvent( "on"+type, obj[type+fn] );
			}
		},
		
		stopEvent : function(e) {
			if(e && e.stopPropagation && e.preventDefault) {
				e.stopPropagation();
				e.preventDefault();
			}
			else if(e && window.event) {
				window.event.cancelBubble = true;
				window.event.returnValue = false;
			}
			return false; // Indispensable pour Safari
		},
		
		getPosition : function(obj, overflown) {
			var curleft = 0,
				 curtop = 0;
			if (obj.offsetParent) {
				do {
					curleft += obj.offsetLeft + (overflown ? -obj.scrollLeft : 0) ;
					curtop += obj.offsetTop + (overflown ? -obj.scrollTop : 0) ;
					obj = obj.offsetParent;
				} while (obj);
				
			}
			return {x:curleft,y:curtop};
		},
		
		remove : function(elm) {
			return elm.parentNode.removeChild(elm);
		}
	}
};
