(function ($) {
	var CountTo = function (element, options) {
		this.$element = $(element);
		this.options  = $.extend({}, CountTo.DEFAULTS, this.dataOptions(), options);
		this.init();
	};

	CountTo.DEFAULTS = {
		from: 0,               // the number the element should start at
		to: 0,                 // the number the element should end at
		speed: 1000,           // how long it should take to count between the target numbers
		refreshInterval: 100,  // how often the element should be updated
		decimals: 0,           // the number of decimal places to show
		formatter: formatter,  // handler for formatting the value before rendering
		onUpdate: null,        // callback method for every time the element is updated
		onComplete: null       // callback method for when the element finishes updating
	};

	CountTo.prototype.init = function () {
		this.value     = this.options.from;
		this.loops     = Math.ceil(this.options.speed / this.options.refreshInterval);
		this.loopCount = 0;
		this.increment = (this.options.to - this.options.from) / this.loops;
	};

	CountTo.prototype.dataOptions = function () {
		var options = {
			from:            this.$element.data('from'),
			to:              this.$element.data('to'),
			speed:           this.$element.data('speed'),
			refreshInterval: this.$element.data('refresh-interval'),
			decimals:        this.$element.data('decimals')
		};

		var keys = Object.keys(options);

		for (var i in keys) {
			var key = keys[i];

			if (typeof(options[key]) === 'undefined') {
				delete options[key];
			}
		}

		return options;
	};

	CountTo.prototype.update = function () {
		this.value += this.increment;
		this.loopCount++;

		this.render();

		if (typeof(this.options.onUpdate) == 'function') {
			this.options.onUpdate.call(this.$element, this.value);
		}

		if (this.loopCount >= this.loops) {
			clearInterval(this.interval);
			this.value = this.options.to;

			if (typeof(this.options.onComplete) == 'function') {
				this.options.onComplete.call(this.$element, this.value);
			}
		}
	};

	CountTo.prototype.render = function () {
		var formattedValue = this.options.formatter.call(this.$element, this.value, this.options);
		this.$element.text(formattedValue);
	};

	CountTo.prototype.restart = function () {
		this.stop();
		this.init();
		this.start();
	};

	CountTo.prototype.start = function () {
		this.stop();
		this.render();
		this.interval = setInterval(this.update.bind(this), this.options.refreshInterval);
	};

	CountTo.prototype.stop = function () {
		if (this.interval) {
			clearInterval(this.interval);
		}
	};

	CountTo.prototype.toggle = function () {
		if (this.interval) {
			this.stop();
		} else {
			this.start();
		}
	};

	function formatter(value, options) {
		return value.toFixed(options.decimals);
	}

	$.fn.countTo = function (option) {
		return this.each(function () {
			var $this   = $(this);
			var data    = $this.data('countTo');
			var init    = !data || typeof(option) === 'object';
			var options = typeof(option) === 'object' ? option : {};
			var method  = typeof(option) === 'string' ? option : 'start';

			if (init) {
				if (data) data.stop();
				$this.data('countTo', data = new CountTo(this, options));
			}

			data[method].call(data);
		});
	};
}(jQuery));