(function(namespace) {
	namespace = namespace || window;
	
	// Elements to clone
	var elements = {
		div: document.createElement('div'),
		span: document.createElement('span'),
		ul: document.createElement('ul'),
		li: document.createElement('li')
	};
	
	// Class names defaults
	var classNames = {
		select: {
			container: 'fauxSelect',
			focus: 'focus',
			open: 'opened',
			close: 'closed',
			select: 'selected',
			hover: 'hover',
			scroll: 'scrolling'
		},
		checkbox: {
			container: 'fauxCheckbox',
			check: 'checked',
			focus: 'focus'
		},
		radio: {
			container: 'fauxRadio',
			check: 'checked',
			focus: 'focus'
		}
	};
	
	// Change class names defaults
	var setClassNames = function(type, datas) {
		var o;
		switch(type.toLowerCase()) {
			case 'select':
				o = classNames.select;
				break;
			case 'checkbox':
				o = classNames.checkbox;
				break;
			case 'radio':
				o = classNames.radio;
				break;
			default:
				throw new Error('FauxFields.setClassNames does not support this field type');
				return;
		}
		for(var k in datas) {
			if(o[k]) {o[k] = datas[k];}
		}
	};
	
	// Faux Select Constructor
	var Select = function(o, id, mH) {
		var that = this;
		
		this.select = o;
		this.lastVal = o.value;
		this.lastIndex = o.selectedIndex;
		$(o).focus(function() {that.focus();}).blur(function() {that.blur();}).keyup(keyUp).keydown(keyDown);
		this.container = $(elements.div.cloneNode(true)).attr('id', id + 'REP').addClass(classNames.select.container);
		this.value = $(elements.span.cloneNode(true)).click(clickValue);
		this.list = $(elements.ul.cloneNode(true)).addClass(classNames.select.close);
		this.maxHeight = mH || false;
		this.container.append(this.value).append(this.list);
		this.preventClose = false;
		this.timer = null;
		
		this.fill();
		this.container.insertAfter(o);
		
		this.height(this.maxHeight);
		if(that.last) {that.list[0].scrollTop = that.last.offset().top - that.list.offset().top;}
		
		$(document).mousedown(function(e) {
			if(that.list.hasClass(classNames.select.open)) {
				var t = e.target;
				while(t.parentNode && t.nodeName.toLowerCase() != 'div') {
					if($(t).attr('for') == id) {return;}
					t = t.parentNode;
				}
				if(!t || (t && t != that.container[0])) {that.close();}
			}
		});
		
		function keyDown(e) {
			var k = e.which;
			if(k == 9) {
				that.close();
			}
		}
		
		function keyUp(e) {
			var k = e.which;
			if(e.altKey && (k == 38 || k == 40)) {
				clickValue();
				return;
			}
			if(that.list.hasClass(classNames.select.open)) {
				switch(k) {
					case 13:
					case 27:
						that.close();
						break;
					case 37:
					case 38:
						if(that.lastIndex > 0) {that.update(that.items[that.lastIndex - 1], true);}
						break;
					case 39:
					case 40:
						if(that.lastIndex + 1 < that.items.length) {that.update(that.items[that.lastIndex + 1], true);}
						break;
					case 34:
					case 35:
						that.update(that.items[that.items.length - 1], true);
						break;
					case 33:
					case 36:
						that.update(that.items[0], true);
						break;
					default:
						that.deselectLi();
						that.selectLi(that.items[o.selectedIndex]);
				}
			} else {
				that.deselectLi();
				that.selectLi(that.items[o.selectedIndex]);
			}
		}
		
		function clickValue(e) {
			if(that.disabled) {return;}
			that.list.hasClass(classNames.select.open) ? that.close(true) : that.open();
		}
	};
	Select.prototype = {
		fill: function(datas, clean) {
			if(datas) {
				if(clean) {this.select.options.length = 0;}
				var k = -1, sel;
				while(datas[++k]) {
					if(datas[k][2]) {sel = k;}
					this.select.options[this.select.options.length] = new Option(datas[k][0], datas[k][1]);
				}
				this.select.selectedIndex = sel;
			}
			var that = this, li = '', s, sO, sI = that.select.selectedIndex;
			this.value.html(this.select.options[this.select.selectedIndex].innerHTML);
			$(this.select).children().each(function(i) {
				sO = (this.className) ? ' ' + this.className : '';
				s = (sI == i) ? ' class="' + classNames.select.select + sO + '"' : '';
				li += '<li' + s + '>' + this.innerHTML + '</li>';
			});
			this.list.mousedown(function(e) {
				if(e.target == that.list[0]) {
					that.preventClose = true;
					return true;
				}
				that.update(e.target);
				that.timer = setTimeout(function() {that.select.focus();}, 20);
			})[0].innerHTML = li;
			this.items = this.list.children().each(function(i) {
				this.val = that.select.options[i].value;
			}).hover(that.hover, that.out);
			if(datas) {
				this.height(this.maxHeight);
			}
			this.last = $(this.items[sI]);
		},
		
		deselectLi: function() {
			this.last.removeClass(classNames.select.select);
		},
		
		selectLi: function(li) {
			this.value.text(li.innerHTML);
			this.last = $(li);
			this.last.addClass(classNames.select.select);
			if(this.maxHeight) {
				var sTop = this.list[0].scrollTop;
				var sMax = sTop + this.maxHeight;
				var oTop = sTop + this.last.offset().top - this.list.offset().top;
				var oMax = oTop + this.last[0].offsetHeight;
				if(oTop < sTop || oMax > sMax) {this.list[0].scrollTop = oTop;}
			}
			if(this.onChanged && this.lastVal != li.val) {
				this.onChanged();
			}
			this.lastVal = li.val;
			this.lastIndex = this.select.selectedIndex;
		},
		
		update: function(li, preventClose) {
			if(typeof li == 'string') {
				this.select.value = li;
				var that = this;
				$('li', this.list).each(function() {
					if(this.val == li) {
						that.deselectLi();
						that.selectLi(this);
					}
				});
			} else if(li.nodeName && li.nodeName.toLowerCase() == 'li') {
				this.select.value = li.val;
				this.deselectLi();
				this.selectLi(li);
			}
			if(!preventClose) {this.close();}
		},
		
		height: function(h) {
			if(h && this.list.height() > h) {
				this.list.addClass(classNames.select.scroll);
				this.list.height(h);
				return;
			} else {
				this.list.height('auto');
			}
			if(!h) {
				return this.list.height();
			}
		},
		
		open: function() {
			this.focus();
			this.list.removeClass(classNames.select.close);
			this.list.addClass(classNames.select.open);
			this.select.focus();
		},
		
		close: function(keepFocus) {
			this.list.removeClass(classNames.select.open);
			this.list.addClass(classNames.select.close);
			if(keepFocus) {
				this.select.focus();
			}
		},
		
		focus: function() {
			if(this.disabled) {return;}
			this.container.addClass(classNames.select.focus);
			clearTimeout(this.timer);
		},
		
		blur: function() {
			if(!this.preventClose) {
				this.container.removeClass(classNames.select.focus);
			}
			this.preventClose = false;
		},
		
		hover: function() {
			$(this).addClass(classNames.select.hover);
		},
		
		out: function() {
			$(this).removeClass(classNames.select.hover);
		}
	};
	
	// Faux Checkbox Constructor
	var Checkbox = function(o, id) {
		var that = this;
		
		this.checkbox = o;
		$(o).focus(function() {that.focus();}).blur(function() {that.blur();}).click(clickContainer);
		this.container = $(elements.div.cloneNode(true));
		this.container.addClass(classNames.checkbox.container);
		this.container.attr('id', id + 'REP');
		this.box = $(elements.div.cloneNode(true));
		if(o.checked) {this.box.addClass(classNames.checkbox.check);}
		this.container.append(this.box);
		
		this.container.insertAfter(o);
		
		this.container.click(clickContainer);
		
		function clickContainer() {
			that.box.hasClass(classNames.checkbox.check) ? that.uncheck() : that.check();
		}
	};
	Checkbox.prototype = {
		check: function() {
			this.checkbox.checked = true;
			this.box.addClass(classNames.checkbox.check);
			if(this.onChanged) {
				this.onChanged(true);
			}
		},
		
		uncheck: function() {
			this.checkbox.checked = false;
			this.box.removeClass(classNames.checkbox.check);
			if(this.onChanged) {
				this.onChanged(false);
			}
		},
		
		focus: function() {
			this.container.addClass(classNames.checkbox.focus);
		},
		
		blur: function() {
			this.container.removeClass(classNames.checkbox.focus);
		}
	};
	
	// conserver le dernier radio pour chaque groupe
	var radioLast = {};
	
	// Faux Checkbox Radio
	var Radio = function(o, id) {
		var that = this;
		
		this.radio = o;
		this.name = o.name;
		this.repID = id + 'REP';
		$(o).focus(function() {that.focus();}).blur(function() {that.blur();}).click(function() {that.check();});
		this.container = $(elements.div.cloneNode(true));
		this.container.addClass(classNames.radio.container);
		this.container.attr('id', this.repID);
		this.box = $(elements.div.cloneNode(true));
		if(o.checked) {
			this.box.addClass(classNames.radio.check);
			radioLast[this.name] = '#' + this.repID;
		}
		this.container.append(this.box);
		
		this.container.insertAfter(o);
		
		this.container.click(function() {that.check();});
	};
	Radio.prototype = {
		check: function() {
			this.radio.checked = true;
			if(radioLast[this.name]) {$(radioLast[this.name]).children().removeClass(classNames.radio.check);}
			this.box.addClass(classNames.radio.check);
			radioLast[this.name] = '#' + this.repID;
			if(this.onChanged) {
				this.onChanged();
			}
		},
		
		focus: function() {
			this.container.addClass(classNames.radio.focus);
		},
		
		blur: function() {
			this.container.removeClass(classNames.radio.focus);
		}
	};
	
	// Faux Fields Manager
	var Manager = function(datas) {
		var that = this;
		
		this.collection = {};
		
		var s = '';
		if(datas.context) {s = '#' + datas.context + ' ';}
		var func;
		switch(datas.field.toLowerCase()) {
			case 'select':
				s += 'select';
				func = Select;
				break;
			case 'checkbox':
				s += 'input[type=checkbox]';
				func = Checkbox;
				break;
			case 'radio':
				s += 'input[type=radio]';
				func = Radio;
				break;
			default:
				throw new Error('FauxField type not supported');
				return;
		}
		$(s).each(function() {
			that.collection[this.id] = new func(this, this.id, datas.maxHeight);
			$(this).addClass('hide');
		});
	};
	var _Manager = function(datas) {
		return new Manager(datas);
	};
	
	if(!namespace.FauxFields) {
		namespace.FauxFields = {
			Manager: _Manager,
			setClassNames: setClassNames
		};
	}
})();