/**
 * TEST : OOP JAVASCRIPT LIBRARY
 * @author Dan Dean
 * @version 0.0.1
 * Changes:
 * 		Added Flash Detection Capabilities
 * 		Added Cookie Handling Capabilities (these need to be updated for .Net interoperability
 * Note:
 * 		This library is subject to MASSIVE changes
 */
var uspopinit = {
	init: function() {
		if (typeof window.us == "undefined") {
			us = new Object();
		}
		if (typeof window.us.pops == "undefined") {
			us.popdt = new Object();
		}
		
		/**
		 * AUGMENT
		 * Used to augment Native JS Object capabilities.
		 * FROM: Object.extend in Prototype base.js
		 * NOTE: Added typeof check to make sure there isn't already a native version
		 * @param destination: The Object to augment
		 * @param source: The Object Literal to augment destination with.
		 */
		Object.augment = function(destination, source) {
			for (property in source) {
				if (typeof destination[property] == "undefined") {
					// [UNCOMMEND TO SEE WHAT IS BEING AUGMENTED IN YOUR TARGET BROWSER]
					// alert(source[property]);
					destination[property] = source[property];
				}
			}
			return destination;
		}
	}
}
uspopinit.init();


/**
 * DEBUG
 * Will be moved to a "Notify" Object that takes care of all sorts of cool stuff.
 */
function debug(aMsg) {
	if (document.all) { // do not operate in IE.
		setTimeout(function() { throw new Error("[debug] " + aMsg); }, 0);
	}
}


/**
 * CONFIGURATION SETTINGS
 * For future use.
 */
us.popdt.config = {}


/**
 * Holds data about the us.popdt Library
 * @author	Dan Dean
 */
us.popdt.stats = {
	org: "Pop Design + Technology",
	title: "JS Library",
	version: {
		major: 0,
		minor: 2,
		note: "Base functionality for x-browser compatability.\nNext To Do: Dynamic Script Loading.",
		toFloat: function() {
			return this.major + "." + this.minor;
		},
		toString: function() {
			return us.popdt.stats.org + " " + us.popdt.stats.title + "\n" + "Version: " + this.major + "." + this.minor + "\nNote: " + this.note;
		}
	}
}


/**
 * Holds reuseable bits of code for use by Library Classes and Functions
 * @author	Dan Dean
 */
us.popdt.snippet = {
	// Used by String.extractScripts, String.stripScripts
	ScriptFragment: '(?:<script.*?>)((\n|\r|.)*?)(?:<\/script>)'
}


/**
 * Augment Native JavaScript Objects, where necessary
 * @author	Dan Dean
 */
us.popdt.jsaugment = function() {
	var ns = us.popdt; // referenced in multiple places in this function, enables easy updating of NameSpace

	/**
	 * Augment the browser String capabilities, where necessary
	 * Taken directly from prototype.js
	 */
	Object.augment(String.prototype, {
		
		/**
		 * Remove whitespace at the front and back of a String
		 * FROM: http://kasimchen.com/2005/03/27/javascript-trim/#comment-789
		 */
		trim: function() {
			return this.replace(/^\s*|\s*$/g,'');
		},
		
		stripTags: function() {
			return this.replace(/<\/?[^>]+>/gi, '');
		},
	
		stripScripts: function() {
			return this.replace(new RegExp(ns.snippet.ScriptFragment, 'img'), '');
		},
		
		extractScripts: function() {
			var matchAll = new RegExp(ns.snippet.ScriptFragment, 'img');
			var matchOne = new RegExp(ns.snippet.ScriptFragment, 'im');
			return (this.match(matchAll) || []).map(function(scriptTag) {
				return (scriptTag.match(matchOne) || ['', ''])[1];
			});
		},
		
		evalScripts: function() {
			return this.extractScripts().map(eval);
		},
	
		escapeHTML: function() {
			var div = document.createElement('div');
			var text = document.createTextNode(this);
			div.appendChild(text);
			return div.innerHTML;
		},
	
		unescapeHTML: function() {
			var div = document.createElement('div');
			div.innerHTML = this.stripTags();
			return div.childNodes[0] ? div.childNodes[0].nodeValue : '';
		},
		
		/**
		 * Prototype.js version of this was dependent on other Prototype.js Objects.
		 * Rewritten for Library independence.
		 * @author Dan Dean
		 * @return Object containing name/value pairs
		 * @return Value of supplied "key"
		 * @usage alert("firstname=dan&lastname=dean".toQueryParams()["lastname"]);
		 */
		toQueryParams: function() {
			var query_array =	this.substr(this.indexOf("?") + 1).split("&");
			var queryObj =		{};
			var query_count =	query_array.length;
			for (var i=0; i < query_count; i++) {
				query_pair = query_array[i].split("=");
				queryObj[query_pair[0]] = query_pair[1];
			}
			return queryObj;
		},
		
		toArray: function() {
			return this.split('');
		},
		
		camelize: function() {
			var oStringList = this.split('-');
			if (oStringList.length == 1) return oStringList[0];
				
			var camelizedString = this.indexOf('-') == 0
				? oStringList[0].charAt(0).toUpperCase() + oStringList[0].substring(1) 
				: oStringList[0];
				
			for (var i = 1, len = oStringList.length; i < len; i++) {
				var s = oStringList[i];
				camelizedString += s.charAt(0).toUpperCase() + s.substring(1);
			}
			
			return camelizedString;
		},
	
		inspect: function() {
			return "'" + this.replace('\\', '\\\\').replace("'", '\\\'') + "'";
		}
	});
	
	/**
	 * Augment the browser Function capabilities, where necessary
	 */
	Object.augment(Function.prototype, {
		// both call() and apply() are need by IE5 for some of the Array methods to work.
		// FROM: http://www.browserland.org/scripts/dragdrop/
		apply: function(scope, args) {
			if (!args) args = [];
			var index = 0, result;
			do { -- index } while (typeof scope[index] != "undefined");
			scope[index] = this;
		
			switch (args.length) {
				case 0:
					result = scope[index]();
					break;
				case 1:
					result = scope[index](args[0]);
					break;
				case 2:
					result = scope[index](args[0], args[1]);
					break;
				case 3:
					result = scope[index](args[0], args[1], args[2]);
					break;
				case 4:
					result = scope[index](args[0], args[1], args[2], args[3]);
					break;
				default:
					result = scope[index](args[0], args[1], args[2], args[3], args[4]);
					break;
			}
		
			delete scope[index];
			return result;
		},

		// FROM: http://www.browserland.org/scripts/dragdrop/
		call: function(scope) {
			var args = new Array(Math.max(arguments.length-1, 0));
			for (var i = 1; i < arguments.length; i++)
				args[i-1] = arguments[i];
			return this.apply(scope, args);
		}
	});
	
	/**
	 * Augment the browser Array capabilities, where necessary
	 * For a full explanation of JS Array capabilities, see the Array documentation:
	 * http://developer.mozilla.org/en/docs/Core_JavaScript_1.5_Reference
	 */
	Object.augment(Array.prototype, {
		// FROM DECONCEPT
		// Adds one or more elements to the end of an array and returns the new length of the array.
		push: function(item) { // IE5
			this[this.length] = item;
			return this.length;
		},

		/**
		 * Adds and/or removes elements from an array.
		 * @param index: Index at which to start changing the array.
		 * @param count: An integer indicating the number of old array elements to remove.
		 * 				 If count is 0, no elements are removed. In this case, you should specify at least one new element.
		 * @return An array containing the removed elements.
		 * FROM http://www.webreference.com/dhtml/column33/13.html
		 */
		splice: function(index,count){
			if(arguments.length == 0) return index;
			if(typeof index != "number") index = 0;
			if(index < 0) index = Math.max(0,this.length + index);
			if(index > this.length) {
				if(arguments.length > 2) index = this.length;
				else return [];
			}
			if(arguments.length < 2) count = this.length-index;
			count = (typeof count == "number") ? Math.max(0,count) : 0;
			removeArray = this.slice(index,index+count);
			endArray = this.slice(index+count);
			this.length = index;
			for(var i=2;i < arguments.length;i++){
				this[this.length] = arguments[i];
			}
			for(var i=0;i < endArray.length;i++){
				this[this.length] = endArray[i];
			}
			return removeArray;
		},

		/**
		 * Returns the first (least) index of an element within the array equal to the specified value, or -1 if none is found.
		 * @param obj: needle
		 * @param si: index to start search. Optional.
		 * FROM http://erik.eae.net
		 */
		indexOf: function (obj, si) {
			if (si == null) {
				si = 0;
			} else if (si < 0) {
				si = Math.max(0, this.length + si);
			}
			for (var i=si; i < this.length; i++) {
				if (this[i] === obj) { return i; }
			}
			return -1;
		},

		/**
		 * Returns the last (greatest) index of an element within the array equal to the specified value, or -1 if none is found.
		 * @param obj: needle
		 * @param si: index to start search. Optional.
		 * FROM http://erik.eae.net
		 */
		lastIndexOf: function (obj, si) {
			if (si == null) {
				si = this.length - 1;
			} else if (si < 0) {
				si = Math.max(0, this.length + si);
			}
			for (var i = si; i >= 0; i--) {
				if (this[i] === obj) { return i; }
			}
			return -1;
		},

		/**
		 * Calls a function for each element in the array.
		 * @param f: callback function to apply to each element in the array.
		 * @param obj: Optional object to apply function to. Defaults to 'this'.
		 * FROM http://erik.eae.net
		 */
		forEach: function (f, obj) {
			var obj = (obj != null) ? obj : this; // IE5 [dd]

			var l = this.length;
			for (var i = 0; i < l; i++) {
				f.call(obj, this[i], i, this);
			}
		},

		/**
		 * Creates a new array with all of the elements of this array for which the provided filtering function returns true.
		 * @param f: callback function to apply to each element in the array.
		 * @param obj: Optional object to apply function to. Defaults to 'this'.
		 * FROM http://erik.eae.net
		 */
		filter: function (f, obj) {
			var obj = (obj != null) ? obj : this; // IE5 [dd]

			var l = this.length;
			var res = [];
			for (var i = 0; i < l; i++) {
				if (f.call(obj, this[i], i, this)) {
					res.push(this[i]);
				}
			}
			return res;
		},
		// Creates a new array with the results of calling a provided function on every element in this array.
		map: function (f, obj) {
			var obj = (obj != null) ? obj : this; // IE5 [dd]

			var l = this.length;
			var res = [];
			for (var i = 0; i < l; i++) {
				res.push(f.call(obj, this[i], i, this));
			}
			return res;
		},
		// Determines the existance of the needle in the stack
		contains: function (obj) {
			return this.indexOf(obj) != -1;
		},
		// Duplicates the subject array to a new array: var blah = myArray.copy()
		copy: function (obj) {
			return this.concat();
		},
		// Add something to stack at the specified index.
		insertAt: function (obj, i) {
			this.splice(i, 0, obj);
		},
		// Add something at the first index of obj2. Bumps everything from obj2 to the end of the stack down one index.
		// If obj2 is not found, obj is added to the end of the array
		insertBefore: function (obj, obj2) {
			var i = this.indexOf(obj2);
			if (i == -1) {
				this.push(obj);
			} else {
				this.splice(i, 0, obj);
			}
		},
		// Removes needle at specified index.
		removeAt: function (i) {
			this.splice(i, 1);
		},
		// Removes the first occurence of the supplied obj.
		remove: function (obj) {
			var i = this.indexOf(obj);
			if (i != -1) {
				this.splice(i, 1);
			}
		}
	});
}
us.popdt.jsaugment();


/**
 * WINDOW
 * Encapsulates all functionality related to windows: Print, Popup, Non-Modal, etc
 * @author Dan Dean
 */
us.popdt.window = {
	// PRINT: Encapsulates all functionality relating to PRINTING THE PAGE
	print: {

		/**
		 * Send the current page to the printer
		 * @author	Dan Dean
		 */
		send: function() {
			if (typeof window.print != "undefined"){
				window.print();
				return true;
			} else {
				alert("Please use your web browsers Print button.");
				return false;
			}
		},

		/**
		 * Attaches PRINT functionality to any link on the page of "print_btn", or the supplied class name
		 * @author TBD
		 */
		attach: function() {
			alert('attach');
			return true;
		}
	}
}


/**
 * COOKIE
 * This Object contains methods related cookie manipulation
 */
us.popdt.cookie = {
	
	/**
	 * Sets a new cookie in the browser. To delete a cookie, pass a negative value in the "expire" param.
	 * @param {string} c_name The name of the cookie. Required.
	 * @param {string} c_value	The value of the cookie. Required.
	 * @param {int} The duration of the cookie in days
	 * @param {string} path The path
	 * @param {string} domain The domain
	 * @param {bool} secure Require HTTPS for cookie access
	 * @return {bool} false if c_name and c_value are not supplied
	 * @return {bool} false if cookie could not be set (browser doesn't except cookies)
	 */
	set: function(c_name, c_value, expire, path, domain, secure) {
		// Make sure a name/value pair was passed
		if (typeof c_name == "undefined" || typeof c_value == "undefined") {
			throw(new Error("cookie.set(): You must supply both a Cookie Name and Cookie Value."));
		}
		var cookie = c_name + "=" + escape(c_value);
		
		if (typeof expire != "undefined") {
			var c_expire = new Date();
			c_expire.setDate(c_expire.getDate() + expire);
			
			cookie += ";expires=" + c_expire.toGMTString();
		}
		
		cookie += (typeof path != "undefined") ? ";path=" + path : "";
		cookie += (typeof domain != "undefined") ? ";domain=" + domain : "";
		cookie += (typeof secure != "undefined" && secure == true) ? ";secure=true" : "";

		document.cookie = cookie;
		if (us.popdt.cookie.get(c_name) !== false) {
			return true;
		} else {
			return false;
		}
	},
	
	/**
	 * Gets the value of a cookie
	 * @param {string} name The name of the cookie to get a value from.
	 * @param {string} key Optional
	 * @requires String.toQueryParams()
	 * Note: Not fully operable with .Net cookies.
	 */
	get: function(c_name, key){
		var cookies = document.cookie.split("; ");
		for (var i=0; i < cookies.length; i++) {
			var cookie = cookies[i].split("=");
			var cookie_name = cookie[0];
			var cookie_value = cookie[1];

			if (cookie_name == c_name) {
				cookie_value = unescape(cookie_value);
				if (key) {
					return cookie_value.toQueryParams()[key];
				}
				return cookie_value;
			}
		}
		return false;
	}
}


/**
 * NAVIGATION
 * Things like redirection, select navigation, etc
 */
us.popdt.navigation = {
	redirect: function(where) {
		document.location = where;
	}
}


/**
 * FLASH
 * This section contains all functionality relating to FLASH DETECTION AND EMBEDDING
 * Most of this is taken from the Deconcept SWFObject
 * A lot more needs to be done to fully port this over to our library.
 */
us.popdt.flash = {
	checked: false, // whether or not we have already checked for flash
	
	/**
	 * HOLDS the version of the flash player
	 */
	PlayerVersion: {
		major: 0,
		minor: 0,
		rev: 0
	},

	/**
	 * STORES the detected version of the flash player.
	 */
	setPlayerVersion: function(arrVersion){
		this.PlayerVersion.major = parseInt(arrVersion[0]) || 0;
		this.PlayerVersion.minor = parseInt(arrVersion[1]) || 0;
		this.PlayerVersion.rev = parseInt(arrVersion[2]) || 0;
	},

	/**
	 * DETECTS the version of the flash player
	 */
	detectVersion: function(){
		// set detected flash
		this.checked = true;
		// set version number
		var PlayerVersion = this.getPlayerVersion();
		if (navigator.plugins && navigator.mimeTypes.length) {
			var plugin = navigator.plugins["Shockwave Flash"];
			if (typeof plugin != "undefined" && typeof plugin.description != "undefined") {
				this.setPlayerVersion(plugin.description.replace(/([a-z]|[A-Z]|\s)+/, "").replace(/(\s+r|\s+b[0-9]+)/, ".").split("."));
			}
		} else {
			try{
				var plugin = new ActiveXObject("ShockwaveFlash.ShockwaveFlash");
				this.setPlayerVersion(plugin.GetVariable("$version").split(" ")[1].split(","));
			}catch(e){}
		}
	},
	
	/**
	 * RETURNS the detected version of the flash player
	 * @param which The part of the version ["major" | "minor" | "rev"] that you want.
	 * @return An the PlayerVersion Object.
	 */
	getPlayerVersion: function(which) {
		var version;
		if (!this.checked) {
			this.detectVersion();
		}
		if (typeof which == "undefined") {
			version = [this.PlayerVersion.major, this.PlayerVersion.minor, this.PlayerVersion.rev];
		}
		return this.PlayerVersion;
	},
	
	/**
	 * Determines if the passed version meets your required flash player version
	 * @param {array} req_version An Array consisting of the flash player version that you need.
	 * @return {bool} True or False
	 * @usage if (!us.popdt.flash.require([8,5,1])) { alert("Install Flash"); }
	 */
	require: function(req_version) {
		var major = req_version[0];
		var minor = req_version[1];
		var rev = req_version[2];
		
		var playerVersion = this.getPlayerVersion();
		var pass = false;
		
		switch(true) {
			case ( (playerVersion.major >= major) && (typeof minor == "undefined") && (typeof rev == "undefined")):
				pass = true;
				break;
			
			case ( (playerVersion.major >= major) && (playerVersion.minor >= minor) && (typeof rev == "undefined") ):
				pass = true;
				break;
				
			case ( (playerVersion.major >= major) && (playerVersion.minor >= minor) && (playerVersion.rev >= rev) ):
				pass = true;
				break;
			
			default:
				pass = false;
		}
		
		return pass;
	},

	/**
	 * EMBEDS SWF on the page
	 */
	FlashObject: function(swf, id, w, h, ver, c, quality){
		// OBJECTS
		this.params = new Object();
		this.variables = new Object();
		this.attributes = new Array();
		
		// ATTRIBUTE ASSIGNMENT
		if(swf) this.setAttribute('swf', swf);
		if(id) this.setAttribute('id', id);
		if(w) this.setAttribute('width', w);
		if(h) this.setAttribute('height', h);
		if (ver) this.setAttribute('version', ver);

		if(c) this.addParam('bgcolor', c);

		var q = quality ? quality : 'high';
		this.addParam('quality', q);
	}
}
us.popdt.flash.FlashObject.prototype = {
	setAttribute: function(name, value){
		this.attributes[name] = value;
	},
	getAttribute: function(name){
		return this.attributes[name];
	},
	addParam: function(name, value){
		this.params[name] = value;
	},
	getParams: function(){
		return this.params;
	},
	addVariable: function(name, value){
		this.variables[name] = value;
	},
	getVariable: function(name){
		return this.variables[name];
	},
	getVariables: function(){
		return this.variables;
	},
	createParamTag: function(n, v){
		var p = document.createElement('param');
		p.setAttribute('name', n);
		p.setAttribute('value', v);
		return p;
	},
	getVariablePairs: function(){
		var variablePairs = new Array();
		var key;
		var variables = this.getVariables();
		for(key in variables){
			variablePairs.push(key +"="+ variables[key]);
		}
		return variablePairs;
	},
	getFlashHTML: function() {
		var flashNode = "";
		if (navigator.plugins && navigator.mimeTypes && navigator.mimeTypes.length) { // netscape plugin architecture
			if (this.getAttribute("doExpressInstall")) this.addVariable("MMplayerType", "PlugIn");
			flashNode = '<embed type="application/x-shockwave-flash" src="'+ this.getAttribute('swf') +'" width="'+ this.getAttribute('width') +'" height="'+ this.getAttribute('height') +'"';
			flashNode += ' id="'+ this.getAttribute('id') +'" name="'+ this.getAttribute('id') +'" ';
			var params = this.getParams();
			for(var key in params){ flashNode += [key] +'="'+ params[key] +'" '; }
			var pairs = this.getVariablePairs().join("&");
			if (pairs.length > 0){ flashNode += 'flashvars="'+ pairs +'"'; }
			flashNode += '/>';
		} else { // PC IE
			if (this.getAttribute("doExpressInstall")) this.addVariable("MMplayerType", "ActiveX");
			flashNode = '<object id="'+ this.getAttribute('id') +'" classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" width="'+ this.getAttribute('width') +'" height="'+ this.getAttribute('height') +'">';
			flashNode += '<param name="movie" value="'+ this.getAttribute('swf') +'" />';
			var params = this.getParams();
			for(var key in params) {
			 flashNode += '<param name="'+ key +'" value="'+ params[key] +'" />';
			}
			var pairs = this.getVariablePairs().join("&");
			if(pairs.length > 0) {flashNode += '<param name="flashvars" value="'+ pairs +'" />';}
			flashNode += "</object>";
		}
		return flashNode;
	},
	write: function(elementId){
		if(this.skipDetect || us.popdt.flash.require(this.getAttribute('version'))){
			var n = (typeof elementId == 'string') ? document.getElementById(elementId) : elementId;
			n.innerHTML = this.getFlashHTML();
		} else{
			// NO FLASH
			// if(this.getAttribute('redirectUrl') != "") {
			// 	document.location.replace(this.getAttribute('redirectUrl'));
			// }
		}
	}
}
