(function ($) {
	function isNumber(n) {
		return !isNaN(parseFloat(n)) && isFinite(n);
	}
	function findTruncPoint(maxWidth, text, start, end, $workerEl, token, fromEnd) {
		var opt1,
			opt2,
			mid;

		if (fromEnd) {
			opt1 = start === 0 ? '' : text.slice(-start);
			opt2 = text.slice(-end);
		} else {
			opt1 = text.slice(0, start);
			opt2 = text.slice(0, end);
		}

		if ($workerEl.html(opt2 + token).width() < $workerEl.html(opt1 + token).width()) {
			return end;
		}

		mid = parseInt((start + end) / 2, 10);
		opt1 = fromEnd ? text.slice(-mid) : text.slice(0, mid);

		$workerEl.html(opt1 + token);
		if ($workerEl.width() === maxWidth) {
			return mid;
		}

		if ($workerEl.width() > maxWidth) {
			end = mid - 1;
		} else {
			start = mid + 1;
		}

		return findTruncPoint(maxWidth, text, start, end, $workerEl, token, fromEnd);
	}

	$.fn.truncate = function (options) {
		var defaults = {
			width: 'auto',
			token: '&hellip;',
			center: false,
			addclass: false,
			addtitle: false
		};
		options = $.extend(defaults, options);

		return this.each(function () {
			var $element = $(this),
				fontCSS = {
					'fontFamily': $element.css('fontFamily'),
					'fontSize': $element.css('fontSize'),
					'fontStyle': $element.css('fontStyle'),
					'fontWeight': $element.css('fontWeight'),
					'font-variant': $element.css('font-variant'),
					'text-indent': $element.css('text-indent'),
					'text-transform': $element.css('text-transform'),
					'letter-spacing': $element.css('letter-spacing'),
					'word-spacing': $element.css('word-spacing'),
					'display': 'none'
				},
				elementText = $element.text(),
				$truncateWorker = $('<span/>').css(fontCSS).html(elementText).appendTo('body'),
				originalWidth = $truncateWorker.width(),
				truncateWidth = isNumber(options.width) ? options.width : $element.width(),
				truncatedText;

			if (originalWidth > truncateWidth) {
				$truncateWorker.text('');
				if (options.center) {
					truncateWidth = parseInt(truncateWidth / 2, 10) + 1;
					truncatedText = elementText.slice(0, findTruncPoint(truncateWidth, elementText, 0, elementText.length, $truncateWorker, options.token, false))
									+ options.token
									+ elementText.slice(-1 * findTruncPoint(truncateWidth, elementText, 0, elementText.length, $truncateWorker, '', true));
				} else {
					truncatedText = elementText.slice(0, findTruncPoint(truncateWidth, elementText, 0, elementText.length, $truncateWorker, options.token, false)) + options.token;
				}

				if (options.addclass) {
					$element.addClass(options.addclass);
				}

				if (options.addtitle) {
					$element.attr('title', elementText);
				}

				$element.html(truncatedText);

			}

			$truncateWorker.remove();
		});
	};
})(jQuery);
