(function($){
	$.widget("ui.scroller",{
		// default config
		options: {
			'id': null,
			'items': [],
			'itemClass': 'ui-scroller-item',
			'html': false,
			'selectOnce': false,
			'orderItems': false,
			"showMaxCount": 9999999,
			'adjustPercents': 0,
			'growDirection':'down',

			'unique': false,
			'uniqueKey': null,

			'expanded': false,
			'openBy': 'hover', // hover | click
			'closeOnSelect': true,
			'itemDidSelect': null,
			'itemMouseOver': null,
			'itemMouseOut': null,
			'willOpen': null,
			'willClose': null
		},

		// constructor
		_create: function() {

			var self = this, $this = this.element, o = this.options;

			this._selectedIndex = -1;
			this._disabledIndex = -1;
			this._showing = false;
			this._scrollTop = 0;

			$this.addClass("ui-scroller closed");

			if(o.expanded)
				$this.show();
			this._height = $this.innerHeight();// || parseInt($this.css('height'));
			this._top = $this.offset().top;
			this._itemHeight = 0;
			this._showCount = 0;
			this._overflow = false;

			if(o.expanded)
				$this.hide();

			if(o.items.length>0)
				this._refreshItems();

			switch(o.openBy)
			{
				case 'click':
					break;
				case 'hover':
				default:
					$this
						.bind('mouseenter.scroller', function() { if(!self._showing) self._open(); })
						.bind('mouseleave.scroller', function() { if(!o.expanded && self._showing) self._close(); });
					break;
			}

			$this.bind('mousemove.scroller', function(e) { self._move(e); });
		},

		_refreshItems: function() {
			var self = this, $this = this.element, o = this.options;
			$this.empty();
			var uniq = [];
			var c = o.items.length;
			for(var i=0,index=0;i<c;i++)
			{
				var item = o.items[index];
				var text = item.text;
				if(o.unique)
				{
					if($.inArray(item[o.uniqueKey],uniq)!=-1)
					{
						o.items.splice(index,1);
						continue;
					}
					uniq.push(item[o.uniqueKey]);
				}

				if(o.orderItems)
					text = ++index+'. '+text;
				else
					++index;
				var $i = $('<div />').addClass(o.itemClass).data('__item',item)
					.hover(
						function(e) {
							var $_item = $(this);
							$_item.addClass('hover');
							if(o.itemMouseOver !== null)
								o.itemMouseOver.apply(this,[o.id, $_item.index(), $_item.data('__item')]);
							$(this).parent().hide().show(); /* parent hide/show for dumb ie8 */
						},
						function(e) {
							var $_item = $(this);
							$_item.removeClass('hover');
							if(o.itemMouseOut !== null)
								o.itemMouseOut.apply(this,[o.id, $_item.index(), $_item.data('__item')]);
						}
					)
					.click(function() {
						self._select(this);
					});
				if(o.html)
					$i.html(text);
				else
					$i.text(text);
				$this.append($i);
				if(!this._itemHeight)
					this._itemHeight = $i.innerHeight();
			}
		},

		_select: function(item,suppressCallback,callbackParam) {
			var o = this.options, $this = this.element;

			var $i,index;

			if(typeof(item)==='object')
			{
				$i = $(item);
				index = $i.index();
			}
			else
			{
				if(item == -1)
				{
					$this.children().removeClass('selected');
					return;
				}
				$i = $this.children().eq(parseInt(item));
				index = item;
			}

			if(index == this._disabledIndex || index == this._selectedIndex)
				return false;

			$this.children().removeClass('selected');
			$i.addClass('selected');
			this._selectedIndex = index;
			if(!this._showing)
			{
				$this.scrollTop(0);
				$this.scrollTop(this._itemHeight*this._selectedIndex);
			}
			
			if((suppressCallback !== true) && o.itemDidSelect !== null)
			{
				o.itemDidSelect.apply($i,[o.id, index, $i.data('__item'), callbackParam]);
			}
			if(this._showing && o.closeOnSelect)
				this._close();
		},

		_open: function() {
			var o = this.options, $this = this.element, self = this;
			if(o.items.length==0)
				return;

			if(!this._itemHeight && o.expanded)
			{
				$this.show();
				this._itemHeight = $this.children().eq(0).innerHeight();
				$this.hide();
			}

			if(o.willOpen !== null)
				o.willOpen.apply($this,[o.id]);
			$this.addClass('opened').removeClass('closed');
			self._showing = true;
			$this.scrollTop(0);
			var nhc = Math.min(o.showMaxCount,o.items.length);
			if(o.expanded && nhc<o.showMaxCount)
				nhc = o.showMaxCount;
			var wh = $(window).height()-4;
			var scrollY = window.scrollY !== undefined ? window.scrollY : document.body.scrollTop;
			var _t = this._top - scrollY;
			var hgt = nhc*this._itemHeight;
			var nt = _t;
			if(o.growDirection=='down')
			{
				if(!o.expanded && o.adjustPercents>0 && o.adjustPercents<=50)
					nt -= hgt/100*o.adjustPercents;
				var _b = hgt+nt;
				if(_b>wh)
				{
					if(!o.expanded)
						nt -= _b-wh;
					else
						hgt = wh;
				}
				if(nt<0)
				{
					hgt = wh;
					nt = 0;
				}
			}
			else
			{
				if(!o.expanded && o.adjustPercents>0 && o.adjustPercents<=50)
					nt += hgt/100*o.adjustPercents;
				nt -= (hgt-(o.expanded?0:this._itemHeight)+4);
				if(nt<0)
				{
					nt = 0;
				}
				if(hgt+nt>wh)
					hgt=wh-nt;
			}

			$this.height(hgt);
			$this.css({'top':(nt-_t)+'px'});
			if(o.expanded)
			{
				$this.show();
				if(o.growDirection=='up')
					$this.scrollTop(100000000);
			}
			this._showCount = hgt/this._itemHeight;
			this._overflow = this._showCount<o.items.length;
			return true;
		},

		_close: function() {
			var o = this.options, $this = this.element, self = this;
			if(o.items.length==0)
				return;
			if(o.willClose !== null)
				o.willClose.apply($this, [o.id]);
			self._showing = false;
			$this.addClass('closed').removeClass('opened');
			$this.children().removeClass('hover');
			$this.height(this._height);
			$this.css({top:0});
			if(!o.expanded && this._selectedIndex!=-1)
			{
				$this.scrollTop(0);
				$this.scrollTop(this._itemHeight*this._selectedIndex);
			}
			if(o.expanded)
				$this.hide();
			return true;
		},

		_move: function(e) {
			var trg = e.srcElement !== undefined ? e.srcElement : e.target;
			if($(trg).is('.ui-scroller'))
				return true;
			var o = this.options, $this = this.element, self = this;
			if(!this._showing || !this._overflow)
				return true;

			var offsetY = e.offsetY !== undefined ? e.offsetY : e.layerY;
			var vh = (this._showCount) * this._itemHeight;
			var th = (o.items.length) * this._itemHeight;
			var ih = this._itemHeight;
			var k = (o.items.length/this._showCount);
			var _top = $(trg).position().top;
			var top = (offsetY%this._itemHeight) + _top - ih;
			var scr =  (th-vh+ih*k*2)*(top/vh);
			$this.scrollTop(scr)
		},

//		option: function() {
//			console.log('options called: '+arguments[0]);
//			return '123';
//		},

		selected: function() {
			return this._selectedIndex != -1 ? this.options.items[this._selectedIndex] : null;
		},

		selectedIndex: function() {
			return this._selectedIndex;
		},

		random: function() {
			return Math.floor(Math.random()*this.options.items.length);
		},

		items: function(items) {
			if(!$.isArray(items))
				return;
			this._disabledIndex = -1;
			this._selectedIndex = -1;
			this.options.items = items;
			this._refreshItems();
		},

		empty: function() {
			this.items([]);
		},

		select: function(index, suppressCallback, callbackParam) {
			var o = this.options, self = this;
			if(index!==undefined)
			{
				var i;
				if(typeof(index)==='string')
				{
					switch(index)
					{
						case 'random':
							i = this.random();
							break;
						case 'last':
							i = o.items.length-1;
							break;
						case 'first':
							i = 0;
							break;
						case 'next':
							i = this.nextIndex();
							break;
						case 'prev':
							i = this.prevIndex();
							break;
						default:
							i = parseInt(index);
							break;
					}
				}
				else
					i = parseInt(index);
				
				if(!isNaN(i) && i>=0 && i<o.items.length)
				{
					self._select(i,suppressCallback===true, callbackParam);
				}
			}
		},

		disable: function() {
			var o = this.options, self = this;
			if(arguments[0]!==undefined)
			{
				var arg = arguments[0];
				var i = parseInt(arg);

				if(!isNaN(i) && i<o.items.length)
				{
					this.element.children().removeClass('disabled');
					if(i>=0)
						this.element.children().eq(i).addClass('disabled');
					this._disabledIndex = i;
				}
			}
			else
			{
				this.element.children().removeClass('disabled');
				this._disabledIndex = -1;
			}
		},

		count: function() {
			return this.options.items.length;
		},

		next: function() {
			var oi = this.options.items;
			var i = this._selectedIndex+1;
			if(i==oi.length)
				i = 0;
			this._select(i);
		},

		nextIndex: function() {
			var oi = this.options.items;
			var i = this._selectedIndex+1;
			if(i==oi.length)
				i = 0;
			return i;
		},

		prev: function() {
			var oi = this.options.items;
			var i = this._selectedIndex-1;
			if(i<0)
				i = oi.length-1;
			this._select(i);
		},

		prevIndex: function() {
			var oi = this.options.items;
			var i = this._selectedIndex-1;
			if(i<0)
				i = oi.length-1;
			return i;
		},

		add: function(item,position) {
			if(item!==undefined && position!==undefined && typeof(item)==='object' && (typeof(position)==='string' || typeof(position)==='number'))
			{
				var self = this, o = this.options, items = o.items;
				var pos = -1;
				if(items.length>0 && o.unique)
				{
					for(var i in items)
					{
						if(items[i][o.uniqueKey] === item[o.uniqueKey])
						{
							return i;
						}
					}
				}

				if(items.length == 0)
					pos = 0;
				else if(typeof(position)==='number')
				{
					if(position>=0)
						pos = position<=items.length ? position : items.length;
				}
				else
				{
					switch(position)
					{
						case 'last':
							pos = items.length;
							break;
						case 'first':
							pos = 0;
							break;
						case 'next':
							pos = this._selectedIndex + 1;
							break;
						case 'prev':
							pos = this._selectedIndex;
							break;
					}
				}

				if(pos == -1)
					return null;

				var newSelected=this._selectedIndex, newDisabled=this._disabledIndex;
				this._selectedIndex = this._disabledIndex = -1;
				if(newSelected>=pos)
					newSelected++; 
				if(newDisabled>=pos)
					newDisabled++; 

				if(pos<items.length)
					this.options.items.splice(pos,0,item);
				else
					this.options.items.push(item);

				this._refreshItems();

				if(newSelected!=-1)
					this._select(newSelected,true);
				if(newDisabled!=-1)
					this.disable(newDisabled);
				return pos;
			}
		},

		shuffle: function() {
			if(arguments[0]!==undefined)
			{
				var order = arguments[0];
				var oldSelected = this._selectedIndex, oldDisabled = this._disabledIndex;
				this._selectedIndex = this._disabledIndex = -1;
				var newSelected = -1, newDisabled = -1;
				if($.isArray(order))
				{
					var ni = [], c = this.options.items.length;
					for(var i=0;i<c;i++)
					{
						var no = order[i];
						if(no==oldSelected)
							newSelected = i;
						if(no==oldDisabled)
							newDisabled = i;
						ni.push(this.options.items[no]);
					}
					this.options.items = ni;
					this._refreshItems();
					this._select(newSelected, true);
					this.disable(newDisabled);
				}
			}
		},

		close: function() {
			this._close();
		},

		open: function() {
			this._open();
		},

		destroy: function() {
			this.options.items.clear();
			$.Widget.prototype.destroy.apply(this,arguments); // default destructor
		}
		
	});

	$.extend($.ui.scroller, {version: "@VERSION"});

})(jQuery);
