/*
Script: pbb-datepicker-1.0-beta.js
	A Javascript class for Mootools 1.2 that adds accessible and unobtrusive date pickers to your form elements

License:
	MIT-style license.

Copyright:
	Copyright (c) 2008 Pokemon_JOJO, <http://www.mibhouse.org/pokemon_jojo/>

Inspiration:
	Some functionality inspired by [Calendar.js](http://electricprism.com/aeron) Copyright (c) 2007 Aeron Glemann, [MIT License](http://opensource.org/licenses/mit-license.php)
*/

var PBBDatePickers = new Class({

	Implements: [Events, Options],

	options: {
		onShow: function(datepicker){
			datepicker.setStyle('visibility', 'visible');
		},
		onHide: function(datepicker){
			datepicker.setStyle('visibility', 'hidden');
		},
		showDelay: 100,
		hideDelay: 100,
		className: null,
		offsets: {x: 0, y: 20},
		
		dateformat: 'd-m-Y',
		
		days: ['Dimanche', 'Lundi', 'Mardi', 'Mercredi', 'Jeudi', 'Vendredi', 'Samedi'], // days of the week starting at sunday
		months: ['Janvier', 'Fevrier', 'Mars', 'Avril', 'Mai', 'Juin', 'Juillet', 'Aout', 'Septembre', 'Octobre', 'Novembre', 'Decembre'],
		weekFirstDay : 1 // first day of the week: 0 = sunday, 1 = monday, etc..
	},
	
	initialize: function(){
		var params = Array.link(arguments, {options: Object.type, elements: $defined});
		this.setOptions(params.options || null);
		this.lock = false;
		
		this.datepicker = new Element('div').addEvents({
			'mouseover': function(){
				this.lock = true;
			}.bind(this),
			'mouseout': function(){
				this.lock = false;
			}.bind(this)
		}).inject(document.body);
		
		if (this.options.className) this.datepicker.addClass(this.options.className);

		var top = new Element('div', {'class': 'datepicker-top'}).inject(this.datepicker);
		this.container = new Element('div', {'class': 'datepicker'}).inject(this.datepicker);
		var bottom = new Element('div', {'class': 'datepicker-bottom'}).inject(this.datepicker);

		this.datepicker.setStyles({position: 'absolute', top: 0, left: 0, visibility: 'hidden'});
		
		if (params.elements) this.attach(params.elements);
	},

	attach: function(elements){
		$$(elements).each(function(element){
			var dateformat = element.retrieve('datepicker:dateformat', element.get('accept'));
			if(!dateformat) {
				dateformat = this.options.dateformat;
				element.store('datepicker:dateformat', dateformat);
			}
			var datevalue = element.retrieve('datepicker:datevalue', element.get('value'));
			if(!datevalue) {
				datevalue = this.format(new Date(), dateformat);
				element.store('datepicker:datevalue', datevalue);
			}
			element.store('datepicker:current', this.unformat(datevalue,dateformat));
			
			var inputFocus = element.retrieve('datepicker:focus', this.elementFocus.bindWithEvent(this, element));
			var inputBlur = element.retrieve('datepicker:blur', this.elementBlur.bindWithEvent(this, element));
			element.addEvents({focus: inputFocus, blur: inputBlur});
			
			element.store('datepicker:native', element.get('accept'));
			element.erase('dateformat');
		}, this);
		return this;
	},

	detach: function(elements){
		$$(elements).each(function(element){
			element.removeEvent('onfocus', element.retrieve('datepicker:focus') || $empty);
			element.removeEvent('onblur', element.retrieve('datepicker:blur') || $empty);
			element.eliminate('datepicker:focus').eliminate('datepicker:blur');
			var original = element.retrieve('datepicker:native');
			if (original) element.set('dateformat', original);
		});
		return this;
	},

	elementFocus: function(event, element){
		this.el = element;
		
		var current = element.retrieve('datepicker:current');
		this.curFullYear = current[0];
		this.curMonth = current[1];
		this.curDate = current[2];
		
		this.build();
		
		this.timer = $clear(this.timer);
		this.timer = this.show.delay(this.options.showDelay, this);

		this.position({page: element.getPosition()});
	},
	
	elementChange: function() {
		this.el.store('datepicker:current', Array(this.curFullYear,this.curMonth,this.curDate));
		this.el.set('value',this.format(new Date(this.curFullYear, this.curMonth, this.curDate),this.el.retrieve('datepicker:dateformat')));
		
		$clear(this.timer);
		this.timer = this.hide.delay(this.options.hideDelay, this);
	},

	elementBlur: function(event){
		if(!this.lock) {			
			$clear(this.timer);
			this.timer = this.hide.delay(this.options.hideDelay, this);
		}
	},
	
	position: function(event){
		var size = window.getSize(), scroll = window.getScroll();
		var datepicker = {x: this.datepicker.offsetWidth, y: this.datepicker.offsetHeight};
		var props = {x: 'left', y: 'top'};
		for (var z in props){
			var pos = event.page[z] + this.options.offsets[z];
			if ((pos + datepicker[z] - scroll[z]) > size[z]) pos = event.page[z] - this.options.offsets[z] - datepicker[z];
			this.datepicker.setStyle(props[z], pos);
		}
	},

	show: function(){
		this.fireEvent('show', this.datepicker);
	},

	hide: function(){
		this.fireEvent('hide', this.datepicker);
	},
	
	build: function() {
		$A(this.container.childNodes).each(Element.dispose);
		
		var table = new Element('table').inject(this.container);
		var caption = this.caption().inject(table);
		var thead = this.thead().inject(table);
		var tbody = this.tbody().inject(table);		
	},
	
	// navigate: calendar navigation 
	// @param type (str) m or y for month or year
	// @param d (int) + or - for next or prev	
	navigate: function(type, d) {
		switch (type) {
			case 'm': // month
				var i = this.curMonth + d;
				
				if (i < 0 || i == 12) {
					this.curMonth = (i < 0) ? 11 : 0;
					this.navigate('y', d);
				}
				else		
					this.curMonth = i;
				
				break;
			case 'y': // year
				this.curFullYear += d;
				
				break;
		}
		
		this.el.store('datepicker:current', Array(this.curFullYear,this.curMonth,this.curDate));
		
		this.el.focus(); // keep focus and do automatique rebuild ;)
	},

	// caption: returns the caption element with header and navigation
	// @returns caption (element)
	caption: function() {
		// start by assuming navigation is allowed
		var navigation = {
			prev: { 'month': true, 'year': true },
			next: { 'month': true, 'year': true }
		};
		
		var caption = new Element('caption');

		var prev = new Element('a').addClass('prev').appendText('\x3c'); // <		
		var next = new Element('a').addClass('next').appendText('\x3e'); // >

		var month = new Element('span').addClass('month').inject(caption);			
		if (navigation.prev.month) { prev.clone().addEvent('click', function() { this.navigate('m', -1); }.bind(this)).inject(month); }
		new Element('span').set('text', this.options.months[this.curMonth]).addEvent('mousewheel', function(e){ e.stop();this.navigate('m', (e.wheel < 0 ? -1 : 1));this.build();}.bind(this)).inject(month);		
		if (navigation.next.month) { next.clone().addEvent('click', function() { this.navigate('m', 1); }.bind(this)).inject(month); }

		var year = new Element('span').addClass('year').inject(caption);			
		if (navigation.prev.year) { prev.clone().addEvent('click', function() { this.navigate('y', -1); }.bind(this)).inject(year); }
		new Element('span').set('text', this.curFullYear).addEvent('mousewheel', function(e){ e.stop();this.navigate('y', (e.wheel < 0 ? -1 : 1));this.build();}.bind(this)).inject(year);
		if (navigation.next.year) { next.clone().addEvent('click', function() { this.navigate('y', 1); }.bind(this)).inject(year); }
		
		return caption;		
	},
	
	// thead: returns the thead element with day names
	// @returns thead (element)
	thead: function() {
		var thead = new Element('thead');
		var tr = new Element('tr').inject(thead);
		for (i = 0; i < 7; i++)	{ 
			new Element('th').set('text', this.options.days[(this.options.weekFirstDay + i)%7].substr(0, 2)).inject(tr);
		}
		
		return thead;
	},
	
	// tbody: returns the tbody element with day numbers
	// @returns tbody (element)
	tbody: function() {
		var d = new Date(this.curFullYear, this.curMonth, 1);
		
		var offset = ((d.getDay() - this.options.weekFirstDay) + 7) % 7; // day of the week (offset)
		var last = new Date(this.curFullYear, this.curMonth + 1, 0).getDate(); // last day of this month
		var prev = new Date(this.curFullYear, this.curMonth, 0).getDate(); // last day of previous month
		
		var v = (this.el.get('value')) ? this.unformat(this.el.get('value'),this.el.retrieve('datepicker:dateformat')) : false;
		var current = new Date(v[0], v[1], v[2]).getTime();
		
		var d = new Date();
		var today = new Date(d.getFullYear(), d.getMonth(), d.getDate()).getTime(); // today obv 
		
		var tbody = new Element('tbody');
		
		tbody.addEvent('mousewheel', function(e){
			e.stop(); // prevent the mousewheel from scrolling the page.
			this.navigate('m', (e.wheel < 0 ? -1 : 1));
			this.build();
		}.bind(this));		
		
		for (var i = 1; i < 43; i++) { // 1 to 42 (6 x 7 or 6 weeks)
			if ((i - 1) % 7 == 0) { tr = new Element('tr').inject(tbody); } // each week is it's own table row
			
			var td = new Element('td').inject(tr);
			var day = i - offset;
			var date = new Date(this.curFullYear, this.curMonth, day);

			if (day < 1) { // last days of prev month
				day = prev + day;
				td.addClass('inactive');
			}
			else if (day > last) { // first days of next month
				day = day - last;
				td.addClass('inactive');
			}
			else {				
				if(date.getTime() == current)  { td.addClass('hilite');  }
				else if (date.getTime() == today) { td.addClass('today');  } // add class for today
				
				td.addEvents({
					'click': function(day) {
						this.curDate = day;
						this.elementChange();
					}.bind(this, day),
					'mouseover': function(td) { 
						td.addClass('hilite'); 
					}.bind(this, td),
					'mouseout': function(td, date) {
						if(date.getTime() != current)
							td.removeClass('hilite'); 
					}.bind(this, [td, date])
				}).addClass('active');
			}
			
			td.set('text',day);
		}	
		return tbody;		
	},

	// unformat: takes a value from an input and parses the d, m and y elements
	// @param val (string)
	// @param f (string) any combination of punctuation / separators and d, j, D, l, S, m, n, F, M, y, Y
	// @returns array
	unformat: function(val, f) {
		f = f.escapeRegExp();
		
		var re = {
			d: '([0-9]{2})',
			j: '([0-9]{1,2})',
			D: '(' + this.options.days.map(function(day) { return day.substr(0, 3); }).join('|') + ')',					
			l: '(' + this.options.days.join('|') + ')',
			S: '(st|nd|rd|th)',
			F: '(' + this.options.months.join('|') + ')',
			m: '([0-9]{2})',
			M: '(' + this.options.months.map(function(month) { return month.substr(0, 3); }).join('|') + ')',					
			n: '([0-9]{1,2})',
			Y: '([0-9]{4})',
			y: '([0-9]{2})'
		}

		var arr = []; // array of indexes

		var g = '';

		// convert our format string to regexp
		for (var i = 0; i < f.length; i++) {
			var c = f.charAt(i);
			
			if (re[c]) {
				arr.push(c);

				g += re[c];
			}
			else {
				g += c;
			}
		}

		// match against date
		var matches = val.match('^' + g + '$');
		
		var dates = new Array(3);

		if (matches) {
			matches = matches.slice(1); // remove first match which is the date

			arr.each(function(c, i) {
				i = matches[i];
				
				switch(c) {
					// year cases
					case 'y':
						i = '19' + i; // 2 digit year assumes 19th century (same as JS)
					case 'Y':
						dates[0] = i.toInt();
						break;

					// month cases
					case 'F':
						i = i.substr(0, 3);
					case 'M':
						i = this.options.months.map(function(month) { return month.substr(0, 3); }).indexOf(i) + 1;
					case 'm':
					case 'n':
						dates[1] = i.toInt() - 1;
						break;

					// day cases
					case 'd':
					case 'j':
						dates[2] = i.toInt();
						break;
				}
			}, this);
		}
		
		dates[0] = (dates[0]) ? dates[0] : new Date().getFullYear();
		dates[1] = (dates[1]) ? dates[1] : new Date().getMonth();
		dates[2] = (dates[2]) ? dates[2] : new Date().getDate();

		return dates;
	},
	
	// format: formats a date object according to passed in instructions
	// @param date (obj)
	// @param format (string) any combination of punctuation / separators and d, j, D, l, S, m, n, F, M, y, Y
	// @returns string
	format: function(date, format) {
		var str = '';
		
		if (date) {
			var j = date.getDate(); // 1 - 31
      		var w = date.getDay(); // 0 - 6
			var l = this.options.days[w]; // Sunday - Saturday
			var n = date.getMonth() + 1; // 1 - 12
			var f = this.options.months[n - 1]; // January - December
			var y = date.getFullYear() + ''; // 19xx - 20xx
			
			for (var i = 0, len = format.length; i < len; i++) {
				var cha = format.charAt(i); // format char
				
				switch(cha) {
					// year cases
					case 'y': // xx - xx
						y = y.substr(2);
					case 'Y': // 19xx - 20xx
						str += y;
						break;
	
					// month cases
					case 'm': // 01 - 12
						if (n < 10) { n = '0' + n; }
					case 'n': // 1 - 12
						str += n;
						break;
	
					case 'M': // Jan - Dec
						f = f.substr(0, 3);
					case 'F': // January - December
						str += f;
						break;
	
					// day cases
					case 'd': // 01 - 31
						if (j < 10) { j = '0' + j; }
					case 'j': // 1 - 31
						str += j;
						break;
	
					case 'D': // Sun - Sat
						l = l.substr(0, 3);
					case 'l': // Sunday - Saturday
						str += l;
						break;
	
					case 'N': // 1 - 7
						w += 1;
					case 'w': // 0 - 6
						str += w;
						break;

					case 'S': // st, nd, rd or th (works well with j)
						if (j % 10 == 1 && j != '11') { str += 'st'; }
						else if (j % 10 == 2 && j != '12') { str += 'nd'; }
						else if (j % 10 == 3 && j != '13') { str += 'rd'; }
						else { str += 'th'; }
						break;
	
					default:
						str += cha;
				}
			}
		}

	  return str; //  return format with values replaced
	}
	
});
