/**
 * KUTILS = K Utilities.
 *
 * It contains utility functions (in the global namespace of jquery or 
 * inside the jquery).
 *
 * @author Md. Afsar Uddin
 */
(function($){
	///////////////////////////////////////////////////////////////////////////
	// Global methods inside the namespace of $
	
	/**
	 * Get the dimension (width, height) of the view port represented by of the document
	 * container "docCon".
	 *
	 * Logic from: http://www.howtocreate.co.uk/tutorials/javascript/browserwindow
	 *
	 * @param docCon - Object. The document container like window, top.framename etc.
	 *                 Mandatory.
	 *
	 * @return Array. Array of 2 integers. scroll-x and scroll-y respectively.
	 *                On error, return [ 0, 0].
	 */
	$.getViewPortDimension = function(docCon) {
		if (typeof docCon.innerWidth == "number") {
			// Non-IE.
			return([ docCon.innerWidth, docCon.innerHeight ]);
		}
		var doc = docCon.document;
		
		if ( doc.documentElement && ( doc.documentElement.clientWidth || doc.documentElement.clientHeight ) ) {
			// IE 6+ in standards compliant mode.
			return([ doc.documentElement.clientWidth, doc.documentElement.clientHeight ]);
		}
		
		if ( doc.body && ( doc.body.clientWidth || doc.body.clientHeight ) ) {
			// IE 4 compatible.
			return([ doc.body.clientWidth, doc.body.clientHeight ]);
		}
		return([ 0, 0 ]);
	};
	
	/**
	 * Get the scrolling positions of the document container "docCon".
	 *
	 * Derived from: http://www.howtocreate.co.uk/tutorials/javascript/browserwindow
	 *
	 * @param docCon - Object. The document container like window, top.framename etc.
	 *                 May be undefined or null.
	 *
	 * @return Array. Array of 2 integers. scroll-x and scroll-y respectively. On error, return [ 0, 0 ].
	 */
	$.getScrollXY = function(docCon) {
		if (docCon && (typeof docCon.pageXOffset == "number" || typeof docCon.pageYOffset == "number")) {
			// Netscape compliant.
			return([ docCon.pageXOffset, docCon.pageYOffset ]);
		}
		var doc = docCon.document;
		if (!doc) {
			return([ 0, 0 ]);
		}
		if ( doc.body && ( doc.body.scrollLeft || doc.body.scrollTop ) ) {
			// DOM compliant.
			return([ doc.body.scrollLeft, doc.body.scrollTop ]);
		}
		if ( doc.documentElement && ( doc.documentElement.scrollLeft || doc.documentElement.scrollTop ) ) {
			// IE6 standards compliant mode.
			return([ doc.documentElement.scrollLeft, doc.documentElement.scrollTop ]);
		}
		return([ 0, 0 ]);
	};
	
	/**
	 * Gets the width of the OS scrollbar. Code from: http://brandonaaron.net/code
	 *
	 * Copyright (c) 2008 Brandon Aaron (brandon.aaron@gmail.com || http://brandonaaron.net)
	 * Dual licensed under the MIT (http://www.opensource.org/licenses/mit-license.php) 
	 * and GPL (http://www.opensource.org/licenses/gpl-license.php) licenses.
	 */
	var scrollbarWidth = 0;
	$.getScrollbarWidth = function() {
		if ( !scrollbarWidth ) {
			if ( $.browser.msie ) {
				var $textarea1 = $('<textarea cols="10" rows="2"></textarea>')
						.css({ position: 'absolute', top: -1000, left: -1000 }).appendTo('body'),
					$textarea2 = $('<textarea cols="10" rows="2" style="overflow: hidden;"></textarea>')
						.css({ position: 'absolute', top: -1000, left: -1000 }).appendTo('body');
				scrollbarWidth = $textarea1.width() - $textarea2.width();
				$textarea1.add($textarea2).remove();
			} else {
				var $div = $('<div />')
					.css({ width: 100, height: 100, overflow: 'auto', position: 'absolute', top: -1000, left: -1000 })
					.prependTo('body').append('<div />').find('div')
						.css({ width: '100%', height: 200 });
				scrollbarWidth = 100 - $div.width();
				$div.parent().remove();
			}
		}
		return scrollbarWidth;
	};
	
	/**
	 * Determine the box model type (W3C or Traditional).
	 */
	var boxModelType = null;
	$.isTraditionalBoxModel = function() {
		if (!boxModelType) {
			var $div = $('<div style="border: 1px solid white; margin: 0px; padding: 0px;" />')
						.css({ width: 100, height: 100, position: 'absolute', top: -1000, left: -1000 })
						.prependTo('body');
			boxModelType = ($div.width() == 100 ? 'W3C' : 'Traditional');
			$div.remove();
		}
		return(boxModelType == 'Traditional');
	};
	
	/**
	 * Get total height of all the elements represented by the list of
	 * JQuery "selectors". Calculation includes padding, border and
	 * margin (only if "includeMargin" is true).
	 *
	 * @param selectors - ANY[]. Array of selectors. Mandatory.
	 * @param includeMargin - Boolean. true to include margin in consideration. Optional.
	 *
	 * @return int. Total height.
	 */
	$.getTotalHeight = function(selectors, includeMargin) {
		return($.getTotalDimUnit(selectors, "outerHeight", includeMargin));
	};
	
	/**
	 * Get total width of all the elements represented by the list of
	 * JQuery "selectors". Calculation includes padding, border and
	 * margin (only if "includeMargin" is true).
	 *
	 * @param selectors - ANY[]. Array of selectors. Mandatory.
	 * @param includeMargin - Boolean. true to include margin in consideration. Optional.
	 *
	 * @return int. Total width.
	 */
	$.getTotalWidth = function(selectors, includeMargin) {
		return($.getTotalDimUnit(selectors, "outerWidth", includeMargin));
	};
	
	/**
	 * Copy the outer height from $src into $dst. If "selector" is provided,
	 * find (using it) the descendant of the $src and $dst and use them as
	 * new source and destination.
	 *
	 * @param $src - JQuery. Source elements. Mandatory.
	 * @param $dst - JQuery. Destination elements. Mandatory.
	 * @param selectors - ANY[]. Array of selectors. Optional.
	 */
	$.copyOuterHeight = function($src, $dst, selector) {
		$.copyDimUnit($src, $dst, "outerHeight", "height", selector);
	};
	
	/**
	 * Copy the outer width from $src into $dst. If "selector" is provided,
	 * find (using it) the descendant of the $src and $dst and use them as
	 * new source and destination.
	 *
	 * @param $src - JQuery. Source elements. Mandatory.
	 * @param $dst - JQuery. Destination elements. Mandatory.
	 * @param selectors - ANY[]. Array of selectors. Optional.
	 */
	$.copyOuterWidth = function($src, $dst, selector) {
		$.copyDimUnit($src, $dst, "outerWidth", "width", selector);
	};
	
	/**
	 * Copy the width from $src into $dst. If "selector" is provided, find
	 * (using it) the descendant of the $src and $dst and use them as new
	 * source and destination.
	 *
	 * @param $src - JQuery. Source elements. Mandatory.
	 * @param $dst - JQuery. Destination elements. Mandatory.
	 * @param selectors - ANY[]. Array of selectors. Optional.
	 */
	$.copyWidth = function($src, $dst, selector) {
		$.copyDimUnit($src, $dst, "width", "width", selector);
	};
	
	/**
	 * Get total dimension unit (width or height) of all the elements
	 * represented by the list of JQuery "selectors". Calculation includes
	 * padding, border and margin (only if "includeMargin" is true).
	 *
	 * Note that "includeMargin" will be effective ony for outer dimension.
	 *
	 * @param selectors - ANY[]. Array of selectors. Mandatory.
	 * @param methodName - String. JQuery method to call to calculate the width or height. Mandatory.
	 * @param includeMargin - Boolean. true to include margin in consideration. Optional.
	 *
	 * @return int. Total value.
	 */
	$.getTotalDimUnit = function(selectors, methodName, includeMargin) {
		if (typeof selectors == "undefined" || selectors == null || selectors.length <= 0) {
			return(0);
		}
		var total = 0;
		for(var i in selectors) {
			total += $(selectors[i])[methodName](includeMargin);
		}
		return(total);
	};
	
	/**
	 * Copy the dimension unit (width or height) from $src into $dst. If "selector"
	 * is provided, find (using it) the descendant of the $src and $dst and use
	 * them as new source and destination.
	 *
	 * @param $src - JQuery. Source elements. Mandatory.
	 * @param $dst - JQuery. Destination elements. Mandatory.
	 * @param getMethodName - String. JQuery method to call to calculate the width or height. Mandatory.
	 * @param setMethodName - String. JQuery method to call to set the width or height. Mandatory.
	 * @param selectors - ANY[]. Array of selectors. Optional.
	 */
	$.copyDimUnit = function($src, $dst, getMethodName, setMethodName, selector) {
		// If "selector" is provided, use it as the descendant of the source ($src).
		if (typeof selector != "undefined" && selector != null) {
			$src = $src.find(selector);
			$dst = $dst.find(selector);
		}
		
		// Save the value in a temporary array and then set in the destination. 
		var temp = new Array($src.length);
		$src.each(function(pos) {
			temp[pos] = $(this)[getMethodName]();
		});
		$dst.each(function(pos) {
			$(this)[setMethodName](temp[pos]);
		});
		
		temp = null; // Releasing memory.
	};
	
	/**
	 * Check whether "val" is string and having the value "auto" (case
	 * insensitively) or not.
	 *
	 * @param val - String. Value to check. Mandatory.
	 *
	 * @return Boolean. "true" to answer yes.
	 */
	$.isAuto = function(val) {
		return(typeof val == "string" && /^auto$/i.test(val));
	};
	
	/**
	 * Get the visual dimension of "$elem" (wrapped within "$wrapper")
	 * which are in front of our eyes (considering the scroll bars).
	 *
	 * @param $elem - JQuery. The guineapig. Mandatory.
	 * @param $wrapper - JQuery. Wrapper of the guineapig. Mandatory.
	 *
	 * @return [ int, int ]. Applied width x height. 
	 */
	$.getVisualDimension = function($elem, $wrapper) {
		var elemW = $elem.outerWidth();
		var elemH = $elem.outerHeight();
		var wrapperW = $wrapper.outerWidth();
		var wrapperH = $wrapper.outerHeight();
		var visualDim = [ 0, 0 ];
		
		// Calculate width as the min of element and its container (handling
		// scroll bar's width when a vertical scroll bar is introduced).
		visualDim[0] = Math.min(elemW, wrapperW);
		if (elemH > wrapperH) {
			visualDim[0] = Math.min(visualDim[0], wrapperW - $.getScrollbarWidth());
		}
		
		// Calculate height as the min of element and its container (handling
		// scroll bar's width when a horizontal scroll bar is introduced).
		visualDim[1] = Math.min(elemH, wrapperH);
		if (elemW > wrapperW) {
			visualDim[1] = Math.min(visualDim[1], wrapperH - $.getScrollbarWidth());
		}
		
		return(visualDim);
	};
	
	/**
	 * Calculate the dimension of the wrapper of the element (having dimension
	 * "elemWidth" x "elemHeight"). This dimension can be applied to another
	 * element to wrap the original element in an "air-tight" way.
	 *
	 * @param elemWidth - int. Width of the element. Mandatory.
	 * @param elemHeight - int. Height of the element. Mandatory.
	 * @param maxWidth - int. Maximum width of the wrapper. Mandatory.
	 * @param maxHeight - int. Maximum height of the wrapper. Mandatory.
	 *
	 * @return [ int, int ]. Applied width x height. 
	 */
	$.getWrapperDim = function(elemWidth, elemHeight, maxWidth, maxHeight) {
		var wrapperDim = [ 0, 0 ];
		
		// Calculate width as the min of element and its container (handling
		// scroll bar's width when a vertical scroll bar is introduced).
		wrapperDim[0] = Math.min(elemWidth, maxWidth);
		if (elemHeight > maxHeight) {
			wrapperDim[0] += $.getScrollbarWidth();
		}
		
		// Calculate height as the min of element and its container (handling
		// scroll bar's width when a horizontal scroll bar is introduced).
		wrapperDim[1] = Math.min(elemHeight, maxHeight);
		if (elemWidth > maxWidth) {
			wrapperDim[1] += $.getScrollbarWidth();
		}
		
		return(wrapperDim);
	};
	
	/**
	 * Escape all types of characters (which have special meaning to
	 * regular expression) from "regex".
	 *
	 * Logic from: http://simonwillison.net/2006/Jan/20/escape/
	 *
	 * @param regex - String. A string which is intended to be used as regular
	 *               expression. Must be valid.
	 */
	$.escapeRegexChars = function(regex) {
		var __specials__ = "/.*+?|()[]{}\\".split("");
		var __sre__ = new RegExp("(\\" + __specials__.join("|\\") + ")", "g");
		return(regex.replace(__sre__, "\\$1"));
	};
	
	/**
	 * Transform the "obj" into a string representation as guided by "format" and "itemSeparator".
	 * For example:
	 *    itemSeparator: " "
	 *    map:           key1 => value1, key2 => value2, ...
	 *
	 * Output:
	 *    kay1="value1" kay2="value2" ....  [ format = "%k=\"%v\"" and itemSeparator = " "  ]
	 *    kay1=>value1, kay2=>value2 ....   [ format = "%k=>%v"    and itemSeparator = ", " ]
	 *
	 * @param obj - Object. The object whose attributes are to be transformed. Must be valid.
	 * @param format - String. A string containing %k and %v. Must be valid.
	 * @param itemSeparator - String. Separator of consecutive to items (1 key/value pair).
	 *                        Optional. Default " ".
	 *
	 * @return String. Above chain of string.
	 */
	$.objectToString = function(obj, format, itemSeparator) {
		// Handle unavailable parameters.
		if (!format) {
			return("");
		}
		if (!itemSeparator) {
			itemSeparator = " ";
		}
		var output = "";
		var isFirst = true;
		for (var i in obj) {
			if (isFirst) {
				isFirst = false;
			} else {
				output += itemSeparator;
			}
			output += format.replace(/%k/g, i).replace(/%v/g, obj[i]);
		}
		return(output);
	};
	
	/**
	 * Get list of block level element names.
	 * 
	 * @return String[]. List of element names.
	 */
	$.getBlockLevelElements = function() {
		return [
			"ADDRESS", "BLOCKQUOTE", "CENTER", "DIR", "DIV", "DL", "FIELDSET", "FORM",
			"H1", "H2", "H3", "H4", "H5", "H6", "HR", "ISINDEX", "MENU", "NOFRAMES",
			"NOSCRIPT", "OL", "P", "PRE", "TABLE", "UL",
			"DD", "DT", "FRAMESET", "LI", "TBODY", "TD", "TFOOT", "TH", "THEAD", "TR"
		];
	};
	
	$.getBlockLevelElementsSelector = function() {
		return $.getBlockLevelElements().join(",");
	};
	
	///////////////////////////////////////////////////////////////////////////
	// Extended methods inside the $
	
	/**
	 * Get the value of the attribute "name". If it is not found,
	 * return "defVal".
	 * 
	 * @param name - String. Name of the attribute. Mandatory.
	 * @param defVal - ANY. Value to be returned on absence of the attribute.
	 */
	$.fn.getAttr = function(name, defVal) {
		var val = this.attr(name);
		return(val ? val : defVal);
	};
	
	/**
	 * Get the html of the first matched element as:
	 *    <tag-name class='class [ cssClass ]'>html</tag-name>
	 *
	 * @param cssClass - String. Extra CSS class to add. Optional.
	 *
	 * @return String. Constructed html.
	 */
	$.fn.asHtml = function(cssClass) {
		if (this.length <= 0) {
			return("");
		}
		if (typeof cssClass == "undefined" || cssClass == null) {
			cssClass = "";
		}
		var tagName = this.get(0).nodeName;
		return(
			"<" + tagName + " class='" + this.getAttr("class", "") + " " + cssClass + "'>" +
				this.html() +
			"</" + tagName + ">"
		);
	};
	
	/**
	 * Get the value of CSS property "propName" (which may be as: "10px")
	 * after striping its "px". If anything wrong, return 0.
	 *
	 * @param propName - String. CSS property name. Mandatory.
	 *
	 * @return integer value of the CSS property. On error, 0.
	 */
	$.fn.pixelVal = function(propName) {
		var val = this.css(propName);
		if (val) {
			val = val.replace(/[^\d]+/gi, "");
		}
		return(val ? parseInt(val) : 0);
	};
	
	/**
	 * Add the CSS class "className" (if it doesn't exists already).
	 *
	 * @param className - String. CSS class name. Mandatory.
	 * 
	 * @return jQuery. The instance itself for chaining purpose.
	 */
	$.fn.addClassIfNotExists = function(className) {
		if (!this.hasClass(className)) {
			this.addClass(className);
		}
		
		return this;
	};
	
	/**
	 * Replace the CSS class(s) "toRemove" (class name or array of class name).
	 * with "toAdd".
	 *
	 * @param toRemove - String or String[]. CSS class name(s) to remove. Mandatory.
	 * @param toAdd - String. CSS class name to add. Mandatory.
	 * 
	 * @return jQuery. The instance itself for chaining purpose.
	 */
	$.fn.replaceClass = function(toRemove, toAdd) {
		if (typeof toRemove == "string") {
			toRemove = [ toRemove ];
		}
		for(var i in toRemove) {
			this.removeClass(toRemove[i]);
		}
		
		return this.addClassIfNotExists(toAdd);
	};
	
	/**
	 * Check whether the mathced element (should be 1) has a vertical
	 * scroll bar present or not.
	 *
	 * @return Boolean. "true" to answer yes.
	 */
	$.fn.hasVScroll = function() {
		return(this.hasScroll('Top'));
	};
	
	/**
	 * Check whether the mathced element (should be 1) has a horizontal
	 * scroll bar present or not.
	 *
	 * @return Boolean. "true" to answer yes.
	 */
	$.fn.hasHScroll = function() {
		return(this.hasScroll('Left'));
	};
	
	/**
	 * Check whether the mathced element (should be 1) has a horizontal
	 * (dir = 'Left') or vertical (dir = 'Top') scroll bar present or not.
	 *
	 * @param dir - String. 'Left' or 'Top'. Mandatory.
	 *
	 * @return Boolean. "true" to answer yes.
	 */
	$.fn.hasScroll = function(dir) {
		var curVal = this['scroll' + dir]();
		this['scroll' + dir](curVal + 1);
		if (this['scroll' + dir]() > curVal) {
			this['scroll' + dir](curVal);
			return(true);
		}
		return(false);
	};
	
	/**
	 * Copy the width from $src into 'this'.
	 *
	 * @param $src - JQuery. Source elements. Mandatory.
	 * 
	 * @return jQuery. The instance itself for chaining purpose.
	 */
	$.fn.syncWidth = function($src) {
		if ($.isTraditionalBoxModel()) {
			this.width($src.outerWidth());
		} else {
			this.width($src.width());
		}
		
		return this;
	};
	
	/**
	 * Get the summation of top and bottom margin of the matched elements.
	 *
	 * @return int. Summation.
	 */
	$.fn.verticalMargin = function() {
		return(this.pixelVal("margin-top") + this.pixelVal("margin-bottom"));
	};
	
	/**
	 * Get the summation of left and right margin of the matched elements.
	 *
	 * @return int. Summation.
	 */
	$.fn.horizontalMargin = function() {
		return(this.pixelVal("margin-left") + this.pixelVal("margin-right"));
	};
	
	/**
	 * Get the summation of left margin and padding of the matched elements.
	 *
	 * @return int. Summation.
	 */
	$.fn.leftSpacing = function() {
		return(this.spacing("left"));
	};
	
	/**
	 * Get the summation of right margin and padding of the matched elements.
	 *
	 * @return int. Summation.
	 */
	$.fn.rightSpacing = function() {
		return(this.spacing("right"));
	};
	
	/**
	 * Get the summation of top margin and padding of the matched elements.
	 *
	 * @return int. Summation.
	 */
	$.fn.topSpacing = function() {
		return(this.spacing("top"));
	};
	
	/**
	 * Get the summation of bottom margin and padding of the matched elements.
	 *
	 * @return int. Summation.
	 */
	$.fn.bottomSpacing = function() {
		return(this.spacing("bottom"));
	};
	
	/**
	 * Get the margin + padding in the "dir" (top, left etc) direction.
	 * 
	 * @param dir - String. "top" or "bottom" or "left" or "right". Mandatory.
	 */
	$.fn.spacing = function(dir) {
		return(this.pixelVal("margin-" + dir) + this.pixelVal("padding-" + dir));
	};
	
	/**
	 * Enlarge the margin of all matched element to "inc". For example,
	 * 1) cssProp: "margin-top",  inc: 20
	 *    Current "margin-top": 10px;
	 *    So, new "margin-top": 30px;
	 * 
	 * 2) cssProp: "margin-left", inc: -20
	 *    Current "margin-left": 10px;
	 *    So, new "margin-left": -10px;
	 * 
	 * @param cssProp - String / String[]. "margin-top" or "padding-top" or like this all margin, padding. Mandatory.
	 * @param inc - int. Amount of pixel to increase. Can be negative. Mandatory.
	 * 
	 * @return jQuery. The instance itself for chaining purpose.
	 */
	$.fn.enlarge = function(cssProp, inc) {
		if (typeof cssProp == "string") {
			cssProp = [ cssProp ];
		}
		
		for(var i in cssProp) {
			this.css(cssProp[i], (this.pixelVal(cssProp[i]) + inc) + "px");
		}
		
		return this;
	};
	
	/**
	 * Copy CSS entries (defined by "cssProp") from "$src" into "this".
	 * 
	 * @param cssProp - String / String[]. "margin-top" or "background-color" etc. Mandatory.
	 * @param $src - jQuery. Source of CSS properties. Mandatory.
	 * 
	 * @return jQuery. The instance itself for chaining purpose.
	 */
	$.fn.copyCSS = function(cssProp, $src) {
		if (typeof cssProp == "string") {
			cssProp = [ cssProp ];
		}
		
		for(var i in cssProp) {
			var val = $src.css(cssProp[i]);
			if (val) {
				this.css(cssProp[i], val);
			}
		}
		
		return this;
	};
	
	/**
	 * Make a room for "$gift" (it is expected to be a "fixed" element) to be placed.
	 * 
	 * @param dir - String. "top" or "bottom" or "left" or "right". Mandatory.
	 * @param $gift - jQuery. Selecting a "fixed" element. Mandatory.
	 * 
	 * @return jQuery. The instance itself for chaining purpose.
	 */
	$.fn.makeRoomForFixedElem = function(dir, $gift) {
		var paddingCSS = "padding-" + dir;
		var roomUnit = (dir == "top" || dir == "bottom") ? $gift.outerHeight() : $gift.outerWidth();
		
		this.css(paddingCSS, Math.max(this.pixelVal(paddingCSS), roomUnit) + "px");
		
		return this;
	};
})(jQuery);

