/*
 * jQuery UI Spinner @VERSION
 *
 * Copyright (c) 2008 jQuery
 * Dual licensed under the MIT (MIT-LICENSE.txt)
 * and GPL (GPL-LICENSE.txt) licenses.
 *
 * http://docs.jquery.com/UI/Spinner
 *
 * Depends:
 *  ui.core.js
 */
(function($) {

$.widget('ui.spinner', {
	_init: function() {
		this._trigger('init', null, this.ui(null));

		// perform data bind on generic objects
		if (this.options.items != null && typeof this.options.items[0] == 'object' && !this.element.is('input')) {
			var data = this.options.items;
			for (var i=0; i<data.length; i++) {
				this._addItem(data[i]);
			}
		}

		// check for decimals in steppinng and set _decimals as internal
		this._decimals = parseInt(this.options.decimals, 10);
		if (this.options.stepping.toString().indexOf('.') != -1 && this._decimals == 0) {
			var s = this.options.stepping.toString();
			this._decimals = s.slice(s.indexOf('.')+1, s.length).length;
		}

		//Initialize needed constants
		var self = this;
		this.element
			.addClass('ui-spinner-box')
			.attr('autocomplete', 'off') // switch off autocomplete in opera
			.width(this.options.width);

		this._setValue( isNaN(this._getValue()) ? this.options.start : this._getValue() );
		
		this.element
		.wrap('<div>')
		.parent()
			.addClass('ui-spinner ui-widget ui-widget-content ui-corner-all')
			// check for IE	
			.css({
				display: !$.support.opacity && $(this).css('display', 'inline-block') ? 'inline' : false
			})
			.append('<button class="ui-spinner-up ui-state-default ui-corner-tr" type="button"><span class="ui-icon ui-icon-triangle-1-n">&#9650;</span></button>')
			.find('.ui-spinner-up')
				.bind('mouseover', function(event) {
					$(this).addClass('ui-state-hover');
				})
				.bind('mousedown', function(event) {
					$(this).addClass('ui-state-active');
					if (!self.counter) {
						self.counter = 1;
					}
					self._mousedown(100, '_up', event);
				})
				.bind('mouseup', function(event) {
					$(this).removeClass('ui-state-active');
					if (self.counter == 1) {
						self._up(event);
					}
					self._mouseup(event);
				})
				.bind('mouseout', function(event) {
					$(this).removeClass('ui-state-active ui-state-hover');
					if (self.timer) {
						self._mouseup(event);
					}
				})
				// mousedown/mouseup capture first click, now handle second click
				.bind('dblclick', function(event) {
					$(this).removeClass('ui-state-active');
					self._up(event);
					self._mouseup(event);
				})
				.bind('keydown.spinner', function(event) {
					var KEYS = $.ui.keyCode;
					if (event.keyCode == KEYS.SPACE || event.keyCode == KEYS.ENTER) {
						$(this).addClass('ui-state-active');
						if (!self.counter) {
							self.counter = 1;
						}
						self._up.call(self, event);
					} else if (event.keyCode == KEYS.DOWN || event.keyCode == KEYS.RIGHT) {
						self.element.siblings('.ui-spinner-down').focus();
					} else if (event.keyCode == KEYS.LEFT) {
						self.element.focus();
					}
				})
				.bind('keyup.spinner', function(event) {
					$(this).removeClass('ui-state-active');
					self.counter = 0;
					self._trigger('change', event);
				})
			.end()
			.append('<button class="ui-spinner-down ui-state-default ui-corner-br" type="button"><span class="ui-icon ui-icon-triangle-1-s">&#9660;</span></button>')
			.find('.ui-spinner-down')
				.bind('mouseover', function(event) {
					$(this).addClass('ui-state-hover');
				})
				.bind('mousedown', function(event) {
					$(this).addClass('ui-state-active');
					if (!self.counter) {
						self.counter = 1;
					}
					self._mousedown(100, '_down', event);
				})				
				.bind('mouseup', function(event) {
					$(this).removeClass('ui-state-active');
					if (self.counter == 1) {
						self._down();
					}
					self._mouseup(event);
				})
				.bind('mouseout', function(event) {
					$(this).removeClass('ui-state-active ui-state-hover');
					if (self.timer) {
						self._mouseup(event);
					}
				})
				// mousedown/mouseup capture first click, now handle second click
				.bind('dblclick', function(event) {
					$(this).removeClass('ui-state-active');
					self._down(event);
					self._mouseup(event);
				})
				.bind('keydown.spinner', function(event) {
					var KEYS = $.ui.keyCode;
					if (event.keyCode == KEYS.SPACE || event.keyCode == KEYS.ENTER) {
						$(this).addClass('ui-state-active');
						if (!self.counter) {
							self.counter = 1;
						}
						self._down.call(self, event);
					} else if (event.keyCode == KEYS.UP || event.keyCode == KEYS.LEFT) {
						self.element.siblings('.ui-spinner-up').focus();
					}
				})
				.bind('keyup.spinner', function(event) {
					$(this).removeClass('ui-state-active');
					self.counter = 0;
					self._trigger('change', event);
				})
			.end();

		// Give the spinner casing a unique id only if one exists in original input 
		// - this should aid targetted customisations if a page contains multiple instances
		this.element.attr('id', function(){
			if (this.id) {
				$(this).parent().attr('id', this.id+'-ui-spinner');
			}
		});

		// DataList: Set contraints for object length and step size. 
		// Manipulate height of spinner.
		this._items = this.element.children().length;
		if (this._items > 1) {
			var margins = this.element.outerHeight(true) - this.element.outerHeight();
			var height = this.element.outerHeight()/this._items + margins*2;
			//var height = this.options.height;
			this.element
			.addClass('ui-spinner-list')
			.height(height)
			.children()
				.addClass('ui-spinner-listitem')
				.height(height)
				.css('overflow', 'hidden')
			.end()
			.parent()
				.height(height)
			.end();
			this.options.stepping = 1;
			this.options.min = 0;
			this.options.max = this._items-1;
		}

		this.element
		.bind('keydown.spinner', function(event) {
			if (!self.counter) {
				self.counter = 1;
			}
			return self._keydown.call(self, event);
		})
		.bind('keyup.spinner', function(event) {
			self.counter = 0;
			self._trigger('change', event);
		})
		.bind('blur.spinner', function(event) {
			self._cleanUp();
		});

		if ($.fn.mousewheel && this.options.mouseWheel) {
			this.element.mousewheel(self._mousewheel);
		}
	},

	_constrain: function() {
		if (this.options.min != null && this._getValue() < this.options.min) {
			this._setValue(this.options.min);
		}
		if (this.options.max != null && this._getValue() > this.options.max) {
			this._setValue(this.options.max);
		}
	},
	_cleanUp: function() {
		this._setValue(this._getValue());
		this._constrain();
	},
	_spin: function(d, event) {
		if (this.disabled) {
			return;
		}

		if (isNaN(this._getValue())) {
			this._setValue(this.options.start);
		}
		this._setValue(this._getValue() + (d == 'up' ? 1:-1) *(this.options.incremental && this.counter > 100 ? (this.counter > 200 ? 100 : 10) : 1) * this.options.stepping);
		this._animate(d);
		this._constrain();
		if (this.counter) {
			this.counter++;
		}
		this._trigger('spin', event);
	},
	_down: function(event) {
		this._spin('down', event);
		this._trigger('down', event);
	},
	_up: function(event) {
		this._spin('up', event);
		this._trigger('up', event);
	},
	_mousedown: function(i, d, event) {
		var self = this;
		i = i || 100;
		if (this.timer) {
			window.clearInterval(this.timer);
			this.timer = 0;
		}
		this.timer = window.setInterval(function() {
			self[d](event);
			if (self.options.incremental && self.counter > 20) {
				self._mousedown(20, d, event);
			}
		}, i);
	},
	_mouseup: function(event) {
		this.counter = 0;
		if (this.timer) {
			window.clearInterval(this.timer);
			this.timer = 0;
		}
		this.element[0].focus();
		this._trigger('change', event);
	},
	_keydown: function(event) {
		var KEYS = $.ui.keyCode;

		if (event.keyCode == KEYS.UP) {
			this._up(event);
		}
		if (event.keyCode == KEYS.DOWN) {
			this._down(event);
		}
		if (event.keyCode == KEYS.HOME) {
			//Home key goes to min, if defined, else to start
			this._setValue(this.options.min || this.options.start);
		}
		if (event.keyCode == KEYS.END && this.options.max != null) {
			//End key goes to maximum
			this._setValue(this.options.max);
		}
		return (event.keyCode == KEYS.TAB || event.keyCode == KEYS.BACKSPACE ||
			event.keyCode == KEYS.LEFT || event.keyCode == KEYS.RIGHT || event.keyCode == KEYS.PERIOD || 
			event.keyCode == KEYS.NUMPAD_DECIMAL || event.keyCode == KEYS.NUMPAD_SUBTRACT || 
			(event.keyCode >= 96 && event.keyCode <= 105) || // add support for numeric keypad 0-9
			(/[0-9\-\.]/).test(String.fromCharCode(event.keyCode))) ? true : false;
	},
	_mousewheel: function(event, delta) {
		// this = element, not widget, in event call
		// we must use a function that is a member of the widget for binding/unbinding the event on option changes
		var self = $.data(this, 'spinner');
		
		delta = ($.browser.opera ? -delta / Math.abs(delta) : delta);
		(delta > 0 ? self._up(event) : self._down(event));
		if (self.timeout) {
			window.clearTimeout(self.timeout);
			self.timeout = 0;
		}
		self.timeout = window.setTimeout(function(){self._trigger('change', event);}, 400);
		event.preventDefault();
	},
	_getValue: function() {
		var val = this.element.val().replace(this.options.point, '.');
		if (this.options.group === '.') {
			val = val.replace('.','');
		}
		return parseFloat(val.replace(/[^0-9\-\.]/g, ''));
	},
	_setValue: function(newVal) {
		if (isNaN(newVal)) {
			newVal = this.options.start;
		}
		this.element.val(
			this.options.currency ? 
				$.ui.spinner.format.currency(newVal, this.options.currency, this.options.group, this.options.point) : 
				$.ui.spinner.format.number(newVal, this._decimals, this.options.group, this.options.point)
		);
	},
	_animate: function(d) {
		if (this.element.hasClass('ui-spinner-list') && ((d == 'up' && this._getValue() <= this.options.max) || (d == 'down' && this._getValue() >= this.options.min)) ) {
			this.element.animate({marginTop: '-' + this._getValue() * this.element.parent().height() }, {
				duration: 'fast',
				queue: false
			});
		}
	},
	_addItem: function(obj, fmt) {
		if (!this.element.is('input')) {
			var wrapper = 'div';
			if (this.element.is('ol') || this.element.is('ul')) {
				wrapper = 'li';
			}
			var html = obj; // string or object set it to html first

			if (typeof obj == 'object') {
				var format = (fmt !== undefined ? fmt : this.options.format);

				html = format.replace(/%(\(([^)]+)\))?/g, 
					(function(data){
						return function(match, a, lbl) { 
							if (!lbl) {
								for (var itm in data) {
									return data[itm]; // return the first item only
								}
							} else {
								return data[lbl];
							}
						};
					})(obj)
				);
			}
			this.element.append('<'+ wrapper +' class="ui-spinner-dyn">'+ html + '</'+ wrapper +'>');
		}
	},
	_setData: function(key, value) {
		if ((key == 'mouseWheel') && (value != this.options.mouseWheel) && $.fn.mousewheel)
			this.element[value ? 'mousewheel' : 'unmousewheel'](this._mousewheel);
		
		$.widget.prototype._setData.call(this, key, value);
	},
	
	plugins: {},
	ui: function(event) {
		return {
			options: this.options,
			element: this.element,
			value: this._getValue(),
			add: this._addItem
		};
	},
	_propagate: function(n,event) {
		$.ui.plugin.call(this, n, [event, this.ui()]);
		return this.element.triggerHandler(n == 'spin' ? n : 'spin'+n, [event, this.ui()], this.options[n]);
	},
	destroy: function() {
		if (!$.data(this.element[0], 'spinner')) {
			return;
		}
		if ($.fn.mousewheel) {
			this.element.unmousewheel();
		}
		this.element
			.removeClass('ui-spinner-box ui-spinner-list')
			.removeAttr('disabled')
			.removeAttr('autocomplete')
			.removeData('spinner')
			.unbind('.spinner')
			.siblings()
				.remove()
			.end()
			.children()
				.removeClass('ui-spinner-listitem')
				.remove('.ui-spinner-dyn')
			.end()
			.parent()
				.removeClass('ui-spinner ui-state-disabled')
				.before(this.element.clone())
				.remove()
			.end();
	},
	enable: function() {
		this.element
			.removeAttr('disabled')
			.siblings()
				.removeAttr('disabled')
			.parent()
				.removeClass('ui-state-disabled');
		this.disabled = false;
	},
	disable: function() {
		this.element
			.attr('disabled', true)
			.siblings()
				.attr('disabled', true)
			.parent()
				.addClass('ui-state-disabled');
		this.disabled = true;
	},
	value: function(newVal) {
		if (!arguments.length)
			return this._getValue();
			
		this._setValue(newVal);
		return this;
	}
});

$.extend($.ui.spinner, {
	version: "@VERSION",
	defaults: {
		decimals: 0,
		stepping: 1,
		start: 0,
		incremental: true,
		mouseWheel: true,
		currency: false,
		format: '%',
		group: '',
		point: '.',
		items: null,
		max: null,
		min: null,
		width: 'auto'
	},
	format: {
		currency: function(num, sym, group, pt) {
			num = isNaN(num) ? 0 : num;
			return (num !== Math.abs(num) ? '-' : '') + sym + this.number(Math.abs(num), 2, group || ',', pt);
		},
		number: function(num, dec, group, pt) {
			var regex = /(\d+)(\d{3})/;
			for (num = isNaN(num) ? 0 : parseFloat(num,10).toFixed(dec), num = num.replace('.', pt); regex.test(num) && group; num=num.replace(regex, '$1'+group+'$2'));
			return num;
		}
	}
});

})(jQuery);
