var JsDate = new Class({

	Extends: Options,
	
	options: {
		relative: null,
		format: 'mm/dd/yyyy',
		min: null
	},
	
  	initialize: function(input, options) {
		this.input = typeof(input) == 'string' ? $(input) : input;
		this.setOptions(options);
		
		this.setup();
		
		this.cont.addEvents({
			'click': this.clicked.bind(this),
			'mousedown': this.mouseDown.bind(this),
			'mouseup': this.mouseUp.bind(this)
		});
		this.input.addEvents({
			'focus': this.show.bind(this),
			'blur': this.hide.bind(this, true)
		});
	},
	
	setup: function() {
		this.cont = new Element('div', { 'class': 'js-date' }).inject(this.input.getParent());
		new Element('p', { 'class': 'year' })
			.adopt(new Element('input', { 'class': 'nav', 'type': 'button', 'value': '<' }))
			.adopt(new Element('span'))
			.adopt(new Element('input', { 'class': 'nav', 'type': 'button', 'value': '>' }))
			.inject(this.cont);
		new Element('p', { 'class': 'month' })
			.adopt(new Element('input', { 'class': 'nav', 'type': 'button', 'value': '<' }))
			.adopt(new Element('span'))
			.adopt(new Element('input', { 'class': 'nav', 'type': 'button', 'value': '>' }))
			.inject(this.cont);
		new Element('div')
			.adopt(new Element('table', { 'cellpadding': 0, 'cellspacing': 1 })
				.adopt(new Element('tbody').adopt(new Element('tr', { 'class': 'head' })
					.adopt(new Element('td').set('text', 'S'))
					.adopt(new Element('td').set('text', 'M'))
					.adopt(new Element('td').set('text', 'T'))
					.adopt(new Element('td').set('text', 'W'))
					.adopt(new Element('td').set('text', 'T'))
					.adopt(new Element('td').set('text', 'F'))
					.adopt(new Element('td').set('text', 'S'))
				)
			))
			.inject(this.cont);
	},
	
	buildCalendar: function() {
		this.cont.getElement('p.year span').set('text', this.date.getFullYear());
		this.cont.getElement('p.month span').set('text', this.getMonthLabel(this.date.getMonth()));
		this.cont.getElements('tr[class=l]').dispose();
		
		var start = this.date.getDay(),
			noDays = this.getDaysInMonth(this.date),
			loop = Math.ceil((noDays + start) / 7) * 7,
			tbody = this.cont.getElement('tbody'),
			date, tr, cl, day, i;
		for (i = 1 - start; i <= loop - start; i++) {
			if ((i + start) % 7 == 1) tr = new Element('tr', { 'class': 'l' }).inject(tbody);
			date = new Date(this.date);
			date.setDate(i);
			day = date.getDate();
			cl = i < 1 || i > noDays ? 'o' : 'r';
			if (this.dateMin && this.getDateYmd(date) < this.getDateYmd(this.dateMin)) cl = 'd ' + cl;
			if (cl.indexOf('o') == -1) {
				if (this.getDateYmd(date) == this.getDateYmd(this.dateCurr)) cl += ' t';
				if (this.dateInput && this.getDateYmd(date) == this.getDateYmd(this.dateInput)) cl += ' c';
			}
			new Element('td', { 'class': cl }).set('text', day).inject(tr);
		}
		
		this.cont.getElements('table td[class^=r]').addEvents({
			'mouseover': function() { this.addClass('h') },
			'mouseout': function() { this.removeClass('h') }
		});
	},
	
	setWorkingMonth: function() {
		this.dateCurr = new Date();
		this.dateCurr.setHours(1, 0, 0);
		if (this.input.value && Date.parse(this.input.value)) {
			this.dateInput = new Date(this.input.value);
			this.dateInput.setHours(1, 0, 0);
			this.date = new Date(this.dateInput);
		}
		else
			this.date = new Date(this.dateCurr);
		this.date.setDate(1);
		if (this.options.min)
			switch (this.options.min) {
				case 'present':
					this.dateMin = new Date(this.dateCurr);
					break;
				default:
					this.dateMin = new Date(this.options.min);
					this.dateMin.setHours(1, 0, 0);
			}
	},
	
	clicked: function(event) {
		var target = event.target;
		
		if (target.tagName == 'INPUT' && target.hasClass('nav')) {
			// year + month navigation arrows
			var mode = target.getParent('p').hasClass('year') ? 12 : 1;
			var inc = target.value == '>' ? 1 : -1;
			
			this.date.setMonth(this.date.getMonth() + mode * inc);
			this.buildCalendar();
		}
		else if (target.tagName == 'TD' && target.hasClass('o')) {
			// click on next / prev months days
			this.date.setMonth(this.date.getMonth() + (target.get('text').toInt() < 15 ? 1 : -1));
			this.buildCalendar();
		}
		else if (target.tagName == 'TD' && target.hasClass('r') && !target.hasClass('d')) {
			// date selector
			var day = event.target.get('text').toInt();
			var ret = '';
			var splitter = this.options.format.replace(/\w+/, '').substr(0, 1);
			var items = this.options.format.split(splitter);
			for (var i = 0; i < items.length; i++) {
				if (ret) ret += splitter;
				switch (items[i]) {
					case 'mm':
						ret += (this.date.getMonth() < 9 ? '0' : '') + (this.date.getMonth() + 1);
						break;
					case 'dd':
						ret += (day < 10 ? '0' : '') + day;
						break;
					case 'yyyy':
						ret += this.date.getFullYear();
						break;
				}
			}
			this.input.value = ret;
			this.hide();
			this.input.blur();
		}
	},
	
	mouseDown: function() {
		$clear(this.int);
		this.input.removeEvents();
	},
	
	mouseUp: function() {
		this.input.focus();
		this.input.addEvents({
			'focus': this.show.bind(this),
			'blur': this.hide.bind(this, true)
		});
	},
	
	hide: function() {
		if (arguments.length == 1) {
			this.int = this.hide.delay(1, this);
			return;
		}
		this.cont.setStyle('display', 'none');
	},
	
	show: function() {
		if (this.cont.getStyle('display') == 'block')
			return;
			
		var pos = this.input.getPosition(this.options.relative ? this.options.relative : null);
		var size = this.input.getSize();
		this.cont.setStyles({ left: pos.x - 1, top: pos.y + size.y + 1, 'display': 'block' });
		
		this.setWorkingMonth();
		this.buildCalendar();
	},
	
	getMonthLabel: function(m) {
		switch (m) {
			case 0:
				return 'Jan';
				break;
			case 1:
				return 'Feb';
				break;
			case 2:
				return 'Mar';
				break;
			case 3:
				return 'Apr';
				break;
			case 4:
				return 'May';
				break;
			case 5:
				return 'Jun';
				break;
			case 6:
				return 'Jul';
				break;
			case 7:
				return 'Aug';
				break;
			case 8:
				return 'Sep';
				break;
			case 9:
				return 'Oct';
				break;
			case 10:
				return 'Nov';
				break;
			default:
				return 'Dec';
		}
	},
	
	getDaysInMonth: function(date) {
		var days = [31, 0, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
		if (date.getMonth() != 1)
			return days[date.getMonth()];
		else
			return (this.date.year % 4 == 0 && this.date.year % 100 != 0) || this.date.year % 400 == 0 ? 29 : 28;
	},
	
	getDateYmd: function(date) {
		var month = date.getMonth() + 1;
		var day = date.getDate();
		return date.getFullYear() + (month < 10 ? '-0' : '-') + month + (day < 10 ? '-0' : '-') + day;
	}
	
});
