/**
 * @version $Id$
 */

(function($){

var DOC = window.document,
	IE = $.browser.msie,
	IE6 = IE && $.browser.version < 7,
	IE7 = IE && !IE6 && $.browser.version < 8,
	IE8 = IE && !IE6 && !IE7 && $.browser.version < 9;

	
// utils
//******************************************************************************

/**
 * Create element
 * 
 * @param {String} name
 * @return {Element}
 */
function createElm(name)
{
	return DOC.createElement(name);
}

/**
 * Delegate function
 * 
 * @param {Object} obj
 * @param {Function} fn
 * @return {Function}
 */
function delegate(obj, fn)
{
	return function(){ fn.apply(obj, arguments); };
}

/**
 * Create class
 * 
 * @param {Function|Object} extend
 * @param {Object} methods
 * @return {Function}
 */
function createClass(extend, methods)
{
	if (!methods) {
		methods = extend;
		extend = null;
	}
	
	function Class() {
		if (this.__construct) this.__construct.apply(this, arguments);
		else if (extend && extend.__construct) extend.__construct.apply(this, arguments);
	};
	Class.prototype = methods;
	
	if (extend)
		for (var i in extend.prototype)
			if (Class.prototype[i] === undefined)
				Class.prototype[i] = extend.prototype[i];
	
	return Class;
}

// Decor
//******************************************************************************

/**
 * Decor
 * 
 * @class
 */
var Decor = createClass({
	cls: 'jdecor',
	clsFocused: 'jdecor-focused',
	clsDisabled: 'jdecor-disabled',
	
	__construct: function(elm, config)
	{
		this.elm = elm;
		this.$elm = $(elm);
		elm.jdecor = this;
		
		this.isWrap = !config.nowrap;
		
		if (this.isWrap) {
			this.container = $(createElm('span')).insertBefore(elm).append(elm);
		}
		else {
			this.container = $(elm.parentNode);
		}
		
		if (config.cls) this.addClass(config.cls);

		this.container.addClass(this.cls);
		
		this.$elm.bind({
			'focus.decor': delegate(this, this.onFocus),
			'blur.decor': delegate(this, this.onBlur)
		});
	},
	
	destroy: function()
	{
		if (this.isWrap) {
			this.$elm.insertAfter(this.container);
			this.container.remove();
		}
		else {
			this.container.removeClass(this.cls+' '+this.disabledCls+' '+this.focusedCls);
		}
		
		this.$elm.unbind('.decor');
		
		for (var i in this) {
			delete this[i];
			if (this[i] !== undefined) this[i] = null;
		}
	},	

	addClass: function(cls)
	{
		this.cls += ' '+cls;
		this.clsFocused += ' '+cls+'-focused';
		this.clsDisabled += ' '+cls+'-disabled';
	},
	
	update: function()
	{
	
	},
	
	onFocus: function()
	{
		this.container.addClass(this.clsFocused);
	},

	onBlur: function()
	{
		this.container.removeClass(this.clsFocused);
	}
	
});

/**
 * Decor factory
 */
Decor.factory = function(elm, config)
{
	
	if (elm.jdecor) {
		elm.jdecor.update();
		return;
	}
	
	if (!config) config = {}; 
	
	switch (elm.nodeName) {
		case 'INPUT':
			switch (elm.type) {
				case 'text':
					return new TextDecor(elm, config);
				case 'password':
					return new PasswordDecor(elm, config);
				case 'checkbox':
					return new CheckboxDecor(elm, config);
				case 'radio':
					return new RadioDecor(elm, config);
				case 'button':
				case 'submit':
				case 'reset':
					return new ButtonDecor(elm, config);
			}
			break;
		case 'TEXTAREA':
			return new TextareaDecor(elm, config);
		case 'BUTTON':
			return new ButtonDecor(elm, config);
		case 'SELECT':
			return new SelectDecor(elm, config);
	}
}


// TextDecor
//******************************************************************************

/**
 * TextDecor
 * 
 * @class
 * @extends Decor
 */
var TextDecor = createClass(Decor, {
	__construct: function(elm, config)
	{
		this.addClass('jdecor-text');	
		if (config.cls) this.addClass(config.cls+'-text');
		
		Decor.prototype.__construct.apply(this, arguments);
		
		if (!this.container.bb) throw 'Required plugin jquery.bb';
		this.container.bb();
	},
	
	update: function()
	{
		this.container.bb();
	}
});


// PasswordDecor
//******************************************************************************

/**
 * PasswordDecor
 * 
 * @class
 * @extends TextDecor
 */
var PasswordDecor = createClass(TextDecor, {
	__construct: function(elm, config)
	{
		this.addClass('jdecor-password');	
		if (config.cls) this.addClass(config.cls+'-password');
		
		TextDecor.prototype.__construct.apply(this, arguments);
	}
});


// CheckboxDecor
//******************************************************************************

/**
 * CheckboxDecor
 * 
 * @class
 * @extends Decor
 */
var CheckboxDecor = createClass(Decor, {
	clsChecked: 'jdecor-checked',
	
	__construct: function(elm, config)
	{
		this.addClass('jdecor-checkbox');	
		if (config.cls) this.addClass(config.cls+'-checkbox');
		
		Decor.prototype.__construct.apply(this, arguments);
		
		this.img = $(createElm('span')).addClass('jdecor-checkbox-img');
		this.container.append(this.img);
		this.img.append(this.$elm);
		
		var fn = delegate(this, this.onChange);
		this.$elm.bind('change.decor', fn);
		if (IE) this.$elm.bind('click.decor', fn);

		this.onChange();
	},
	
	addClass: function(cls)
	{
		Decor.prototype.addClass.apply(this, arguments);
		this.clsChecked += ' '+cls+'-checked';
	},
	
	onChange: function()
	{
		if (this.checked === this.elm.checked) return;
		this.checked = this.elm.checked;
		
		this.checked
			? this.container.addClass(this.clsChecked)
			: this.container.removeClass(this.clsChecked);
		
		if ($.png) this.img.png();
	}
});


// RadioDecor
//******************************************************************************

/**
 * RadioDecor
 * 
 * @class
 * @extends CheckboxDecor
 */
var RadioDecor = createClass(CheckboxDecor, {
	
	__construct: function(elm, config)
	{
		RadioDecor.all.push(this);
		
		this.addClass('jdecor-radio');	
		if (config.cls) this.addClass(config.cls+'-radio');
		
		CheckboxDecor.prototype.__construct.apply(this, arguments);
		
		if ($.browser.safari) {
			var fn = delegate(this, this.onChange);
			this.$elm.bind('keydown.decor', function(){ setTimeout(fn, 1); });
		}
	},
	
	checkChange: function()
	{
		CheckboxDecor.prototype.onChange.apply(this, arguments);
	},
	
	onChange: function()
	{
		var radio, i = 0;
		while (radio = RadioDecor.all[i++]) radio.checkChange();
	}
});
RadioDecor.all = [];


// TextareaDecor
//******************************************************************************

/**
 * TextareaDecor
 * 
 * @class
 * @extends TextDecor
 */
var TextareaDecor = createClass(TextDecor, {
	__construct: function(elm, config)
	{
		this.addClass('jdecor-textarea');	
		if (config.cls) this.addClass(config.cls+'-textarea');
		
		TextDecor.prototype.__construct.apply(this, arguments);
		
		this.$elm.css('display', 'block');
		
		if (this.$elm.scrollbar) this.$elm.scrollbar({dir: 'both', auto: true});
	}
});


// ButtonDecor
//******************************************************************************

/**
 * ButtonDecor
 * 
 * @class
 * @extends Decor
 */
var ButtonDecor = createClass(Decor, {
	__construct: function(elm, config)
	{
		this.isButtion = elm.nodeName === 'BUTTON';
		
		this.addClass('jdecor-button');	
		if (config.cls) this.addClass(config.cls+'-button');
		
		Decor.prototype.__construct.apply(this, arguments);
		
		if (!this.container.bb) throw 'Required plugin jquery.bb';
		this.container.bb();
		
		this.text = $(createElm('span')).addClass('jdecor-button-text')
			.text(this.isButtion ? this.$elm.text() : this.$elm.val())
			.css({
				lineHeight: this.container.height()+'px',
				width: this.container.width()
			})
			.insertBefore(this.elm);
	},
	
	update: function()
	{
		this.container.bb();
		this.text
			.text(this.isButtion ? this.$elm.text() : this.$elm.val())
			.css({
				lineHeight: this.container.height()+'px',
				width: this.container.width()
			});
	}
});


// SelectDecor
//******************************************************************************

/**
 * SelectDecor
 * 
 * @class
 * @extends Decor
 */
var SelectDecorI = 0;
var SelectDecor = createClass(Decor, {
	__construct: function(elm, config)
	{
		this.i = SelectDecorI++;
		
		
		this.addClass('jdecor-select');	
		if (config.cls) this.addClass(config.cls+'-select');
		
		Decor.prototype.__construct.apply(this, arguments);
		
		if (!this.container.bb) throw 'Required plugin jquery.bb';
		this.container.bb();

		var click = delegate(this, this.onClick);
		
		this.text = $(createElm('input'))
			.addClass('jdecor-select-text')
			.attr('readonly', 'readonly')
			.appendTo(this.container)
			.click(click)
			.attr('unselectable', 'on')
			.focus(delegate(this, this.onFocus))
			.blur(delegate(this, this.onBlur));

		this.btn = $(createElm('span'))
			.addClass('jdecor-select-btn')
			.appendTo(this.container)
			.click(click);
			
		this.listWrap = $(createElm('span'))
			.addClass('jdecor-select-list-wrap')
			.attr('unselectable', 'on')
			.hide()
			.appendTo(DOC.body);
			
		this.list = $(createElm('span'))
			.addClass('jdecor-select-list')
			.appendTo(this.listWrap)
			.mouseover(delegate(this, this.onMouseOver))
			.mouseout(delegate(this, this.onMouseOut));
			
			
		this.$elm.change(delegate(this, this.onChange));
			
		this.lastHtml = this.elm.innerHTML;
		setInterval(delegate(this, this.checkOptions), 500);
			
		this.up();
	},
	
	open: function()
	{
		if (this.opened) return; this.opened = this;
		this.listWrap.show();
		$(DOC).bind('click.decor-select'+this.i, delegate(this, this.onClickDoc));
	},
	
	close: function()
	{
		if (!this.opened) return; this.opened = false;
		this.listWrap.hide();
		this.hover();
		$(DOC).unbind('.decor-select'+this.i);
	},

	select: function()
	{
		if(!this.hoverN) return;
		
		this.setValue(this.hoverN.attr('value'));
	},
	
	setValue: function(v)
	{
		this.$elm.val(v);
		this.$elm.change();
		this.close();
	},
	
	hover: function(n)
	{
		if (n) {
			this.hoverN = n;
			n.addClass('jdecor-select-hover');
		}
		else if(this.hoverN) {
			this.hoverN.removeClass('jdecor-select-hover');
			this.hoverN = null;
		}
	},
	
	onChange: function()
	{
		this.syncText();
	},
	
	onMouseOver: function(e)
	{
		if ($(e.target).parent()[0] === this.list[0])
			this.hover($(e.target));
	},
	
	onMouseOut: function(e)
	{
		this.hover();
	},
	
	onClickDoc: function(e)
	{
		var i = 10000, n = $(e.target), close = true;
		
		if (n.parent()[0] === this.list[0]) {
			this.select();
			return;
		}
		
		while (n[0] !== DOC.body) {
			if (i-- <= 0) break;
			if (n[0] === this.container[0]) {
				close = false;
				break;
			}
			n = n.parent();
		}
		
		if (close) {
			this.close();
			return;
		}
		
	},
	
	onClick: function()
	{
		this.$elm.focus();
		this.opened ? this.close() : this.open();
		return false;
	},
	
	syncText: function()
	{
		if (this.elm.selectedIndex > -1)
			this.text.val(this.elm.options[this.elm.selectedIndex].text);
	},
	
	sync: function()
	{
		this.syncText();
		this.list.empty();
		this.list.width('auto'),
		this.list.height('auto');
		
		for (var o, os = this.elm.options, i = 0, l = os.length; i < l; i++) {
			o = os[i];
			$(createElm('span')).text(o.text).attr('value', o.value).appendTo(this.list);
		}
		
		var cw = this.container.width(),
			w = this.listWrap.width(),
			h = this.listWrap.height();
		
		if (w < cw) w = cw; else w += 17;
		if (h > 200) h = 200;
		
		this.listW = w;
		var p = this.$elm.offset();
		p.top += this.$elm.height();
		this.list.css({width: w, height: h});
		this.listWrap.css(p);
	},
	
	checkOptions: function()
	{
		if (this.lastHtml !== this.elm.innerHTML) {
			this.lastHtml = this.elm.innerHTML;
			this.sync();
		}
	},
	
	up: function()
	{
		var h = this.$elm.height(),
			bw = this.btn.width(),
			w = this.$elm.width() - bw,
			t = this.container.css('paddingTop'),
			l = parseInt(this.container.css('paddingLeft')) || 0;
			
		this.text.css({
			top: t, left: l,
			width: w, height: h,
			paddingTop: this.$elm.css('paddingTop'),
			paddingRight: this.$elm.css('paddingRight'),
			paddingBottom: this.$elm.css('paddingBottom'),
			paddingLeft: this.$elm.css('paddingLeft')
		});
		
		this.btn.css({
			top: t, left: w + l,
			height: h
		});
		
		this.text.show();
		this.btn.show();
		
		this.sync();
	},
	
	update: function()
	{
		this.text.hide();
		this.btn.hide();
		this.up();
		this.container.bb();
	}
});


// plugin
//******************************************************************************

/**
 * Initialize
 */
function initialize()
{
	initialize = function(){};
	
	var css = [
		'.jdecor {display: inline-block; vertical-align: baseline;}',
		'.jdecor input {display: inline-block; border: none; background: none; padding: 0; margin: 0; outline: none;}',
		
		'.jdecor textarea {display: block; border: none; padding: 0; margin: 0; overflow: auto; outline: none;}',
		
		'.jdecor select {display: block; border: none; padding: 0; margin: 0; outline: none; visibility: hidden;}',
		'.jdecor-select-text {position: absolute; display: block; border: none; padding: 0; margin: 0; background: none; cursor: default; -moz-user-select: none; -webkit-user-select: none;}',
		'.jdecor-select-btn {position: absolute; display: block; width: 15px;}',
		'.jdecor-select-list-wrap {position: absolute; z-index: 1000; display: block; background: #fff; cursor: default; -moz-user-select: none; -webkit-user-select: none;}',
		'.jdecor-select-list {display: block; overflow: auto; }',
		'.jdecor-select-list span {display: block; white-space: nowrap;}',
		'.jdecor-select-hover {background: highlight; color: highlighttext;}',
		
		'.jdecor-checkbox {font-size: 0; line-height: 0; width: 13px; height: 13px; vertical-align: middle;}',
		'.jdecor-checkbox input {display: block; width: 100%; height: 100%; opacity: 0;}',
		'.jdecor-checkbox-img {display: block; width: 100%; height: 100%;}',
		
		'.jdecor-button-text {display: block; width: 100%; position: absolute; top: 0; left: 0; text-align: center; z-index:-1;}',
		'.jdecor-button button {opacity: 0;}'
	];
	
	if (!IE8)
		css.push(
			'.jdecor-checkbox-focused .jdecor-checkbox-img {outline: #000 dotted 1px;}',
			'.jdecor-button-focused {outline: #000 dotted 1px;}',
			'.jdecor-select-focused .jdecor-select-text {outline: #000 dotted 1px;}'
		);
	if ($.browser.safari)
		css.push('.jdecor textarea {resize: none;}');
		
	var style = createElm('style'),
		head = DOC.documentElement.firstChild;
		
	style.setAttribute('type', 'text/css');
	
	if (style.styleSheet)
		style.styleSheet.cssText = css.join('');
	else 
		style.appendChild(DOC.createTextNode(css.join('')));
	
	head.insertBefore(style, head.firstChild);
	
	if (IE && $.ie) $.ie.css(style.styleSheet);
}


/**
 * Decorate - jquery plugin 
 */
$.fn.decorate = function(config)
{
	initialize();
	for (var i = 0, l = this.length; i < l; i++) Decor.factory(this[i], config);
	return this;
};

var $hide = $.fn.hide;
$.fn.hide = function()
{
	var l = this.length;
	if (l > 1) for (var i = 0; i < l; i++) $(this[i]).hide();
	
	if (this[0].jdecor) $hide.apply(this[0].jdecor.container, arguments);
	else $hide.apply(this, arguments);
	
	return this;
};
var $show = $.fn.show;
$.fn.show = function()
{
	var l = this.length;
	if (l > 1) for (var i = 0; i < l; i++) $(this[i]).show();
	
	if (this[0].jdecor) $show.apply(this[0].jdecor.container, arguments);
	else $show.apply(this, arguments);
	
	return this;
};


})(jQuery);