// Bam.js library is a product of MLB.com / MLB Advanced Media Inc.
// Any use of this code base outside or w/o written permission of MLB.com is prohibited
var bam = (typeof(bam)==="object")?bam:(function() {
	//Private function to lookup document css styles by href
	var _findStyle = function(href) {
		var styles = document.styleSheets,
			stLen = styles.length - 1,
			cStyle = null;
		if(stLen >= 0) {				
			do {
				cStyle = styles[stLen];
				if(!!cStyle.href && cStyle.href.lastIndexOf(href) > -1) {
					return cStyle;	
				}
			} while(stLen--);
		}
		return null;
	};
	//Prevents modules from being requested twice
	var _modules = [];
		_modules.isLoaded = function(path) {				
			var _out = false,
			_l = this.length-1,
			_c = null;
			if(_l >= 0) {
				do {
					_c=_modules[_l];
					if(!!_c && _c === path) {
						_out=true;	break;
					}
				} while(_l--);
			}
			return _out;
		};
	var _bam = {
		version: "3.1",
		//Sets a local path for packages to load dependencies
		homePath: "/shared/scripts/bam/",
		//Extends functionality of the bam namespace
		/* Ex:
			 bam.extend({
				utils: {
					//... utils code	
				}
			});
			Then access it by - bam.util
		*/			
		extend: function() {
			var ext = null;
			var obj = null;
			if(arguments.length>1){obj=arguments[0];ext=arguments[1];}else{obj=_bam;ext=arguments[0];}
			if(typeof(ext) === "object" && !!ext) {
				for(var i in ext) {
					if (!ext.hasOwnProperty || ext.hasOwnProperty(i)) {
						if(typeof(ext[i]) === "object") {
							if(!obj[i] || !!obj[i].path) { //If backwards
								obj[i] = ext[i];
								if(arguments.length === 1){obj[i].packageName = "bam." + i;}
							} 
						}
					}
				}
			}
		},
		// Appends functions in bulk to an object
		augment: function() {
			var ext = null;
			var obj = null;
			if(arguments.length>1){obj=arguments[0];ext=arguments[1];}else{obj=_bam;ext=arguments[0];}
			if(typeof(ext) === "object" && !!ext) {
				for(var i in ext) {
					if (!ext.hasOwnProperty || ext.hasOwnProperty(i)) {
						if(typeof(ext[i]) === "function") {
							if(!obj[i]) {
								obj[i] = ext[i];
							}
						}
					}
				}
			}
		},
		// (DEPRICATED) This function resembles Java import functionality for a package sub component
		// It activates a package subcomponent if it hasn't been activated yet
		// Usage: bam.imports(bam.dom);
		imports: function() {
			function _syncLoad(obj) {
				if(typeof(obj) === "object" && !!obj.lib) {
					var path = _bam.homePath + obj.path;
					delete _bam[obj.lib];
					_bam.loadSync(path);
				}
			}
			if(arguments.length > 1) {
				var aLen = arguments.length-1;
				do {
					_syncLoad(arguments[aLen]);
				} while(aLen--);
			} else {
				_syncLoad(arguments[0]);
			}
		},			
		//This function loads stand alone remote bam package (async)
		load: function(path, cb) {
			if(typeof(path) === "string") {
				if(!_modules.isLoaded(path)) {
					$.getJSON(path, function(json){
						bam.extend(json);
						_modules.push(path);
						if(!!cb && typeof(cb) === "function") {
							cb();
						}
					});
				} else {
					if(!!cb && typeof(cb) === "function") {
						cb();
					}
				}
			}
		},
		loadSync: function() {
			function _syncLoad(path) {
				$.ajax({
					type: "GET",
					url: path,
					dataType: "json",
					async:false,
					success: function(json) {
						bam.extend(json);
						_modules.push(path);
					}
				});
			}
			if(arguments.length > 1) {
				var aLen = arguments.length-1, _a=0;
				do {						
					if(!_modules.isLoaded(arguments[_a])) {
						_syncLoad(arguments[_a]);
					}
				} while(_a++ < aLen);
			} else {
				if(!_modules.isLoaded(arguments[0])) {
					_syncLoad(arguments[0]);
				}
			}
		},
		//Loads and applies CSS stylesheet to a page
		loadCSS: function(){
			 var stylePath;				
			 var aLen = arguments.length-1, _a=0;
			 if(aLen >= 0) {
				 var _head = document.getElementsByTagName("head")[0], 
					 _link = document.createElement("link"),
					 cLink = null;
					 _link.setAttribute("rel", "stylesheet");
					 _link.setAttribute("type", "text/css");
				 do {	 
					  if(!_findStyle(arguments[_a])){
						   stylePath = arguments[_a];
						   if(typeof(stylePath) === "string") {
							   cLink = _link.cloneNode(true);
							   cLink.setAttribute("href", stylePath);
							   _head.appendChild(cLink);
						   }
					  }
				 } while(_a++ < aLen);
			 }
		 },
		//This function unloads/removes a stylesheet from a document based on href path specified
		unloadCSS: function() {							 
			 var styleSheet = null, 
				 owner = null,
				 parent = null;				 
			 var aLen = arguments.length-1;
			 if(aLen >= 0) {
				 do {
					  if(!!(styleSheet = _findStyle(arguments[aLen]))){
						   //First disable it
						   styleSheet.disabled = true;
						   //Then remove it
						   owner = (!!styleSheet.owningElement)?styleSheet.owningElement:styleSheet.ownerNode;
						   parent = (!!owner.parentElement)?owner.parentElement:owner.parentNode;
						   parent.removeChild(owner);
					  }
				 } while(aLen--);
			 }
		 },			 
		// bam.clone() makes a copy of the passed object and returns it.
		// If the passed object is not of type "object" or is null, an error is thrown
		clone: function(obj){
			if(typeof(obj)==="object"&&!!obj){
				function F(){}
				F.prototype = obj;
				return new F();
			} 
			else {
				throw new Error("bam.clone() was called with an invalid or null argument.");	
			}
		},
		//Backwards compat code
		collections		:{path:"bam.collections.js", lib:"collections"},
		cookies			:{path:"bam.cookies.js", lib:"cookies"},
		datetime		:{path:"bam.datetime.js", lib:"datetime"},
		dom				:{path:"bam.dom.js", lib:"dom"},
		filters			:{path:"bam.filters.js", lib:"filters"},
		forms			:{path:"bam.forms.js", lib:"forms"},
		soap			:{path:"bam.soap.js", lib:"soap"},
		url				:{path:"bam.url.js", lib:"url"},
		validation		:{path:"bam.validation.js", lib:"validation"},
		xml				:{path:"bam.xml.js", lib:"xml"},
		//Objects utils
		object: {
			typeOf: function(obj) {
				var out = "";
				if(typeof(obj) === "object") {
					switch(true) {
						case (!obj): out = "null"; break;
						case (obj instanceof String): out = "string"; break;
						case (obj instanceof Number): out = isNaN(obj)?"NaN":"number"; break;
						case (obj instanceof Date): out = isNaN(obj)?"NaN":"date"; break;
						case (obj instanceof Array): out = "array"; break;
						case (!!obj.tagName): out = obj.tagName.toLowerCase(); break;
						case (typeof(obj.nodeType)!=="undefined"): out = "xml"; break;
						default: out = "object";
					}				
				} else {
					if(typeof(obj) === "number" && isNaN(obj)) {
						out = "NaN";	
					} else {
						out = typeof(obj);
					}
				}
				return out;
			},
			
	    /**
       * Returns the value of a nested property or undefined if any property of
       * the dot-delimited path does not exist.
       *
       * Example:
       * var foo = {
       *   deeply: {
       *     nested: {
       *       property: 'bar'
       *     }
       *   }
       * };
       *
       * getDeepValue(foo, 'deeply.nested.property'); // returns 'bar'
       * getDeepValue(foo, 'deeply.held.belief');     // returns undefined
       *
       * @method
       * @name getDeepValue
       * @alias bam.object.getDeepValue
       * @memberOf bam.object
       * @static
       * @public
       * @param {Object} obj Source object
       * @param {String} deepProp Dot-delimited path to nested property
       * @returns Value of nested property or undefined
       */
      getDeepValue: function (obj, deepProp) {
        
        var props = deepProp.split('.'),
            i = 0, n = props.length;

        while (typeof (obj = obj[props[i]]) !== 'undefined' && ++i < n);
        
        return obj;
      },
			
      /**
       * Sets the value of an object's nested property, specified by dot-delimited
       * string, defining any undefined property in its path.
       *
       * Example:
       * var foo = {
       *   deeply: {}
       * };
       *
       * setDeepValue(foo, 'deeply.nested.property', 'bar');
       *
       * // foo = {
       * //   deeply: {
       * //     nested: {
       * //       property: 'bar'
       * //     }
       * //   }
       * // }
       *
       * @method
       * @name setDeepValue
       * @alias bam.object.setDeepValue
       * @memberOf bam.object
       * @static
       * @public
       * @param {Object} obj Target object
       * @param {String} deepProp Dot-delimited path to nested property
       * @param val Value to assign to nested property
       * @returns {Object} The modified target object
       */
      setDeepValue: function (obj, deepProp, val) {

        var props = deepProp.split('.'),
            root = obj,
            i = 0, n = props.length - 1,
            p, t;

        while (i < n) {
          p = props[i];
          t = typeof obj[p];
          obj = obj[p] = (t === 'object' || t === 'function') ? obj[p] : {};
          i++;
        }
        
        obj[props[i]] = val;
        
        return root;
      },
			  
      namespace: function (obj, namespace) {
        var props = namespace.split('.'),
            i, n;
        for (i = 0, n = props.length; i < n; ++i) {
          obj = obj[props[i]] = obj[props[i]] || {};
        }
        return obj;
      },
          
      /**
       * YUI 2.6.0
       * Utility to set up the prototype, constructor and superclass properties to
       * support an inheritance strategy that can chain constructors and methods.
       * Static members will not be inherited.
       *
       * @method extend
       * @static
       * @param {Function} subclass   the object to modify
       * @param {Function} superclass the object to inherit
       * @param {Object} overrides  additional properties/methods to add to the
       *                              subclass prototype.  These will override the
       *                              matching items obtained from the superclass 
       *                              if present.
       */
      extend: function (subclass, superclass, overrides) {
        if (!superclass || !subclass) {
          throw new Error('extend failed, please check that all dependencies are included.');
        }
        var F = function () {};
        F.prototype = superclass.prototype;
        subclass.prototype = new F();
        subclass.prototype.constructor = subclass;
        subclass.superclass = superclass.prototype;
        if (superclass.prototype.constructor === Object.prototype.constructor) {
          superclass.prototype.constructor = superclass;
        }
        if (overrides) {
          for (var i in overrides) {
            if (overrides.hasOwnProperty(i)) {
              subclass.prototype[i] = overrides[i];
            }
          }
          var isIE  = !+"\v1";
          if (isIE) {
            bam.object._IEEnumFix(subclass.prototype, overrides);
          }
        }
      },
			
      /**
       * YUI 2.6.0
       * IE will not enumerate native functions in a derived object even if the
       * function was overridden.  This is a workaround for specific functions 
       * we care about on the Object prototype. 
       * @property _IEEnumFix
       * @param {Function} obj  the object to receive the augmentation
       * @param {Function} ext  the object that supplies the properties to augment
       * @static
       * @private
       */
      _IEEnumFix: function(obj, ext) {
        var fnNames = ['toString', 'valueOf'],
            fnName,
            fn,
            i, n;
        for (i = 0, n = fnNames.length; i < n; ++i) {
          fnName = fnNames[i];
          fn = ext[fnName];
          if (typeof fn === 'function' && fn != Object.prototype[fnName]) {
            obj[fnName] = fn;
          }          
        }
			},
			
      /**
       * Returns a function bound to the scope (the object referred to by the
       * "this" keyword inside the function) of a supplied object. Accepts
       * optional default arguments to be passed to the function if supplied
       * arguments are undefined. This method is useful for helping callback
       * functions keep their intended scope.
       *
       * Example:
       *
       * var Person = function(name) {
       *   this.name = name;
       * };
       * 
       * Person.prototype = {
       *   greet: function(greeting, occupation) {
       *     return greeting + ', my name is ' + this.name + ' and I\'m a ' + occupation + '.';
       *   }
       * };
       * 
       * var furf = new Person('furf');
       *
       * // NOTE: The following two lines are synonymous...
       * var callback = bind(furf.greet, furf, 'Aloha', 'code monkey');
       * var callback = bind('greet', furf, 'Aloha', 'code monkey');
       *
       * callback();                   // Returns "Aloha, my name is furf and I'm a code monkey."
       * callback('Hello');            // Returns "Hello, my name is furf and I'm a code monkey."
       * callback('Ciao', 'gigolo');   // Returns "Ciao, my name is furf and I'm a gigolo."
       * callback(undefined, 'bonzo'); // Returns "Aloha, my name is furf and I'm a bonzo."
       *
       * @method
       * @name bind
       * @alias bam.object.bind
       * @memberOf bam.object
       * @static
       * @public
       * @param {Function|String} fn Function or method name of obj param
       * @param {Object} obj Object (scope) in which to execute function
       * @returns {Function} Bound function
       */
      bind: function(fn, obj /*, defaults */) {

        var scope = obj || window,
            defaults;

        fn = (typeof fn === 'string') ? scope[fn] : fn;

        // If no defaults are supplied, return the lightweight callback
        if (arguments.length < 3) {

          return function() {
            fn.apply(scope, arguments);
          };

        // Otherwise, wrap with code to merge defaults with supplied arguments
        } else {

          defaults = Array.prototype.slice.call(arguments, 2);

          return function() {

            // Merge defaults with supplied arguments
            var args = [];
            for (var i = 0; i < Math.max(arguments.length, defaults.length); ++i) {
              args[i] = (typeof arguments[i] !== 'undefined') ? arguments[i] : defaults[i];
            }

            fn.apply(scope, args);
          };
        }
      }
		},
		util: {

      /**
       * Ensures the value passed in is returned as an array. Useful for
       * situations that require iteration of data. Undefined values are returned
       * as empty arrays.
       *
       * @method
       * @name ensureArray
       * @alias bam.util.ensureArray
       * @memberOf bam.util
       * @static
       * @public
       * @param val Original value
       * @returns {Array} The original array or the original value wrapped in array
       */
      ensureArray: function (val) {
        return (val instanceof Array) ? val : (typeof val !== 'undefined') ? [val] : [];
      },
      
      /**
       * Shortcut utility for returning the queryResults.row property of a data
       * object as an iterable array. Useful for extracting data from a standard
       * MLB feed.
       *
       * @method
       * @name getQueryResults
       * @alias bam.util.getQueryResults
       * @memberOf bam.util
       * @static
       * @public
       * @param {Object} data Source object
       * @param {String} deepProp (optional) Dot-delimited path to nested property
       * @returns {Array} Query result rows wrapped in an iterable array
       */
      getQueryResults: function (data /*, deepProp */) {
        var deepProp = (typeof arguments[1] !== 'undefined') ? arguments[1] + '.' : '';
        return bam.util.ensureArray(bam.object.getDeepValue(data, deepProp + 'queryResults.row'));
      },

      getQueryResult: function (data /*, deepProp, index */) {
        return bam.util.getQueryResults(data, arguments[1])[arguments[2] || 0];
      },
      
      countQueryResults: function (data /*, deepProp */) {
        var deepProp = (typeof arguments[1] !== 'undefined') ? arguments[1] + '.' : '';
        return parseInt(bam.object.getDeepValue(data, deepProp + 'queryResults.totalSize'), 10);
      },
      
      /**
       * Shortcut utility for wrapping data as queryResults.row property of an
       * object. Useful for passing non-standard MLB feed data to a DataGrid
       * instance.
       *
       * Example:
       *
       * var uie = wrapQueryResults([
       *   { id: 1, name: 'Mike' },
       *   { id: 2, name: 'Aleks' },
       *   { id: 3, name: 'Sam' },
       *   ...
       * ]);
       *
       * // uie = {
       * //   queryResults: {
       * //     row: [
       * //       { id: 1, name: 'Mike' },
       * //       { id: 2, name: 'Aleks' },
       * //       { id: 3, name: 'Sam' },
       * //       ...
       * //     ]
       * //   }
       * // }
       *
       * @method
       * @name wrapQueryResults
       * @alias bam.util.wrapQueryResults
       * @memberOf bam.util
       * @static
       * @public
       * @param {Object} data Source object
       * @returns {Object} Wrapped object
       */
      wrapQueryResults: function (data) {
        return bam.object.setDeepValue({}, 'queryResults.row', data);
      }
		},
		//Number extensions
		number: {
      
      /**
       * Returns either the original value or the limit if value is out of bounds
       *
       * @method
       * @name limitValueToRange
       * @alias bam.util.limitValueToRange
       * @memberOf bam.util
       * @static
       * @public
       * @param {Number} val Value to limit
       * @param {Number} rangeA One end of a numeric range
       * @param {Number} rangeB The other end of a numeric range
       * @returns {Number} Limited value
       */
      limitValueToRange: function(val, rangeA, rangeB) {
        var min = Math.min(rangeA, rangeB),
            max = Math.max(rangeA, rangeB);
        return Math.max(Math.min(val, max), min);
      }
		},
		//String extensions
		string: (function(){
			var _self = {			
				trim: function(str) {
					if(typeof(str) === "string"){return str.replace(/^\s*(\S*(\s+\S+)*)\s*$/,"$1");}
				},
				equals: function(str, str2) {
					if(typeof(str) === "string" && typeof(str2) === "string"){return str === str2;}
				},
				equalsIgnoreCase: function(str, str2) {
					if(typeof(str) === "string" && typeof(str2) === "string"){return str.toLowerCase() === str2.toLowerCase();}
				},
				toCharArray: function(str) {
					if(typeof(str) === "string"){
						var sl = str.length-1;
						var out = [];
						if(sl>=0){do {out.push(str.charAt(sl));} while(sl--);}
						return out.reverse();
					}
				},
				instr: function(str, from, to, val) {
					var _out = str;
					if(typeof(str) === "string"){
						if((!isNaN(from)) && (from >= 0) && (from <= str.length)) {
							if((!isNaN(to)) && (to >= 0) && (to <= str.length)) {
								var _tmp = str.substring(0, from);
								_tmp += val;
								_tmp += str.substring(to);
								_out = _tmp;
							}
						}
					}
					return _out;
				},
				textWrap: function(str, largestWordLength, wordPartSize, wordBreaker) {
					if( bam.object.typeOf(str) !== "string") {
						return str;
					}
					else {
						str = String(str);
					}
					
					largestWordLength = largestWordLength	|| 20;
					wordPartSize = wordPartSize				|| 5;
					
					var userAgent;
					if (wordBreaker) {
					} else if((userAgent = navigator.userAgent.match(/Firefox\/([0-9\.]+)/i)) && parseInt(userAgent[1], 10) < 3) {	// FF 2 does not have &shy; support, but does support &#8203;
						wordBreaker = "&#8203;";
					} else {
						wordBreaker = "&shy;";
					}
	
					var regex = new RegExp("([a-z0-9\\-_]{" + largestWordLength + ",})([^<]*?>)?", "gi");
					return str.replace(regex, function() {
						var match = arguments[1];
						var result = [];
						var i = 0;
	
						if(match.indexOf(wordBreaker) !== -1 || arguments[2]) {
							if(arguments[2])
								match += arguments[2];										// the word has already been split or we're inside a long tag
							return match;
						}
	
						while (match.length > 0) {
							result.push(match.substring(0, wordPartSize));
							match = match.substring(wordPartSize);
						}
						return result.join(wordBreaker);
					});
				},
				StringBuffer: function(str) {
					var _out = [],
						_that = this;
					this.length = 0;
					this.append = function(obj) {
						if(!!obj) {
						var _tmp = String(obj);
						_that.length += _tmp.length;
						_out = _out.concat(bam.string.toCharArray(_tmp));
						}
						return _that;
					};
					this.replace = function(start, end, rplStr) {
						if(bam.object.typeOf(rplStr) === "string" && 
						(bam.object.typeOf(start) === "number" && (start >= 0 && start <= _that.length)) &&
						(bam.object.typeOf(end) === "number" && (end > start && end <= _that.length))
						) {
							var _tmp = _that.toString();
							_tmp = bam.string.instr(_tmp, start, end, rplStr);
							_that.clear();
							_that.append(_tmp);
						}
						return _that;
					};
					this.remove = function(start, end) {
						if(!!end) {
							_that.replace(start, end, "");
						} else {
							_that.replace(start, _that.length, "");
						}					
						return _that;
					};
					this.reverse = function() {
						_out.reverse();
						return _that;
					};
					this.clear = function() {
						_that.length = 0;
						_out = [];
						return _that;
					};
					this.toString = function() {
						return _out.join("");
					};
					if(!!str) {
						_that.append(str);
					}
				},
				//escape HTML
				escapeHTML: function(htmlStr) {
					var _out = htmlStr;
					if(typeof(htmlStr) === "string") {
						//Prevent double escapement
						var _rxEscaped = /&\w*;/g;
						var _escMatch = null;
						while(!!(_escMatch = _rxEscaped.exec(_out))) {
							_out = bam.string.instr(_out, _escMatch.index, _escMatch.index + _escMatch[0].length, escape(_escMatch[0]));
							_rxEscaped.input = _out.substring(_escMatch.lastIndex);
						}
						_out = _out.replace(/&/g, "&amp;");
						_out = _out.replace(/</g, "&lt;");
						_out = _out.replace(/>/g, "&gt;");
						_out = _out.replace(/"/g, "&quote;");
						//Restore escaped pre-encoded tokens
						_out = unescape(_out);
					}
					return _out;
				},
				//Unescape HTML to it's original state
				unescapeHTML: function(htmlStr) {
					var _out = 	htmlStr;
					if(typeof(htmlStr) === "string") {
						var _htmlTokens = [[/&lt;/g, "<"], [/&gt;/g, ">"], [/&amp;/g, "&"], [/&quote;/g, "\""]];
						var _t = _htmlTokens.length-1, _c = null;
						do {
							_c = _htmlTokens[_t];
							_out = _out.replace(_c[0], _c[1]);
						} while(_t--);
					}
					return _out;
				}
			};
			return _self;
		})()
	};	
	
	return _bam;
})();