/**
 * @author SowingSadness
 * @mail sowingsadness@gmail.com
 */
//var $ = window.jQuery ? window.jQuery : {};
( function( $, document, undefined ) {

	function prepareOptions( pluginObject ) {
		var oOptions = pluginObject.options;
		/*
		 * generate for uniq event id
		 */
		oOptions.__id = Math.floor(Math.random()*Date.parse(new Date())/10000).toString();
		if ( ! oOptions.source ) {
			oOptions.source = pluginObject.element;
		}
		if ( ! oOptions.target ) {
			oOptions.target = pluginObject.element;
		}
		if ( ! (oOptions.source instanceof Function) ) {
			oOptions.autocomplete = false;
		}
		if ( oOptions.autocomplete ) {
			oOptions.allowFilter = true;
		}
	}
	function prepareItems( $Object ) {
		//var $optionGroups = $select.children( 'optgroup' );
		var arrOptionData = [];
		$Object.each( function( nIndex, domElement ) {
			$oItem = $( domElement );
			var oData = {
				name: $oItem.text(),
				value: $oItem.val()
			};
			var aExtendData = getDataAttrs( $oItem );
			$.extend( oData, aExtendData );
			arrOptionData.push( oData );
		});

		return arrOptionData;
	}
	function prepareSource( source ) {
		if ( source === undefined ) {
			return null;
		}
		var data = {};
		if ( source instanceof jQuery ) {
			data['id']			=	source.attr( 'id' );
			data['name']		=	source.attr( 'name' );
			data['titlename']	=	source.attr( 'data-titlename' );

			data['tabindex']	=	source.attr( 'tabindex' );
			//data['tabindex']	= (data['tabindex'] !== undefined) ? 'tabindex=' + data['tabindex'] : undefined;

			data['items']		=	prepareItems( source.children( 'option' ) );

			var selectedItem	= source.children( 'option[selected]' );
			var selectedValue	= selectedItem.val();
			var selectedDataVal	= selectedItem.attr( 'data-value' );
			if ( selectedDataVal === undefined ) {
				selectedDataVal	= selectedValue;
			}

			data['selected']	=	selectedDataVal;
		} else if ( source instanceof Array ) {
			data['items'] = source;
		} else if ( source instanceof Function ) {
			data['items'] = [];
		} else {
			return null;
		}

		return data;
	}
	function getDataAttrs( $Object ) {
		var prefix = 'data-';
		var oAttrsData = {};
		var sAttrs = $Object[0].attributes;
		if ( sAttrs !== undefined ) {
			for ( var i=0; i < sAttrs.length; i++ ) {
				if ( sAttrs[i].name.substr( 0, prefix.length ).toLowerCase() === prefix ) {
					var sAttrName = sAttrs[i].name.substr( prefix.length );
					oAttrsData[sAttrName] = sAttrs[i].textContent ? sAttrs[i].textContent : sAttrs[i].value ;
				}
			}
		}

		return oAttrsData;
	}
	function returnHookKeys() {
		return [$.ui.keyCode.RIGHT,$.ui.keyCode.DOWN,$.ui.keyCode.LEFT,$.ui.keyCode.UP,$.ui.keyCode.ENTER];
	}
	function inArray ( arr, val ) {
		for ( var x in arr ) {
			if ( arr.hasOwnProperty( x ) && (arr[x] === val) ) {
				return true;
			}
		}
		return false;
	}

	function shiftItemActivity( pluginObj, direction ) {
		var selectShift, slectorShift;
		if ( direction === 'next' ) {
			selectShift = function ( $activeItem ) { return $activeItem.nextAll( ':visible:first' ); }; //next not work
			slectorShift = ':visible:first';
		} else {
			selectShift = function ( $activeItem ) { return $activeItem.prevAll( ':visible:first' ); };
			slectorShift = ':visible:last';
		}

		var $activeItem = pluginObj.jobjects.itemList.children( '.active' );
		if ( $activeItem.length === 0 ) {
			$activeItem = pluginObj.jobjects.itemList.children( slectorShift );
		} else {
			var $shiftItem = selectShift( $activeItem );

			if ( $shiftItem.length === 0 ) {
				$activeItem = pluginObj.jobjects.itemList.children( slectorShift );
			} else {
				$activeItem = $shiftItem;
			}
		}
		if ( $activeItem.length > 0 ) {
			pluginObj._itemOver( {targetItem: $activeItem} );
		}
	}

	var pluginObject = {
		/**
		 * jQuery.ui.widget::options
		 */
		options: {
			'target'		:	undefined,
			'source'		:	undefined,
			'templateList'	:	'<ul></ul>',
			'templateItem'	:	'<li data-value="${value}" data-filter="${name}">${name}</li>',
			'width'			:	'180px',
			'height'		:	'22px',
			'tabindex'		:	true,
			'allowInput'	:	false,
			'allowFilter'	:	true,
			'maxVisibleItems' : false,
			'autocomplete'	:	false,
			/**
			 * source-info
			 */
			'id'			: undefined,
			'name'			: undefined,
			'titlename'		: undefined,
			'selected'		: undefined,
			'class'			: undefined
		},
		closeTimer: [],
		outStackTime: 120,
		/**
		 * jQuery.ui.widget::destroy()
		 */
		destroy: function() {
			//$.Widget.prototype.destroy.apply( this, arguments ); // default destroy
			// now do other stuff particular to this widget
		},
		_keyHook: function ( event ) {
			var pluginObj = this;
			switch ( event.which ) {
				case $.ui.keyCode.RIGHT:
				case $.ui.keyCode.DOWN:
					//вниз
					shiftItemActivity( this, 'next' );
					this._reposScrollArea( event );
					event.stopPropagation();
					break;
				case $.ui.keyCode.LEFT:
				case $.ui.keyCode.UP:
					//вверх
					shiftItemActivity( this, 'prev' );
					this._reposScrollArea( event );
					event.stopPropagation();
					break;
				case $.ui.keyCode.ENTER:
					var $activeItem = this.jobjects.itemList.children('.active');
					if ( $activeItem.length > 0 ) {
						event.targetItem = $activeItem[0];
						this._itemSelect( event );
					} else if ( this.options.allowInput ) {
						//Hack - i don't know how do it better...
						this.itemNewSet( event, this.jobjects.enteredInput.val() );
					}
					event.stopPropagation();
					//return focus to input - to restore tabindex chain
					this.jobjects.input.focus();
					break;
				case $.ui.keyCode.TAB:
					//return focus to input - to restore tabindex chain
					this.jobjects.input.focus();
					setTimeout( function () { pluginObj.comboboxBlur( event ); }, 1 );
					break;
			}
		},
		clearTimeout: function () {
			for (var x in this.closeTimer ) {
				clearTimeout( this.closeTimer[x] );
			}
			this.closeTimer = [];
		},
		activateEventHandlers: function () {
			var pluginObj = this;
			/*
			 * dont send focus event on parent input
			 *
			 */
			this.jobjects.enteredInput.bind( 'keydown', function ( event ) { pluginObj._keyHook( event );} )
				.bind( 'keypress', function ( event ) {
					var arrKeyCodes = returnHookKeys();
					// отсекаю обработку левых действий при нажатии функц клавиш
					return ! inArray( arrKeyCodes, event.keyCode );
				} );

			this.jobjects.itemList.delegate( '.jqcmbx-li', "mouseover", function( event ) { pluginObj._itemOver( event ); })
								.delegate( '.jqcmbx-li', 'click', function( event ) { pluginObj._itemClick( event ); } );;

			this.jobjects.combobox.bind( 'keyup', function( event ) { pluginObj.filterList( event ); } )
				.bind( 'click', function( event ) { pluginObj._comboboxClick( event ); } )
				.bind( 'focusin', function( event ) {
					if ( ! pluginObj.listVisible ) {
						pluginObj.comboboxFocus( event );
					}
				} );
		},
		deactivateEventHandlers: function () {
			this.jobjects.enteredInput.unbind( 'keydown' )
									.unbind( 'keypress' );
			this.jobjects.items.unbind( 'mouseover' )
									.unbind( 'click' );
		},

		_showEnteredField: function ( event ) {
			(! this.options.allowFilter) && this.jobjects.enteredInput.attr( 'readonly', 'readonly' ) || this.jobjects.enteredInput.removeAttr( 'readonly', 'readonly' );
			this.options.allowFilter && this.jobjects.visibleInput.hide();
			this.jobjects.enteredInput.focus();
		},
		_hideEnteredField: function ( event ) {
			(! this.options.allowFilter) && this.jobjects.enteredInput.attr( 'readonly', 'readonly' ) || this.jobjects.enteredInput.removeAttr( 'readonly', 'readonly' );
			this.jobjects.enteredInput.blur();
			/* this.jobjects.enteredInput.hide();*/
			this.jobjects.enteredInput.val( '' );
			this.jobjects.visibleInput.show();
		},

		comboboxClose: function ( event ) {
			this.clearTimeout();
			this._hideEnteredField( event );
			this._itemListHide( event );
		},
		comboboxOpen: function ( event ) {
			this.filterList( event );
			this._itemListShow( event );
			this._showEnteredField( event );
		},
		comboboxFocus: function ( event ) {
			var pluginObj = this;
			this._trigger( 'focus', event, this );

			this.comboboxOpen( event );
			// USE IN pluginObj as this
			$( document ).bind( 'click.jqcmbx' + this.options.__id , function ( event ) {
				if ( $( event.target ).closest( '.jqcmbx' )[0] !== pluginObj.jobjects.combobox[0] ) {
					pluginObj.comboboxBlur( event );
				}
			} );
		},
		comboboxBlur: function ( event ) {
			this._trigger( 'blur', event, this );
			this.comboboxClose();
			$( document ).unbind( 'click.jqcmbx' + this.options.__id );
		},
		_comboboxClick: function ( event ) {
			this.clearTimeout();

			this._trigger( 'click', event, this );
			if ( this.listVisible ) {
				this.comboboxBlur( event );
			} else {
				this.comboboxFocus( event );
			}
		},

		_hideInvisibleActiveElements: function () {
			var $activeItem = this.jobjects.itemList.children( '.active:visible' );
			if ( $activeItem.length === 0 ) {
				this.jobjects.itemList.children().removeClass( 'active' );
			}
		},
		filterList: function ( event ) {
			var arrKeyCodes = returnHookKeys();
			if ( inArray( arrKeyCodes, event.keyCode ) ) {
				return true;
			}

			var enteredValue = this.jobjects.enteredInput.val().toLowerCase();

			if ( this.options.autocomplete && this.options.source instanceof Function ) {
				this._autocomplete( enteredValue );
			} else if ( this.options.allowFilter ) {
				this._filterList( enteredValue );
			}
			this._hideInvisibleActiveElements();
			this._resizeItemsList( event );
		},
		_filterList: function ( enteredValue ) {
			for ( var i = 0; i < this.jobjects.items.length; i++ ) {
				var $item = this.jobjects.items.filter( function( index ) {
					return index === i;
				} );
				var filter = $item.attr( 'data-filter' ).toLowerCase();
				if ( ! ((filter !== undefined) && (filter.match( enteredValue ) !== null)) ) {
					$item.hide();
				} else {
					$item.show();
				}
			}
		},
		_autocomplete: function ( enteredValue ) {
			var pluginObj = this;
			this.options.source.call( pluginObj, enteredValue );
		},
		search: function ( source ) {
			this.deleteItems();
			this.addItems( source );
		},
		_itemListShow: function ( event ) {
			this.jobjects.itemList.show();

			this._resizeItemsList( event );

			var pluginObj = this;
			var tmpTimer = setTimeout( function() {
				pluginObj.listVisible = true;
			}, pluginObj.outStackTime );
			this.closeTimer.push( tmpTimer );

			$( document ).unbind( 'click.jqcmbx' + this.options.__id );

			this._trigger( 'listshow', event, this );
		},
		_itemListHide: function ( event ) {
			this.jobjects.itemList.hide();

			var pluginObj = this;
			var tmpTimer = setTimeout( function() {
				pluginObj.listVisible = false;
			}, pluginObj.outStackTime );
			this.closeTimer.push( tmpTimer );

			this._trigger( 'listhide', event, this );
		},
		_reposScrollArea: function ( event ) {
			if ( this.options.maxVisibleItems ) {
				var $activeItem = this.jobjects.itemList.children( '.active' );
				if ( $activeItem.length === 0 ) {
					$activeItem = this.jobjects.itemList.children( ':visible:first' );
					if ( $activeItem.length === 0 ) {
						return;
					}
				}

				var iItemHeight = $( this.jobjects.items[0] ).outerHeight();
				var iViewYRange = this.options.maxVisibleItems * iItemHeight;
				var iTopRange = this.jobjects.itemList.scrollTop();
				var iDownRange = iViewYRange;
				var iOffsetTop = $activeItem.position().top;
				if ( iOffsetTop < 0 ) {
					this.jobjects.itemList.scrollTop( iTopRange + iOffsetTop );
				} else if ( iOffsetTop >= iDownRange ) {
					this.jobjects.itemList.scrollTop( iTopRange + iOffsetTop - iDownRange + iItemHeight );
				}
			}
		},
		_resizeItemsList: function ( event ) {
			var iItemHeight = $( this.jobjects.items[0] ).outerHeight();
			var iCurrentMaxHeight = iItemHeight * this.jobjects.itemList.children( ':visible' ).length;
			if ( this.options.maxVisibleItems ) {
				var iItemListHeight = this.jobjects.items.length ? (this.options.maxVisibleItems * iItemHeight) : iCurrentMaxHeight;
				if ( iItemListHeight > iCurrentMaxHeight ) {
					iItemListHeight = iCurrentMaxHeight;
				}
			} else {
				iItemListHeight = iCurrentMaxHeight;
			}
			var sItemListHeight = iItemListHeight + 'px';
			this.jobjects.itemList.css( 'height', sItemListHeight );
		},

		_itemSelect: function ( event ) {
			this.itemSelect( event );
			this.comboboxClose();
		},
		itemSelect: function ( event ) {
			var $activeItem = (event.targetItem === undefined) ? $( event.currentTarget ) : $( event.targetItem );

			this.jobjects.itemList.children().removeClass( 'active' );
			if ( $activeItem.length === 0 ) {
				return;
			}

			this.jobjects.activeItem = $activeItem;

			var value = $activeItem.attr( 'data-value' );
			this.jobjects.visibleInput.html( $activeItem.html() );
			this.jobjects.titleInput.val( $activeItem.text() );
			this.jobjects.valueInput.val( value );

			$activeItem.addClass( 'active' );

			this._trigger( 'change', event, $activeItem );
		},
		itemNewSet: function ( event, newValue ) {
			if ( this.options.allowInput ) {
				this.jobjects.itemList.children().removeClass( 'active' );
				this.jobjects.activeItem = undefined;

				var $newItem = $.tmpl( 'optionTmpl', {name:newValue, value:newValue} ).addClass( 'jqcmbx-li' );
				this.jobjects.visibleInput.html( $newItem.html() );
				this.jobjects.titleInput.val( $newItem.text() );
				this.jobjects.valueInput.val( newValue );
				this.comboboxClose( event );

				this._trigger( 'change', event, $newItem );
			}
		},
		select: function ( dataValue ) {
			if ( dataValue === undefined ) {
				return this.jobjects.valueInput.val();
			}
			var $activeItem = this.jobjects.itemList.children( '[data-value='+dataValue+']:first' );
			if ( $activeItem.length > 0) {
				this.itemSelect( {targetItem: $activeItem[0]} );
			} else {
				this.itemNewSet( {}, dataValue ); //while hack
			}
			return this;
		},
		_itemClick: function ( event ) {
			this._itemSelect( event );

			this._trigger( 'listclick', event, this );
			this._trigger( 'click', event, this );

			event.stopPropagation();
		},
		_itemOver: function ( event ) {
			var $activeItem = (event.targetItem === undefined) ? $( event.currentTarget ) : $( event.targetItem );
			this.jobjects.items.removeClass( 'active' );
			$activeItem.addClass( 'active' );
		},

		addItems: function ( source ) {
			$items = $.tmpl( 'optionTmpl', source ).addClass( 'jqcmbx-li jqcmdx-item-class' );
			$items.appendTo( this.jobjects.itemList );
			this.jobjects.items = this.jobjects.itemList.find( '.jqcmbx-li' );
		},
		deleteItems: function () {
			this.jobjects.items.remove();
		},

		_create: function() {
			var pluginObj = this;
			this.listVisible = false;

			prepareOptions( this );

			var sStyle = ' .jqcmbx {display:inline-block;position:relative;font:.8em Arial, Verdana, serf;} ' +
			' .jqcmbx-input {position: relative;} .jqcmbx-input-border {position:relative;width:100%;border:1px solid #abadb3;cursor:default;z-index: 1;}' +
			' .jqcmbx-input .jqcmbx-input-visible {width:100%;height:100%;}' +
			' .jqcmbx-input .input-size {position:absolute;left:0px;right:21px;z-index:2;}' +
			' .jqcmbx-input .input-size input {width:100%;border:none;background:transparent;}' +
			' .jqcmbx-input span.jqcmbx-opener {position:absolute;right:-1px;top:1px;height:20px;width:14px;z-index:2;background:url("img/jqcmbx-arrow.png")}' +
			' .jqcmbx-input span.jqcmbx-opener:hover {background-position: 0 40px;}' +
			' .jqcmbx-input span.jqcmbx-opener:active {background-position: 0 20px;}' +
			' .jqcmbx-ul {list-style-position:inside;list-style-type:none;font-size:1em;background:white;border:1px solid black;display:none;position:absolute;margin:0px;padding:0px;width:100%;overflow-y:auto;z-index: 3;}' +
			' .jqcmbx-li {position:relative;margin:0px; padding:0px 0px 0px 5px;cursor:default;} ' +
			' .jqcmbx-li:hover, .jqcmbx-li.active {background:#3399ff;color:white;}';
			if ( $( '#stylesheet-jqcmbx' ).length === 0 ) {
				$( 'head' ).prepend( '<style id="stylesheet-jqcmbx">' + sStyle + '</style>' );
			}

			var $target = $( this.options.target );
			var oSource	= prepareSource( this.options.source );
			var arrItemsData = oSource['items'];
			delete oSource['items'];

			/*
			 * if wrong options - exit;
			 */
			if ( (oSource === null) || ($target.length === 0) ) {
				return;
			}
			var arrConfig = {};
			arrConfig.id		=	this.options.id;
			arrConfig.name		=	this.options.name;
			arrConfig.titlename	=	this.options.titlename;
			arrConfig.selected	=	this.options.selected;
			arrConfig.addclass	=	this.options['class'];
			arrConfig 			=	$.extend( oSource, arrConfig );

			var ti = oSource['tabindex'];
			arrConfig.tabindex	= 	(this.options.tabindex === true) ? (function() { return ti ? 'tabindex='+ti : '';})() : (function() {return (pluginObj.options.tabindex === false) ? '' : 'tabindex='+pluginObj.options.tabindex;})();

			$.template( 'comboBoxTmpl', '<div id="${id}" class="jqcmbx ${addclass}"><div class="jqcmbx-input">' +
					'<div class="input-size"><input name="jqcmbx_entered_value" type="text" ${tabindex} /></div>' +
					'<div class="jqcmbx-input-border"><div class="jqcmbx-input-visible"></div></div>' +
					'<input name="${titlename}" class="jqcmbx-input-title" type="hidden"/><input name="${name}" class="jqcmbx-input-name" type="hidden"/>' +
					'<span class="jqcmbx-opener" />' +
					'</div></div>' );
			$.template( 'optionTmpl', this.options.templateItem );
			$.template( 'optGroupTmpl', this.options.templateItem );

			var $itemList = $( this.options.templateList ).addClass( 'jqcmbx-ul' ); //FIXME: необходимо искать самый глубокий элемент и брать его как контейнер списка

			var $comboBox	= $.tmpl( 'comboBoxTmpl', arrConfig );
			var $enteredInput= $comboBox.find( 'input[name=jqcmbx_entered_value]' );
			if ( ! this.options.allowFilter ) {
				$enteredInput.attr( 'readonly', 'readonly' );
			}
			var $visibleInput= $comboBox.find( '.jqcmbx-input-visible' );
			$visibleInput.addClass( 'jqcmdx-item-class' );
			var $titleInput	= $comboBox.find( 'input.jqcmbx-input-title' );
			var $valueInput	= $comboBox.find( 'input.jqcmbx-input-name' );
			$comboBox.append( $itemList );

			$target.replaceWith( $comboBox );

			this.jobjects = {
					combobox	: $comboBox,
					input		: $comboBox.children( '.jqcmbx-input' ),
					enteredInput: $enteredInput,
					visibleInput: $visibleInput,
					titleInput	: $titleInput,
					valueInput	: $valueInput,
					itemList	: $itemList,
					items		: null,
					activeItem  : undefined
			};

			this.addItems( arrItemsData );

			this.jobjects.combobox.css( 'width', this.options.width );
			this.jobjects.combobox.find( '.jqcmbx-input-border' ).css( 'height', this.options.height );
			this.jobjects.items.css( 'height', this.options.height );

			this.select( arrConfig.selected );

			this.activateEventHandlers();
		},

		value: function () {
			return this.jobjects.valueInput.val();
		}
	};

	$.widget( "ui.comboboxtmpl", pluginObject );
} )( jQuery, document );
