/*
VEVO Custom Ellipsis Implementation
AKA "Better Ellipsis"
*/
(function($){
	$.fn.vevoEllipsis = function (param) {
		var config = {'type': 'domScroll', 'max' : 0, 'context' : null};
 
		if (param) { $.extend(config, param); }

		switch(config.type)
		{
			case 'domScoll':
				ellipsisScrollFit(this, config.context, DOMFitter);
				break;
			case 'domHeight':
				ellipsisHeightFit(this, config.context, DOMFitter, config.max);
				break;
			case 'domWidth':
				ellipsisWidthFit(this, config.context, DOMFitter, config.max);
				break;
			case 'domLength':
				ellipsisTextFit(this, config.context, DOMFitter, config.max);
				break;

			case 'txtScoll':
				ellipsisScrollFit(this, config.context, TextFitter);
				break;
			case 'txtHeight':
				ellipsisHeightFit(this, config.context, TextFitter, config.max);
				break;
			case 'txtWidth':
				ellipsisWidthFit(this, config.context, TextFitter, config.max);
				break;
			case 'txtLength':
				ellipsisTextFit(this, config.context, TextFitter, config.max);
				break;

			case 'reset':
			case 'restore':
				retoreFromData(this);
				break;
			default: 
				ellipsisScrollFit(this, config.context, DOMFitter);
		}
		return this;
	}

	function log()
	{
		window.console && console.log && console.log.apply(this, arguments)
	}

	function TextFitter(/*jQ*/selector, /*jQ*/context, /*function*/constrainTester)
	{
		$(selector).each(function(index){
			var localContext = this;
			if (!!context)
			{
				localContext = $(this).parents(context)[0];
			}

			if (!constrainTester(this, localContext)) return;
			
			if ($(this).text().length <= 1) return;

			var backupHTML = $(this).html(), backupText = $(this).text();
			var appendedEllipsis = false;
			var currentText = backupText;
			
			var regex;
			// if there are space characters we would use those, otherwise, we chop off 5 characters at a time. 
			if (/\s/.test(currentText))
			{
				regex = /\s+\S+\u2026?\s*$/;
			}
			else
			{
				regex = /.{5}\u2026?$/;
			}

			while(constrainTester(this, localContext) && currentText.length > 1)
			{
				currentText = currentText.replace(regex,'\u2026');
				$(this).text(currentText);
			}

			if (appendedEllipsis && constrainTester(this, localContext))
			{
				// if we are unable to meet the constraint, revert content
				log('Unable apply ellipsis, please check parameters.');
				//$(this).html(backupHTML);
			}
			else
			{
				if (!$(this).attr('title'))
				{
					$(this).attr('title', $.trim(backupText).replace(/\s+/g,' '));
				}
				$(this).addClass('ellipsed')
				$.data(this, 'vevoEllipsisBackup', backupText);
			}
		});
	}

	/* 
		Generic linear goal seek function that takes a target element, a context element, and a function.
		If context is not supplied, 
	*/
	function DOMFitter(/*jQ*/selector, /*jQ*/context, /*function*/constrainTester)
	{
		$(selector).each(function(index){
			var localContext = this;
			if (!!context)
			{
				localContext = $(this).parents(context)[0];
			}

			if (!constrainTester(this, localContext)) return;

			var kids = this.childNodes;
			if (kids.length <= 1) return;

			//log('a', this, localContext, constrainTester(this, localContext));

			var backupHTML = $(this).html(), backupText = $(this).text();
			var appendedEllipsis = false;

			while(constrainTester(this, localContext) && kids.length > 1)
			{
				if (!appendedEllipsis)
				{
					var ellipsis = document.createElement('span');
					ellipsis.appendChild(document.createTextNode('\u2026'));
					this.appendChild(ellipsis);
					appendedEllipsis = true;
				}
				this.removeChild(kids[kids.length - 2]);
			}

			if (appendedEllipsis && constrainTester(this, localContext))
			{
				// if we are unable to meet the constraint, revert content
				log('Unable apply ellipsis, please check parameters.');
				//$(this).html(backupHTML);
			}
			else
			{
				if (!$(this).attr('title'))
				{
					$(this).attr('title', $.trim(backupText).replace(/\s+/g,' '));
				}
				$(this).addClass('ellipsed');
				$.data(this, 'vevoEllipsisBackup', backupHTML);
			}
		});
	}
	
	function retoreFromData(/*jQ*/target)
	{
		$(target).each(function(index){
			var val = $.data(this, 'vevoEllipsisBackup');
			if (!!val)
			{
				$(this).html(val);
			}
			$(this).removeClass('ellipsed');
		});
	}

	function ellipsisScrollFit(/*jQ*/target, /*jQ*/context, /*function*/fitter)
	{
		var func = function (t, c) { 
			try { return hasScroll(c || t); }
			catch(e) { log(e); return false; }
		}
		fitter(target, context, func);
	}

	function ellipsisHeightFit(/*jQ*/target, /*jQ*/context, /*function*/fitter, /*number*/maxHeight)
	{
		var mHeight = parseInt(maxHeight);
		if (isNaN(mHeight)) throw new Error('maxHeight must be a number.');

		var func = function (t, c) { return getHeight(c || t) > mHeight; }
		fitter(target, context, func);
	}

	function ellipsisWidthFit(/*jQ*/target, /*jQ*/context, /*function*/fitter, /*number*/maxWidth)
	{
		var mWidth = parseInt(maxWidth);
		if (isNaN(mWidth)) throw new Error('maxWidth must be a number.');

		var func = function (t, c) { return getWidth(c || t) > mWidth; }
		fitter(target, context, func);
	}

	function ellipsisTextFit(/*jQ*/target, /*jQ*/context, /*function*/fitter, /*number*/maxCount)
	{
		var mCount = parseInt(maxCount);
		if (isNaN(mCount)) throw new Error('maxCount must be a number.');

		var func = function (t, c) { return $.trim($(c || t).text()).replace(/\s+/g, ' ').length > mCount; }
		fitter(target, context, func);
	}
	
	function getHeight(obj)
	{
		return obj.clientHeight || obj.offsetHeight;
	}

	function getWidth(obj)
	{
		return obj.clientWidth || obj.offsetWidth;
	}

	// Determine if the element has scroll area.
	function hasScroll(/*jQ*/target)
	{
		var v = hasVScroll(target), h = hasHScroll(target);
		if (v && h) throw new Error("Elements must wrap vertically or horizontally, not unconstrained in both.")
		return v || h;
	}

	// Determine if the element has a vertical scroll.
	function hasVScroll(/*jQ*/target)
	{
		if (target == null) { throw new Error('Target invalid.') }
		//log('h', obj.scrollHeight, obj.clientHeight);
		return target.scrollHeight > getHeight(target) + 1;
	}

	// Determine if the element has a horizontal scroll.
	function hasHScroll(/*jQ*/target)
	{
		if (target == null) { throw new Error('Target invalid.') }
		//log('w', obj.scrollWidth, obj.clientWidth);
		return target.scrollWidth > getWidth(target) + 1;
	}

	function applyShortName(/*jQ*/target)
	{
		var abbr = $('abbr', target);
		
		$.each(abbr, function(index){
			var orginalText = $(this).text();

			// if the inner content is not text, do not do anything.
			if (orginalText != $(this).html()) { return; }

			var titleText = $.trim($(this).attr('title'));

			// if the suggested abbreviation is empty, we would truncate the letter to 15 characters. 
			if ((typeof titleText) !== 'string' || titleText.length === 0) 
			{
				$(this).attr('title', orginalText);
				$(this).text(ellipsisClause(orginalText, 10));
				return;
			}

			// if the suggested abbreviation is longer than current text, do not do anything
			if (titleText.length >= orginalText.length) { return; }

			$(this).attr('title', orginalText);
			$(this).text(titleText);
		});
	}

	/* 
	Search for a word border (\b in regex) between truncate point and truncate point minus word length tolerance. 
	* If the word border was found, it will attempt to truncate at that word border. 
	* If no word border was found, it will simply return with the expect number characters. 
	*/
	function ellipsisClause(/*string*/targetClause, /*number*/truncateLength)
	{
		if (typeof truncateLength != 'number') { throw new Error('Argument "truncateLength" is not a number.'); } 
		targetClause = $.trim('' + targetClause); // force input to a string if it is not
		
		if (targetClause.length <= truncateLength) { return targetClause; }

		var WordLengthTolerance = 5;
		var truncated = targetClause.substr(0, truncateLength);

		// after 1st truncation, we will attempt to find the last occuring word border in the string, and return everything before it.
		var match = /^(.+)\s.+$/.exec(truncated);

		// if we cannot match the above string simply return the initial truncation with elipsis. 
		if (match == null) { return truncated + '\u2026'; }

		// if we cannot match the word boundry with in tolerance, simply return the initial truncation with elipsis. 
		if (match[1].length < truncateLength - WordLengthTolerance) { return truncated + '\u2026'; }

		return match[1] + '\u2026';
	}

	function replaceSpaceWithThinSpace(/*string*/text)
	{
		return text.replace(/ +/g, '\u2009');
	}

})(jQuery);