/**
 * Digital Fruition Javascript Tools
 *
 * A collection of common Digital Fruition functions, including the DFTools
 * library, the com.digitalfruition library, and some assorted common code for
 * dealing with dates, arrays, events, numbers, etc.
 * 
 * All copyright Digital Fruition, LLC except where otherwise noted
 * 
 * A licence must be granted to you by Digital Fruition, LLC in order to make
 * use of this file. USING THIS FILE WITHOUT AGREEMENT TO DIGITAL FRUITION, LLC
 * POLICIES AND TERMS AND CONDITIONS IS EXPRESSLY FORBIDDEN
 * http://www.digitalfruition.com/tos
 *
 * @see http://www.digitalfruition.com/tos
 * @author Joshua Gitlin
 * @copyright 2001 - 2009 Digital Fruition, LLC. All Rights Reserved.
 * @license Propritary Digital Fruition License.
 */


/**
 * Mouse Wheel Event
 *
 * Extend the prototype event class to handle mouse wheel events
 *
 * @author unknown
 * @copyright unknown
 * @license unknown
 */
Object.extend(Event, {
	wheel:function (event){
		var delta = 0;
		if (!event) event = window.event;
		if (event.wheelDelta) {
			delta = event.wheelDelta/120; 
			if (window.opera) delta = -delta;
		} else if (event.detail) { delta = -event.detail/3;	}
		return Math.round(delta); //Safari Round
	}
});


/**
 * Digital Frution Standard Tools Library
 *
 * A collection of standard digital fruition tools, including a console, an
 * error reporting system, math and string functions, and more.
 *
 * @version 1.0
 * @author Joshua Gitlin
 * @copyright 2001 - 2009 Digital Fruition, LLC. All Rights Reserved.
 * @license Propritary Digital Fruition License.
 */
var DFTools =
{
	version: '1.0',
	
	urls: {
		self: '',
		base: '',
		reportError: 'problemReport.php'
	},
	
	setupUrlBase: function()
	{
		$A(document.getElementsByTagName("script")).findAll( function(s) {
			return (s.src && s.src.match(/DFTools\.js(\?.*)?$/))
		}).each( function(s) {
			DFTools.urls.self = s.src;
			DFTools.urls.base = DFTools.urls.self.replace(/[^\/]+\/DFTools\.js(\?.*)?$/,'');
		});
	},
	
	/**
	 * Create (if necessary) a given namespace
	 *
	 * Taken from http://weblogs.asp.net/mschwarz/archive/2005/08/26/423699.aspx
	 */
	namespace: function(ns)
	{
		var nsParts = ns.split(".");
		var root = window;
		
		for(var i=0; i<nsParts.length; i++)
		{
			if(typeof(root[nsParts[i]]) == "undefined"){
				root[nsParts[i]] = new Object();
			}
			
			root = root[nsParts[i]];
		}
	},
	
	
	
	/**
	 * Digital Fruition console
	 *
	 */
	console: (function()
	{
		var log = $A(), debug = $A();
		
		var consoleLog = function()
		{
			try
			{
				try
				{
					var entry = new Date;
					entry = entry.toString() + ": " + $A(arguments).join(' ');
				}
				catch(e)
				{
					entry = "DFTools.console: Error while building a log entry!"
				}
				log.push(entry);
				
				if(log.length >= DFTools.console.maxLogSize)
					log.shift();
				
				if(console && typeof(console.log) == 'function') { console.log.apply(console,arguments); }
			}
			catch(e) {}
		};
		
		var consoleError = function()
		{
			try
			{
				try
				{
					var entry = new Date;
					entry = entry.toString() + ": ERROR: " + $A(arguments).join(' ');
				}
				catch(e)
				{
					entry = "DFTools.console: Error while building a log error entry!"
				}
				log.push(entry);
				
				if(log.length >= DFTools.console.maxLogSize)
					log.shift();
				
				if(console && typeof(console.error) == 'function') { console.error.apply(console,arguments); }
				else if(console && typeof(console.log) == 'function') { console.log.apply(console,arguments); }
			}
			catch(e) {}
		};
		
		var debugLog = function()
		{
			if(DFTools.console.debugLogEnabled)
			{
				try
				{
					try
					{
						var entry = $A(arguments).join(' ');
					}
					catch(e)
					{
						entry = "DFTools.console: Error while building a debug log entry!"
					}
					debug.push(entry);
					
					if(debug.length >= DFTools.console.maxLogSize)
						debug.shift();
					
					if(console && typeof(console.debug) == 'function') { console.debug.apply(console,arguments); }
				}
				catch(e) {}
			}
		};
		
		var getConsoleLog = function()
		{
			return log.join("\n");
		};
		
		var getDebugLog = function()
		{
			return debug.join("\n");
		};
		
		var getAllLogs = function()
		{
			return {'log':getConsoleLog(), 'debug': getDebugLog()};
		};
				
		return {
			log: consoleLog,
			error: consoleError,
			debug: debugLog,
			
			getLog: getConsoleLog,
			getDebugLog: getDebugLog,
			getAll: getAllLogs,
			
			maxLogSize: 1000,
			debugLogEnabled: false
		};
	})(),
	
	cancelEvent: function(e)
	{
		e.stop();
	},
	
	writeEmail: function(u,d,tld)
	{
		document.write('<a href="');
		document.write('mailto:');
		document.write(u);
		document.write('@');
		document.write(d + '.' + tld);
		document.write('">');
		document.write(u);
		document.write('@');
		document.write(d + '.' + tld);
		document.write('</a>');
	},
	
	isValidEmail: function(string)
	{
		return /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i.test(string);
	},
	
	fireEvent: function(element,event)
	{
		if (document.createEventObject)
		{
			// dispatch for IE
			
			var evt = document.createEventObject();
			return element.fireEvent('on'+event,evt);
		}
		else
		{
			// dispatch for firefox + others
			
			var evt = document.createEvent("HTMLEvents");
			evt.initEvent(event, true, true ); // event type,bubbling,cancelable
			return !element.dispatchEvent(evt);
		}
	},
	
	submitForm: function(form)
	{
		
		return DFTools.fireEvent(form,'submit');
	},
	
	loadFlash: function(options,container)
	{
		options = $H(options);
		
		var flashObj = {embedAttrs: {}, params: {}, objAttrs: {}};
		
		$A(["pluginspage"]).each(function(a){ flashObj.embedAttrs[a] = options.get(a); });
		
		$A(["src","movie"]).each(function(a){ flashObj.embedAttrs["src"] = flashObj.params["movie"] = options.get(a); });
		
		$A(["onafterupdate",
			"onbeforeupdate",
			"onblur",
			"oncellchange",
			"onclick",
			"ondblClick",
			"ondrag",
			"ondragend",
			"ondragenter",
			"ondragleave",
			"ondragover",
			"ondrop",
			"onfinish",
			"onfocus",
			"onhelp",
			"onmousedown",
			"onmouseup",
			"onmouseover",
			"onmousemove",
			"onmouseout",
			"onkeypress",
			"onkeydown",
			"onkeyup",
			"onload",
			"onlosecapture",
			"onpropertychange",
			"onreadystatechange",
			"onrowsdelete",
			"onrowenter",
			"onrowexit",
			"onrowsinserted",
			"onstart",
			"onscroll",
			"onbeforeeditfocus",
			"onactivate",
			"onbeforedeactivate",
			"ondeactivate",
			"type",
			"codebase"]).each(function(a){ if(typeof(options.get(a)) != 'undefined') { flashObj.objAttrs[a] = options.get(a); options.unset(a); } });

		$A(["width",
			"height",
			"align",
			"vspace", 
			"hspace",
			"class",
			"title",
			"accesskey",
			"name",
			"id",
			"tabindex"]).each(function(a){ if(typeof(options.get(a)) != 'undefined') { flashObj.objAttrs[a] = flashObj.embedAttrs[a] = options.get(a); options.unset(a); } });
		
		options.each(function(pair){ flashObj.params[pair.key] = flashObj.embedAttrs[pair.key] = pair.value; });
		
		flashObj.objAttrs["classid"] = "clsid:d27cdb6e-ae6d-11cf-96b8-444553540000";
		flashObj.embedAttrs["type"] = "application/x-shockwave-flash";
		
		
		var tag = '';
		
		tag = $H(flashObj.objAttrs).inject('<object ',function(str,pair){
			return str+' '+pair[0]+'="'+pair[1]+'"';
		});
		
		tag += $H(flashObj.params).inject('>',function(str,pair){
			return str+'<param name="'+pair[0]+'" value="'+pair[1]+'" /> ';
		});
		
		tag += $H(flashObj.embedAttrs).inject('<embed ',function(str,pair){
			return str+' '+pair[0]+'="'+pair[1]+'"';
		});
		
		tag += ' ></embed></object>';
		
		if(container = $(container))
		{
			container.insert(tag);
		}
		
		return tag;
	},
	
	imagesLoaded: function(container)
	{
		container = $(container);
		
		if(!container)
			return false;
		
		var images = container.getElementsBySelector('img');
		
		return Try.these(
			function() { return images.pluck('complete').all(); },
			function() { alert("Can't be sure if imagews are loaded, assuming yes..."); return true; }
		);
	},
	
	/**
	 * Set the default text for fields, which will appear when the fields are empty and dissapear when
	 * they gain focus.
	 *
	 * This function takes a field or an array of fields and applies default text to them. This means
	 * that when the field is empty, the default text will appear and the field will get a class of
	 * .defaultText, allowing the text to change color or style. When the field gains focus, the default
	 * text and class are removed.
	 *
	 * You may pass either a single element, and element ID, or an array of elements / element IDs. If
	 * the text field is omitted, the text for each field will be pulled from the field's title attribute
	 *
	 * @param Array/Element fields
	 * @param string [text]
	 * @return value
	 */
	defaultFieldText: function()
	{
		var defaultFieldTextHash = $H({});
		
		var defaultText = '';
		
		var defaultFieldTextMethods = $H(
		{
			getDefaultText: function()
			{
				return defaultFieldTextHash.get(this.identify());
			},
			
			setDefaultText: function(text)
			{
				defaultFieldTextHash.set(this.identify(),text);
			},
			
			defaultTextUpdate: function()
			{
				val = $F(this);
				var defaultText = this.getDefaultText();
				
				if(!val.length || val == defaultText)
				{
					this.value = defaultText;
					this.addClassName('defaultText');
				}
				else
				{
					this.removeClassName('defaultText');
				}
			},
			
			defaultTextClear: function()
			{
				val = $F(this);
				var defaultText = this.getDefaultText();
				
				if(val == defaultText)
				{
					this.value = '';
				}
				this.removeClassName('defaultText');
			}
		});
		
		var applyDefaultFieldText = function(field)
		{
			field = $(field);
			
			if(field)
			{
				defaultFieldTextMethods.each(function (pair) { field[pair.key] = pair.value.bind(field); });
				
				field.setDefaultText(defaultText ? defaultText : field.title);
				
				field.observe('blur',field.defaultTextUpdate);
				field.observe('focus',field.defaultTextClear);
				
				field.defaultTextUpdate();
			}
		};
		
		var defaultFieldText = function(fields,text)
		{
			defaultText = text;
			
			if(!(fields instanceof Array)) { fields = [fields]; }
			
			fields = $A(fields);
		
			fields.each(applyDefaultFieldText);
		};
		
		return defaultFieldText;
	}(),
	
	var_dump: function(v,seen)
	{
		var dump = {type:'unknown',value:''};
		
		try
		{
			if(!seen)
				seen = $A();
			
			dump.type = typeof v;
			
			switch (dump.type)
			{
				case "function":
					return false;
				
				case "undefined":
				case "unknown":
					dump.value = 'unknown';
				
				case "boolean":
				case "number":
				case "string":
					dump.value = v;
			}
			
			if (v === null)
			{
				dump.type = 'null';
				dump.value = null;
			}
			else if (Object.isElement(v))
			{
				dump.type = 'Element';
				dump.value = 'N/A';
			}
			else if (dump.type == 'object')
			{
				var index = seen.indexOf(v);
				
				if(index != -1)
				{
					++index;
					dump.value = "(*recusion*)";
				}
				else
				{
					index = seen.push(v);
					
					dump.value = {};
					for (var property in v)
					{
						if (!Object.isUndefined(v[property]))
						{
							dump.value[property] = DFTools.var_dump(v[property],seen);
							
							if(!dump.value[property])
								delete dump.value[property];
						}
					}
				}
				
				dump.type += " (#" + index + ")";
			}
		}
		catch(ex)
		{
			dump = {type:'unknown',value:'Error!'};
		}
		
		return dump;
	},
	
	reportError: function(error,module,data)
	{
		var msg;
		
		try
		{
			msg = "Unknown Error";
			
			if(error.message) { msg = error.message; }
			
			DFTools.console.log("DFTools Error Report:",msg,error,module,data);
			
			var post = {
				errorMsg: msg,
				module: module ? module : 'Unknown',
				report: {
					browserName: BrowserDetect.name,
					browserOS: BrowserDetect.OS,
					browserVersion: BrowserDetect.version,
					location: window.location.toString()
				}
			};
			
			
			if(error.name) { post.errorName = error.name; }
			
			if(error.fileName) { post.fileName = error.fileName; }
			if(error.lineNumber) { post.lineNumber = error.lineNumber; }
			if(error.number) { post.errorNumber = error.number; }
			
			if(error.error_function) { post.report.errorFunction = error.error_function; }
			if(error.error_handler) { post.report.errorHandler = error.error_handler; }
			
			if(error.description) { post.report.errorDescription = error.description; }
			if(error.stack) { post.report.errorStack = error.Stack; }
			
			if(window.innerHeight) {
				post.report.windowHeight = window.innerHeight;
				post.report.windowWidth = window.innerWidth;
			}
			
			if(typeof(window.pageXOffset) != 'undefinied') {
				post.report.pageXOffset = window.pageXOffset;
				post.report.pageYOffset = window.pageYOffset;
			}
			
			if(document.referrer) {
				post.report.referrer = document.referrer;
			}
			
			if(data)
				post.report.moduleData = DFTools.var_dump(data);
			
			post.report.logs = DFTools.console.getAll();
			
			post.report.stack = $A(DFTools.getStackTrace()).join("\n");
			
			post.report = Object.toJSON(post.report);
			
			new Ajax.Request(DFTools.urls.base+DFTools.urls.reportError,{method:'post', parameters: post});
		}
		catch (error_error)
		{
			// Damn, we just can't win. Error when reporting an error!
			// Just give up and ignore this error.
			
			msg = "Unknown Error";
			
			if(error_error.message) { msg = error_error.message; }
			
			DFTools.console.log("Error while reporting en error!!",msg,error_error);
		}
	},
	
	/**
	 * Set a cookie.
	 *
	 * @url http://www.quirksmode.org/js/cookies.html
	 */
	setCookie: function(name,value,days)
	{
		if (days)
		{
			var date = new Date();
			date.setTime(date.getTime()+(days*24*60*60*1000));
			var expires = "; expires="+date.toGMTString();
		}
		else var expires = "";
		document.cookie = name+"="+value+expires+"; path=/";
	},
	
	/**
	 * Get the value of a cookie.
	 *
	 * @url http://www.quirksmode.org/js/cookies.html
	 * @return string
	 */
	getCookie: function(name)
	{
		var nameEQ = name + "=";
		var ca = document.cookie.split(';');
		for(var i=0;i < ca.length;i++) {
			var c = ca[i];
			while (c.charAt(0)==' ') c = c.substring(1,c.length);
			if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length,c.length);
		}
		return null;
	},
	
	/**
	 * Remove a cookie.
	 *
	 * @url http://www.quirksmode.org/js/cookies.html
	 */
	deleteCookie: function(name)
	{
		DFTools.setCookie(name,"",-1);
	},
	
	
	format: 
	{
		bytes: function(n)
		{
			var units = $A(['KB','MB','GB','TB']);
			var increment = 1024;
			
			var precision = 2;
			
			var unit = 'b';
			
			for(var i=0; i<units.length && n >= increment; ++i)
			{
				n /= increment;
				unit = units[i];
			}
			
			var mult = Math.pow(10,precision);
			
			n = Math.ceil(n*mult)/mult;
			
			return n+unit;
		}
	}
}

DFTools.cancelEvent = DFTools.cancelEvent.bindAsEventListener(DFTools);


/**
 * Page Unload System
 *
 * The DFTools Page Unload System is a way to detect is the page is being
 * unloaded (due to the user closing the window/tab, clicking back/forward,
 * or taking any other action to navigate away from the page). 
 *
 * DFTools.pageUnload can be queried to find out if the page is being unloaded
 * (I.E. to determine if that is the cause of a failure for an AJAX request to
 * complete) but it can also be used to observe the unload event and cancel it
 * by prompting the user.
 *
 * @author Joshua Gitlin
 * @copyright 2001 - 2009 Digital Fruition, LLC. All Rights Reserved.
 * @license Propritary Digital Fruition License.
 */
DFTools.pageUnload = new function()
{
	var isHappening = false;
	
	var okToUnload = true;
	
	var unloadWarningFunctions = $A();
	
	var unloadWarnings = $A();
	
	var resetHappeningFunction = function()
	{
		isHappening = false;
	}
	
	var resetHappening = new PeriodicalExecuter(resetHappeningFunction,1);
	
	var beforeunloadHandler = function()
	{
		isHappening = true;
		
		if(!this.okToUnload())
		{
			return unloadWarnings.join("\n\n");
		}
	}.bind(this);
	
	window.onbeforeunload = beforeunloadHandler;
	
	this.isHappening = function()
	{
		return isHappening;
	}
	
	this.okToUnload = function()
	{
		unloadWarnings = unloadWarningFunctions.collect(function(f){return f();}).without(false);
		
		okToUnload = !unloadWarnings.any();
		
		return okToUnload;
	}
	
	this.addUnloadWarning = function(f)
	{
		if(typeof(f) == 'function')
		{
			unloadWarningFunctions.push(f);
		}
	}
	
	this.removeUnloadWarning = function(f)
	{
		if(typeof(f) == 'function')
		{
			unloadWarningFunctions = unloadWarningFunctions.without(f);
		}
	}
}


/**
 * Digital Frution Admin UI Library
 *
 * This class needs documentation. Contact the developer for more information.
 *
 * @author Joshua Gitlin
 * @copyright 2001 - 2009 Digital Fruition, LLC. All Rights Reserved.
 * @license Propritary Digital Fruition License.
 */
var DF_UI = 
{
	MoveWindow: function (win,x,y)
	{
		var pos = win.getPosition();
		win.setPosition(pos[0]+x,pos[1]+y);
	}
}


var writeEmail = DFTools.writeEmail;


/**
 * Quirksmode Browser detection Library
 *
 * Provides browser detection capabilities.
 *
 * @author Peter-Paul Koch
 * @copyright 2009 Quirksmode. I don't believe in copyrights for JavaScript or CSS solutions.
 * @license Open: http://www.quirksmode.org/about/copyright.html
 * @see http://www.quirksmode.org/js/detect.html
 */
var BrowserDetect = {
	init: function () {
		this.browser = this.searchString(this.dataBrowser) || "An unknown browser";
		this.version = this.searchVersion(navigator.userAgent)
			|| this.searchVersion(navigator.appVersion)
			|| "an unknown version";
		this.OS = this.searchString(this.dataOS) || "an unknown OS";
	},
	searchString: function (data) {
		for (var i=0;i<data.length;i++)	{
			var dataString = data[i].string;
			var dataProp = data[i].prop;
			this.versionSearchString = data[i].versionSearch || data[i].identity;
			if (dataString) {
				if (dataString.indexOf(data[i].subString) != -1)
					return data[i].identity;
			}
			else if (dataProp)
				return data[i].identity;
		}
	},
	searchVersion: function (dataString) {
		var index = dataString.indexOf(this.versionSearchString);
		if (index == -1) return;
		return parseFloat(dataString.substring(index+this.versionSearchString.length+1));
	},
	dataBrowser: [
		{ 	string: navigator.userAgent,
			subString: "OmniWeb",
			versionSearch: "OmniWeb/",
			identity: "OmniWeb"
		},
		{
			string: navigator.vendor,
			subString: "Apple",
			identity: "Safari"
		},
		{
			prop: window.opera,
			identity: "Opera"
		},
		{
			string: navigator.vendor,
			subString: "iCab",
			identity: "iCab"
		},
		{
			string: navigator.vendor,
			subString: "KDE",
			identity: "Konqueror"
		},
		{
			string: navigator.userAgent,
			subString: "Firefox",
			identity: "Firefox"
		},
		{
			string: navigator.vendor,
			subString: "Camino",
			identity: "Camino"
		},
		{		// for newer Netscapes (6+)
			string: navigator.userAgent,
			subString: "Netscape",
			identity: "Netscape"
		},
		{
			string: navigator.userAgent,
			subString: "MSIE",
			identity: "Explorer",
			versionSearch: "MSIE"
		},
		{
			string: navigator.userAgent,
			subString: "Gecko",
			identity: "Mozilla",
			versionSearch: "rv"
		},
		{ 		// for older Netscapes (4-)
			string: navigator.userAgent,
			subString: "Mozilla",
			identity: "Netscape",
			versionSearch: "Mozilla"
		}
	],
	dataOS : [
		{
			string: navigator.platform,
			subString: "Win",
			identity: "Windows"
		},
		{
			string: navigator.platform,
			subString: "Mac",
			identity: "Mac"
		},
		{
			string: navigator.platform,
			subString: "Linux",
			identity: "Linux"
		}
	]

};
BrowserDetect.init();


/**
 * WebKit Version Detection Library
 *
 * provides version information about browsers using WebKit, including Apple's
 * Safari and Google's Chrome.
 *
 * @author Unknown
 * @copyright unknown
 * @license Open
 * @see http://trac.webkit.org/wiki/DetectingWebKit
 */
var WebKitDetect = {  };

// If the user agent is WebKit, returns true. Otherwise, returns false.
WebKitDetect.isWebKit = function isWebKit()
{
    return RegExp(" AppleWebKit/").test(navigator.userAgent);
}

// If the user agent is WebKit, returns an array of numbers matching the "." separated 
// fields in the WebKit version number, with an "isNightlyBuild" property specifying
// whether the user agent is a WebKit nightly build. Otherwise, returns null.
//
// Example: 418.10.1 => [ 418, 10, 1 ] isNightlyBuild: false
WebKitDetect.version = function version() 
{
    /* Some example strings: 
            Mozilla/5.0 (Macintosh; U; Intel Mac OS X; en) AppleWebKit/418.9.1 (KHTML, like Gecko) Safari/419.3
            Mozilla/5.0 (Macintosh; U; Intel Mac OS X; en) AppleWebKit/420+ (KHTML, like Gecko) Safari/521.32
     */
     
    // grab (AppleWebKit/)(xxx.x.x)
    var webKitFields = RegExp("( AppleWebKit/)([^ ]+)").exec(navigator.userAgent);
    if (!webKitFields || webKitFields.length < 3)
        return null;
    var versionString = webKitFields[2];

    var isNightlyBuild = versionString.indexOf("+") != -1;

    // Remove '+' or any other stray characters
    var invalidCharacter = RegExp("[^\\.0-9]").exec(versionString);
    if (invalidCharacter)
        versionString = versionString.slice(0, invalidCharacter.index);
    
    var version = versionString.split(".");
    version.isNightlyBuild = isNightlyBuild;
    return version;
}

// If the user agent is a WebKit version greater than or equal to the version specified
// in the string minimumString, returns true. Returns false otherwise. minimumString 
// defaults to "".
//
// Example usage: WebKitDetect.versionIsAtLeast("418.10.1")
WebKitDetect.versionIsAtLeast = function versionIsAtLeast(minimumString)
{
    function toIntOrZero(s) 
    {
        var toInt = parseInt(s);
        return isNaN(toInt) ? 0 : toInt;
    }

    if (minimumString === undefined)
        minimumString = "";
    
    var minimum = minimumString.split(".");
    var version = WebKitDetect.version();

    if (!version)
        return false;
        
    if (version.isNightlyBuild)
        return true;

    for (var i = 0; i < minimum.length; i++) {
        var versionField = toIntOrZero(version[i]);
        var minimumField = toIntOrZero(minimum[i]);
        
        if (versionField > minimumField)
            return true;
        if (versionField < minimumField)
            return false;
    }

    return true;
}

WebKitDetect.isMobile = function isMobile()
{
    return WebKitDetect.isWebKit() && RegExp(" Mobile/").test(navigator.userAgent);
}

WebKitDetect.mobileDevice = function mobileDevice()
{
    if (!WebKitDetect.isMobile())
        return null;
        
    var fields = RegExp("(Mozilla/5.0 \\()([^;]+)").exec(navigator.userAgent);
    if (!fields || fields.length < 3)
        return null;
    return fields[2];
}

// Example: 1C28 => [ 1, C, 28 ]
WebKitDetect._mobileVersion = function _mobileVersion(versionString)
{
    var fields = RegExp("([0-9]+)([A-Z]+)([0-9]+)").exec(versionString);
    if (!fields || fields.length != 4)
        return null;
    return [ fields[1], fields[2], fields[3] ];
}

WebKitDetect.mobileVersion = function mobileVersion()
{
    // grab (Mobile/)(nxnnn)
    var fields = RegExp("( Mobile/)([^ ]+)").exec(navigator.userAgent);
    if (!fields || fields.length < 3)
        return null;
    var versionString = fields[2];
    
    return WebKitDetect._mobileVersion(versionString);
}

WebKitDetect.mobileVersionIsAtLeast = function mobileVersionIsAtLeast(minimumString)
{
    function toIntOrZero(s) 
    {
        var toInt = parseInt(s);
        return isNaN(toInt) ? 0 : toInt;
    }

    if (minimumString === undefined)
        minimumString = "";

    var minimum = WebKitDetect._mobileVersion(minimumString);
    var version = WebKitDetect.mobileVersion();

    if (!version)
        return false;
        
    var majorVersInt = toIntOrZero(version[0]);
    var majorMinInt = toIntOrZero(minimum[0]);
    if (majorVersInt > majorMinInt)
        return true;
    if (majorVersInt < majorMinInt)
        return false;
    
    var majorVersAlpha = version[1];
    var majorMinAlpha = minimum[1];
    if (majorVersAlpha > majorMinAlpha)
        return true;
    if (majorVersAlpha < majorMinAlpha)
        return false;
    
    var minorVersInt = toIntOrZero(version[2]);
    var minorMinInt = toIntOrZero(minimum[2]);
    if (minorVersInt > minorMinInt)
        return true;
    if (minorVersInt < minorMinInt)
        return false;
    
    return true;
}


/**
 * Digital Frution Stack Trace
 *
 * Returns a Javascript function call stack trace. Taken from the web and
 * adapted for our needs. License unknown.
 *
 * @return array
 * @author Unknown
 * @copyright Unknown
 * @license Unknown
 */
DFTools.getStackTrace = (function ()
{
	var mode;
	/*
	try {(0)()} catch (e) {
		mode = e.stack ? 'Firefox' : window.opera ? 'Opera' : 'Other';
	}
	*/
	
	mode = BrowserDetect.browser;
	
	switch (mode)
	{
		case 'Firefox' : return function ()
			{
				try {(0)()} catch (e)
				{
					return e.stack.replace(/^.*?\n/,'').replace(/(?:\n@:0)?\s+$/m,'').replace(/^\(/gm,'{anonymous}(').split("\n");
				}
			};
		
		case 'Opera' : return function () 
			{
				try {(0)()} catch (e)
				{
					var lines = e.message.split("\n"),ANON = '{anonymous}',lineRE = /Line\s+(\d+).*?in\s+(http\S+)(?:.*?in\s+function\s+(\S+))?/i,i,j,len;
					
					for (i=4,j=0,len=lines.length; i<len; i+=2)
					{
						if (lineRE.test(lines[i]))
						{
							lines[j++] = (RegExp.$3 ? RegExp.$3 + '()@' + RegExp.$2 + RegExp.$1 : ANON + RegExp.$2 + ':' + RegExp.$1) + ' -- ' + lines[i+1].replace(/^\s+/,'');
						}
					}
					
					lines.splice(j,lines.length-j);
					return lines;
				}
			};
		
		default : return function ()
			{
				var curr = arguments.callee.caller, FUNC = 'function', ANON = "{anonymous}", fnRE = /function\s*([\w\-$]+)?\s*\(/i, stack = [],j=0,fn,args,i;
				
				while (curr)
				{
					fn    = fnRE.test(curr.toString()) ? RegExp.$1 || ANON : ANON;
					args  = stack.slice.call(curr.arguments);
					i     = args.length;
					
					while (i--)
					{
						switch (typeof args[i])
						{
							case 'string'  : args[i] = '"'+args[i].replace(new RegExp('"','g'),'\\"')+'"'; break;
							case 'function': args[i] = FUNC; break;
						}
					}
					
					stack[j++] = fn + '(' + args.join() + ')';
					curr = curr.caller;
				}
				
				return stack;
			};
	}
})();






/*
* FastInit: system for observing when the DOM is ready (but images have not yet loaded)
*
* @copyright Copyright (c) 2007 Andrew Tetlaw
*/
var FastInit = {
	onload : function() {
		if (FastInit.done) { return; }
		FastInit.done = true;
		for(var x = 0, al = FastInit.f.length; x < al; x++) {
			FastInit.f[x]();
		}
	},
	addOnLoad : function() {
		var a = arguments;
		for(var x = 0, al = a.length; x < al; x++) {
			if(typeof a[x] === 'function') {
				if (FastInit.done ) {
					a[x]();
				} else {
					FastInit.f.push(a[x]);
				}
			}
		}
	},
	listen : function() {
		if (/WebKit|khtml/i.test(navigator.userAgent)) {
			FastInit.timer = setInterval(function() {
				if (/loaded|complete/.test(document.readyState)) {
					clearInterval(FastInit.timer);
					delete FastInit.timer;
					FastInit.onload();
				}}, 10);
		} else if (document.addEventListener) {
			document.addEventListener('DOMContentLoaded', FastInit.onload, false);
		} else if(!FastInit.iew32) {
			if(window.addEventListener) {
				window.addEventListener('load', FastInit.onload, false);
			} else if (window.attachEvent) {
				return window.attachEvent('onload', FastInit.onload);
			}
		}
	},
	f:[],done:false,timer:null,iew32:false
};
/*@cc_on @*/
/*@if (@_win32)
FastInit.iew32 = true;
document.write('<script id="__ie_onload" defer src="' + ((location.protocol == 'https:') ? '//0' : 'javascript:void(0)') + '"><\/script>');
document.getElementById('__ie_onload').onreadystatechange = function(){if (this.readyState == 'complete') { FastInit.onload(); }};
/*@end @*/
FastInit.listen();



//+ Jonas Raoni Soares Silva
//@ http://jsfromhell.com/string/pad [v1.0]

String.prototype.pad = function(l, s, t){
	return s || (s = " "), (l -= this.length) > 0 ? (s = new Array(Math.ceil(l / s.length)
		+ 1).join(s)).substr(0, t = !t ? l : t == 1 ? 0 : Math.ceil(l / 2))
		+ this + s.substr(0, l - t) : this;
};



/* Date/Time Format v0.2; MIT-style license
By Steven Levithan <http://stevenlevithan.com> */

/**
 * Date/Time Format Library
 *
 * Excellent date/time formatting library by Steven Levithan
 *
 * @author Joshua Gitlin
 * @copyright 2009 Steven Levithan <http://stevenlevithan.com>
 * @see http://stevenlevithan.com
 * @version v0.2
 * @license MIT-style license.
 */
Date.prototype.format = function(mask) {
	var d = this; // Needed for the replace() closure
	
	// If preferred, zeroise() can be moved out of the format() method for performance and reuse purposes
	var zeroize = function (value, length) {
		if (!length) length = 2;
		value = String(value);
		for (var i = 0, zeros = ''; i < (length - value.length); i++) {
			zeros += '0';
		}
		return zeros + value;
	};
	
	return mask.replace(/"[^"]*"|'[^']*'|\b(?:d{1,4}|m{1,4}|yy(?:yy)?|([hHMs])\1?|TT|tt|[lL])\b/g, function($0) {
		switch($0) {
			case 'd':	return d.getDate();
			case 'dd':	return zeroize(d.getDate());
			case 'ddd':	return ['Sun','Mon','Tue','Wed','Thr','Fri','Sat'][d.getDay()];
			case 'dddd':	return ['Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturday'][d.getDay()];
			case 'm':	return d.getMonth() + 1;
			case 'mm':	return zeroize(d.getMonth() + 1);
			case 'mmm':	return ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'][d.getMonth()];
			case 'mmmm':	return ['January','February','March','April','May','June','July','August','September','October','November','December'][d.getMonth()];
			case 'yy':	return String(d.getFullYear()).substr(2);
			case 'yyyy':	return d.getFullYear();
			case 'h':	return d.getHours() % 12 || 12;
			case 'hh':	return zeroize(d.getHours() % 12 || 12);
			case 'H':	return d.getHours();
			case 'HH':	return zeroize(d.getHours());
			case 'M':	return d.getMinutes();
			case 'MM':	return zeroize(d.getMinutes());
			case 's':	return d.getSeconds();
			case 'ss':	return zeroize(d.getSeconds());
			case 'l':	return zeroize(d.getMilliseconds(), 3);
			case 'L':	var m = d.getMilliseconds();
					if (m > 99) m = Math.round(m / 10);
					return zeroize(m);
			case 'tt':	return d.getHours() < 12 ? 'am' : 'pm';
			case 'TT':	return d.getHours() < 12 ? 'AM' : 'PM';
			// Return quoted strings with the surrounding quotes removed
			default:	return $0.substr(1, $0.length - 2);
		}
	});
};



/* Digital Fruition Standard Library____________________________________________ */

DFTools.namespace("com.digitalfruition.Std");


com.digitalfruition.Std.LoadedObserver = Class.create(Abstract.TimedObserver,
{	
	getValue: function()
	{
		var loaded = false;
		
		if(typeof this.element.loaded == 'function')
			loaded = this.element.loaded();
		else if(typeof this.element.loaded != 'undefined')
			loaded = this.element.loaded
		if(typeof this.element.complete != 'undefined')
			loaded = this.element.complete
		
		return loaded;
	}
});


com.digitalfruition.Std.HtmlTag = Class.create(
{
	initialize: function(tag,attrs,children)
	{
		var attrs = $H(typeof(attrs) == 'object' ? attrs : {});
		
		if(typeof(children) == 'undefined') {
			children = $A([]);
		} else if(children instanceof Array) {
			children = $(children);
		} else {
			children = $A([children]);
		}
		
		this.tag = tag;
		this.attrs = attrs,
		this.childTags = children.collect(com.digitalfruition.Std.HtmlTag.createFromConfig);
	},
	
	toHTML: function()
	{
		var html = '<'+this.tag;
		
		if(this.attrs && Object.isHash(this.attrs))
		{
			html = this.attrs.inject(html,function(acc,pair){
				return acc + ' ' + pair[0] + '="' + pair[1].escapeHTML().gsub(/"/,'&quot;') + '"';
			});
		}
		
		html += '>';
		
		if(this.childTags.length) {
			html = html + this.childTags.join('') + '</'+this.tag + '>'
		}
		
		return html;
	},
	
	toElement: function()
	{
		var el = new Element(this.tag, this.attrs.toObject());
		
		this.childTags.each(Element.insert.curry(el));
		
		return el;
	}
});

com.digitalfruition.Std.HtmlTag.prototype.toString = com.digitalfruition.Std.HtmlTag.prototype.toHTML;

com.digitalfruition.Std.HtmlTag.createFromConfig = function(config)
{
	if(Object.isHash(config)) {
		config = config.toObject();
	}
	
	if(typeof(config) == 'string') {
		return config;
	} else if(typeof(config) == 'object') {
		return new com.digitalfruition.Std.HtmlTag(config.tag,config.attrs,config.children);
	}
};


/**
 * Standard Event Listener Functions
 *
 * This library is meant to be added to a class via Class.addmethods and it
 * provides the class with standard event handling capabilities. Once added to a
 * class the class will then be able to listen for events, fire and respond to
 * events, and stop listening for events.
 */
com.digitalfruition.Std.EventListener = 
{
	/**
	 * Add an event listener
	 * 
	 * Adds handler as an event listener for the event eventName. When eventName is
	 * fired handler will be called in the context of this object with the event
	 * name and any other parameters passed to fire() as it's parameters.
	 * 
	 * This function is aliased as: observe()
	 * 
	 * To remove the event listener, call removeListener() and pass the same event
	 * name and handler.
	 * 
	 * @param string eventName
	 * @param function handler
	 */
	addListener: function(eventName,handler)
	{
		if(typeof(eventName) != 'string')
		{
			throw new TypeError("eventName must be a string");
		}
		
		if(typeof(handler) != 'function')
		{
			throw new TypeError("handler must be a function");
		}
		
		if(!this._myHandlers)
		{
			this._myHandlers = $H({});
		}
		
		if(!this._myHandlers.get(eventName))
		{
			this._myHandlers.set(eventName,$A([]))
		}
		
		this._myHandlers.get(eventName).push(handler);
		
		return this;
	},
	
	/**
	 * Remove an event listener
	 * 
	 * Removes handler as an event listener for the event eventName. Pass the same
	 * handler and eventName as were passed to addListener to remove that handler.
	 * 
	 * This function is aliased as: stopObserving()
	 * 
	 * @param string eventName
	 * @param function handler
	 */
	removeListener: function(eventName,handler)
	{
		if(typeof(eventName) != 'string')
		{
			throw new TypeError("eventName must be a string");
		}
		
		if(typeof(handler) != 'function')
		{
			throw new TypeError("handler must be a function");
		}
		
		if(!this._myHandlers)
		{
			this._myHandlers = $H({});
		}
		
		if(!this._myHandlers.get(eventName))
		{
			this._myHandlers.set(eventName,$A([]))
		}
		
		this._myHandlers.set(eventName,this._myHandlers.get(eventName).without(handler));
		
		return this;
	},
	
	/**
	 * Fire an event
	 * 
	 * Fire the event eventName, causing all handlers for that event to be called.
	 * Each handler will be called with this object as it's context and the handler
	 * will receive the same arguments passed to this function, so the first
	 * argument will be the event name, and any subsequent arguments passed to fire
	 * will also be passed to each handler.
	 * 
	 * @param string eventName
	 */
	fire: function(eventName)
	{
		if(typeof(eventName) != 'string')
		{
			throw new TypeError("eventName must be a string");
		}
		
		if(this._myHandlers && !this.suspendEvents)
		{
			var events = this._myHandlers.get(eventName);
			
			if(Object.isArray(events))
			{
				events.invoke('apply',this,arguments)
			}
		}
		
		return this;
	}
};
com.digitalfruition.Std.EventListener.observe = com.digitalfruition.Std.EventListener.addListener;
com.digitalfruition.Std.EventListener.stopObserving = com.digitalfruition.Std.EventListener.removeListener;


/* Framework Classes____________________________________________________________ */

DFTools.namespace("com.digitalfruition.Framework");

/**
 * Base class representing "media": Images, PDFs, Flash, really anything that 
 * can be displayed.
 *
 */
com.digitalfruition.Framework.Media = Class.create(
{
	initialize: function(config)
	{
		$H(config).each(function(pair)
		{
			this[pair.key] = pair.value;
		},this);
		
		this.myHtmlTag = false;
		this.myElement = false;
	},
	
	refresh: function()
	{
		this.myBuildHtmlTag();
		
		if(this.myElement) {
			this.myElement = this.myHtmlTag.toElement();
		}
	},
	
	toElement: function()
	{
		if(!this.myHtmlTag) {
			this.myBuildHtmlTag();
		}
		
		var el = this.myHtmlTag.toElement();
		
		return el;
	},
	
	toHTML: function()
	{
		if(!this.myHtmlTag) {
			this.myBuildHtmlTag();
		}
		
		return this.myHtmlTag.toString();
	},
	
	element: function()
	{
		if(!this.myElement) {
			this.myElement = this.toElement();
		}
		
		return this.myElement;
	},
	
	myBuildHtmlTag: function()
	{
		var attrs = $H({
			href: this.src
		});
		
		if(this.className) {
			attrs.set('class',this.className);
		}
		
		if(this.title) {
			attrs.set('title',this.title);
		}
			
		
		this.myHtmlTag = new com.digitalfruition.Std.HtmlTag('a', attrs, [this.title]);
	}
});

com.digitalfruition.Framework.Media.prototype.toString = com.digitalfruition.Framework.Media.prototype.toHTML;


/**
 * Class representing an image (which means it has a width and height and
 * can be scaled)
 *
 */
com.digitalfruition.Framework.Media.Image = Class.create(com.digitalfruition.Framework.Media,
{
	initialize: function($super,options)
	{
		this.defaultWidth  = this.defaultHeight = 0;
		
		$super(options);
	},
	
	srcForSize: function(width,height)
	{
		var src = this.src;
		
		if(typeof(width) == 'undefined')
		{
			width = this.defaultWidth;
			height = this.defaultHeight;
		}
		
		return src.replace(/([0-9]+)x([0-9]+)/,parseInt(width)+'x'+parseInt(height));
	},
	
	myBuildHtmlTag: function()
	{
		var tagname = 'img', children = $A();
		
		var attrs = $H({
			src: this.srcForSize()
		});
		
		if(this.className) {
			attrs.set('class',this.className);
		}
		
		if(this.title) {
			attrs.set('title',this.title);
		}
		
		if(this.link)
		{
			children.push({
				tag: tagname,
				attrs:attrs.toObject()
			});
			
			attrs = $H({
				href: this.link
			});;
			
			tagname = 'a';
		}
		
		this.myHtmlTag = new com.digitalfruition.Std.HtmlTag(tagname, attrs, children);
	}
});



/* Media Classes________________________________________________________________ */


if(com.digitalfruition.Framework && com.digitalfruition.Framework.Media)
{
	// For Framework sites, the media objects are all Framework media objects.
	// For non framework sites com.digitalfruition.Framework would be defined and
	// the media ojects would still work just without their ability to auto scale
	// and other magical features.
	
	com.digitalfruition.Media = com.digitalfruition.Framework.Media;
}
else
{
	/**
	 * Base class representing "media": Images, PDFs, Flash, really anything that 
	 * can be displayed.
	 *
	 */
	com.digitalfruition.Media = Class.create(
	{
		initialize: function(config)
		{
			$H(config).each(function(pair)
			{
				this[pair.key] = pair.value;
			},this);
		}	
	});
	
	
	/**
	 * Class representing an image (which means it has a width and height and
	 * can be scaled)
	 *
	 */
	com.digitalfruition.Media.Image = Class.create(com.digitalfruition.Media,
	{
	
	});
}

/**
 * Class representing a stream of Media objects. Extend this class and provie
 * means to load the stream.
 *
 */
com.digitalfruition.Media.MediaStream = Class.create();

com.digitalfruition.Media.MediaStream.addMethods(Enumerable);
com.digitalfruition.Media.MediaStream.addMethods(com.digitalfruition.Std.EventListener);
com.digitalfruition.Media.MediaStream.addMethods(
{
	initialize: function(config)
	{
		this.media = $A();
		
		this.myPointer = 0;
		
		this.fire('initialize');
	},
	
	getAt: function(index)
	{
		if(index < 0 || index >= this.media.length)
		{
			throw new RangeError("Index out of bounds");
		}
		
		return this.media[index];
	},
	
	prev: function()
	{
		if(this.myPointer <= 0)
		{
			throw new RangeError("End of Stream Reached");
		}
		
		return this.media[this.myPointer--];
	},
	
	next: function()
	{
		if(this.myPointer >= this.media.length)
		{
			this.myPointer = 0;
			throw new RangeError("End of Stream Reached");
		}
		
		return this.media[this.myPointer++];
	},
	
	add: function(media)
	{
		if(media instanceof com.digitalfruition.Media)
		{
			this.media.push(media);
			
			this.fire('add',media);
		}
	},
	
	length: function()
	{
		return this.media.length;
	},
	
	_each: function(iterator)
	{
		return this.media._each(iterator);
	}
});
	

/**
 * Media Stream served by RSS
 *
 */	
com.digitalfruition.Media.MediaStream.RSSMediaStream = Class.create(com.digitalfruition.Media.MediaStream,
{
	initialize: function(config)
	{
		this.url = config.url;
		
		this.method = config.method ? config.method : 'get';
		
		this.parameters = config.parameters ? config.parameters : '';
		
		
		this.myItemTagNames = ['item'];
		
		if(config.itemTags)
		{
			if(config.itemTags instanceof Array)
			{
				this.myItemTagNames = config.itemTags;
			}
			else if(typeof(config.itemTags) == 'string')
			{
				this.myItemTagNames = [config.itemTags];
			}
			else
			{
				this.myItemTagNames = config.itemTags;
			}
		}
		
		this.myItemTagNames = $A(this.myItemTagNames);
		
		this.myNamespacePrefix = config.namespace ? config.namespace+':' : '';
		
		this.myLoaded = false;
		
		this.media = $A();
		
		this.myPointer = 0;
		
		this.fire('initialize',config);
		
		if(config.autoLoad) { this.load(); }
	},
	
	load: function()
	{
		DFTools.console.debug("RSSMediaStream: load() called, requesting AJAX content",this);
		
		this.fire('beforeLoad');
		
		new Ajax.Request(this.url,{
			method: this.method,
			parameters: this.parameters,
			onSuccess: this.myHandleLoadAjaxSuccess.bind(this),
			onFailure: this.myHandleLoadAjaxFailure.bind(this)
		})
	},
	
	loaded: function()
	{
		return this.myLoaded;
	},
	
	myHandleLoadAjaxSuccess: function(transport)
	{
		try
		{
			DFTools.console.debug("RSSMediaStream: AJAX call successful, extracting media from RSS",this);
			
			this.media = this.myExtractMediaFromRSS(transport.responseXML);
			
			this.myLoaded = true;
			
			this.fire('load',this.media);
		}
		catch(e)
		{
			DFTools.console.debug("RSSMediaStream: myHandleLoadAjaxSuccess caught an exception",e,this);
			
			this.myHandleLoadAjaxFailure(transport,e);
		}
	},
	
	myHandleLoadAjaxFailure: function(transport,err)
	{
		DFTools.console.log("RSSMediaStream: Ajax Loading Failure. Transport: ",transport,", this:",this);
		
		this.myLoaded = false;
		this.myLoadError = "Ajax error";
		
		this.media = $A();
		
		var report = {
			'url': this.url,
			'method': this.method,
			'parameters': this.parameters,
			myItemTagNames: this.myItemTagNames,
			myNamespacePrefix: this.myNamespacePrefix,
			myLoaded: this.myLoaded = false,
			media: this.media,
			myPointer: this.myPointer
		};
		
		if(err.error_function) { report.errorFunction = e.error_function; }
		if(err.error_handler) { report.errorHandler = e.error_handler; }
		
		DFTools.reportError(err,com.digitalfruition.Media.MediaStream.RSSMediaStream,report);
		
		this.fire('loadError');
	},
	
	myExtractMediaFromRSS: function(rss)
	{
		DFTools.console.debug("RSSMediaStream: myExtractMediaFromRSS called on ",this,"with",rss);
		
		var media = $A();
		
		var items = this.myGetRSSItems(rss);
		
		if(items.length)
		{
			DFTools.console.debug("RSSMediaStream: items.length=",items.length);
			
			items.each(function(item)
			{
				var item_images = item.getElementsByTagName(this.myNamespacePrefix+"image");
				if(item_images)
				{
					DFTools.console.debug("RSSMediaStream: found ",item_images.length," image tags! Extracting URLs...");
					media.push($A(item_images).collect(this.myCollectItemImages,this));
				}
			},this);
		}
		
		media = media.flatten();
		
		DFTools.console.debug("RSSMediaStream: myExtractMediaFromRSS found the following media: ",media);
		
		return media;
	},
	
	myGetRSSItems: function(rss)
	{
		DFTools.console.debug("RSSMediaStream: myGetRSSItems called on ",this,"with object",rss," of type",typeof(rss));
		
		items = $A();
		
		if(true || rss && typeof(rss.getElementsByTagName) == 'function')
		{
			this.myItemTagNames.each(function(tagName)
			{
				items.push.apply(items,$A(rss.getElementsByTagName(this.myNamespacePrefix+tagName)));
			},this);
		}
		else
		{
			try
			{
				DFTools.console.debug("RSSMediaStream: myGetRSSItems typeof(rss.getElementsByTagName) != 'function'!");
				DFTools.console.debug("RSSMediaStream: typeof(rss)=",typeof(rss));
				DFTools.console.debug("RSSMediaStream: typeof(rss.getElementsByTagName)=",typeof(rss.getElementsByTagName));
				DFTools.console.debug("RSSMediaStream: Object.inspect(rss)=",Object.inspect(rss));
			}
			catch(e) {}
			
			DFTools.console.log("RSSMediaStream: myGetRSSItems: RSS object doesn't support getElementsByTagName; myGetRSSItems cannot continue.");
		}
		
		
		return items;
	},
	
	myCollectItemImages: function(image)
	{
		var media_object = {src: '', title: '', link: ''};
		
		try { media_object.src = image.getElementsByTagName(this.myNamespacePrefix+"url")[0].firstChild.nodeValue; } catch(e) {}
		try { media_object.link = image.getElementsByTagName(this.myNamespacePrefix+"link")[0].firstChild.nodeValue; } catch(e) {}
		try { media_object.title = image.getElementsByTagName(this.myNamespacePrefix+"title")[0].firstChild.nodeValue; } catch(e) {}
		
		DFTools.console.debug("RSSMediaStream: myCollectItemImages creating a new image from data: ",media_object);
		
		return new com.digitalfruition.Framework.Media.Image(media_object);
	}
});



/** Theme Objects_______________________________________________________________ */


com.digitalfruition.ThemeObject = Class.create(
{
	initialize: function(options)
	{
		com.digitalfruition.ThemeObject.instances.push(this);
		
		this.mySetOptions($H(options));
	},
	
	defaultOptions: function()
	{
		return $H({});
	},
	
	mySetOptions: function(options)
	{
		var defaults = this.defaultOptions();
		
		$H(defaults).each(function(v){
			this[v.key] = (typeof(options.get(v.key)) == 'undefined') ? v.value : options.get(v.key);
		},this);
	}
});
com.digitalfruition.ThemeObject.addMethods(com.digitalfruition.Std.EventListener);

com.digitalfruition.ThemeObject.instances = $A();


/**
 * Create a DFThemeObject from a hash-like object of options.
 *
 * This function will take the options hash (or object which can be converted
 * into a hash) and detremine the appropriate subclass of ThemeObject to create.
 *
 * @return com.digitalfruition.ThemeObject
 */
com.digitalfruition.ThemeObject.create = function(options)
{
	var c = options['class'];
	
	if (c && com.digitalfruition.ThemeObject[c])
	{
		c = com.digitalfruition.ThemeObject[c];
	}
	else
	{
		c = com.digitalfruition.ThemeObject;
	}
	
	return new c(options);
}

/**
 * Create a DFThemeObject from a hash-like object of options.
 *
 * This function will take the options hash (or object which can be converted
 * into a hash) and detremine the appropriate subclass of ThemeObject to create.
 *
 * @return com.digitalfruition.ThemeObject
 */
com.digitalfruition.ThemeObject.createFromForms = function(cssClass)
{
	var config, c, cssClassParts;
	
	cssClass = cssClass ? cssClass : 'DFThemeObject';
	
	return $$('form.'+cssClass).each(function(form)
	{
		config = form.serialize(true);
		
		$H(config).each(function(pair)
		{
			if(typeof(pair.value) == 'string' && pair.value.isJSON())
			{
				config[pair.key] = pair.value.evalJSON();
			}
		});
		
		c = com.digitalfruition.ThemeObject;
		
		cssClassParts = $w(form.className).without(cssClass);
		
		while (cssClassParts.length && c[cssClassParts[0]])
		{
			c = c[cssClassParts.shift()];
		}
		
		if(typeof(c) != 'function')
		{
			c = com.digitalfruition.ThemeObject;
		}
		
		return new c(config);
	}).length
}




/**
 * Breadcrumb: Shows a stack of links, to indicate steps a user has taken.
 *
 * The Breadcrumb theme object can be used to maintain, using JavaScript, an
 * ordered list (technically a stack) of links which a website user has taken.
 * Links can be push()ed onto the stack and they will be appended in the given
 * style.
 */
com.digitalfruition.ThemeObject.Breadcrumb = Class.create(com.digitalfruition.ThemeObject,
{
	initialize: function($super,options)
	{
		options = options ? options : {};
		
		$super(options);
		
		this.crumbs = $A();
		
		this.container = $(options['container']);
		
		if(!this.container) {
			this.container = new Element('div');
		}
	},
	
	defaultOptions: function()
	{
		return $H(
		{
			separator:	'<span class="sep">&nbsp;&gt;&nbsp;</sep>'
		});
	},
	
	/**
	 * Convert a single crumb (link) of a breadcumb to a DOM Element
	 *
	 * @access private
	 * @return Element
	 */
	crumbToElement: function(crumb)
	{
		var el;
		
		if(typeof(crumb[1]) == 'string')
		{
			el = new Element('a',{href:crumb[1]}).update(crumb[0]);
		}
		else
		{
			el = new Element('a',{href:'#'}).update(crumb[0]);
			
			if(typeof(crumb[1]) == 'function')
			{
				el.observe('click',crumb[1]);
			}
		}
		
		return el;
	},
	
	/**
	 * Append a given crumb element (created via crumbToElement) to
	 * the breadcrumb comtainer.
	 *
	 * @access private
	 * @return void
	 */
	appendCrumbElementToContainer: function(crumbEl)
	{
		if(this.crumbs.length)
		{
			this.container.insert(this.separator);
		}
		this.container.insert(crumbEl);
	},
	
	/**
	 * Append a new crumb (link) onto this breadcrumb. Pass the text as well as the
	 * URL or JavaScript function for the link
	 *
	 * @param string text
	 * @param string/function action
	 * @return void
	 */
	push: function(text,action)
	{
		var crumb = [text,action];
		
		var crumbEl = this.crumbToElement(crumb);
		this.appendCrumbElementToContainer(crumbEl);
		
		this.crumbs.push(crumb);
	},
	
	/**
	 * Refresh the breadcrumb
	 *
	 * @return void
	 */
	refresh: function()
	{
		this.container.update('');
		var crumbEls = this.crumbs.collect(this.crumbToElement,this);
		
		if(crumbEls.length)
		{
			this.container.insert(crumbEls.shift());
			crumbEls.each(this.appendCrumbElementToContainer,this);
		}
	},
	
	/**
	 * Move the Breadcrumb to a new container.
	 *
	 * @param Element element
	 * @return void
	 */
	applyTo: function(element)
	{
		if(element = $(element))
		{
			this.container.update('');
			this.container = element;
			this.refresh();
		}
	}
});



/**
 * Fixed Div Theme Object
 *
 * Theme Object for creating a <div> which has CSS position fixed cross browser.
 */
com.digitalfruition.ThemeObject.FixedDiv = Class.create(com.digitalfruition.ThemeObject,
{
	initialize: function($super,options)
	{
		$super(options);
		
		this.div = false;
		
		this.pos = $H({});
		
		this._createDiv();
		
		this._mySetupObservers();
	},
	
	defaultOptions: function()
	{
		return $H(
		{
			cssClass:		'DFFixed',
			innerHTML:		'&nbsp;',
			bodyHack:		true
		});
	},
	
	_createDiv: function()
	{
		var html,className, body = $(document.body);
		
		className = this.cssClass;
		html = '<div class="'+this.cssClass+'">'+this.innerHTML+'</div>';
		new Insertion.Top(body,html);
		this.div = body.down();
		this._setDivStyle(this.div);
	},
	
	_setDivStyle: function(div)
	{
		var style = $H({position: 'fixed'});
		
		if(BrowserDetect.browser == "Explorer" && parseInt(BrowserDetect.version) < 7)
			style.position = 'absolute';
		
		div.setStyle(style);
	},
	
	setPos: function(side,px)
	{
		var style = $H({});
		
		if(BrowserDetect.browser == "Explorer" && parseInt(BrowserDetect.version) < 7)
		{
			if(this.bodyHack)
			{
				style[side] = px+'px';
				this.div.setStyle(style);
			}
			else
			{
				/*switch(side)
				{
					case 'top': style[side] = 'expression( ('+px+' + (tempVar=document.documentElement.scrollTop?document.documentElement.scrollTop:document.body.scrollTop))+\'px\' )'; break;
					case 'right': style[side] = px+'px'; break;
					case 'bottom': style[side] = 'expression( ('+px+' + (tempVar=document.documentElement.scrollTop?document.documentElement.scrollTop:document.body.scrollTop))+\'px\' )'; break;
					case 'left': style[side] = px+'px'; break;
				}*/
				
				switch(side)
				{
					case 'top': this.div.style.setExpression(side, '('+px+' + (tempVar=document.documentElement.scrollTop?document.documentElement.scrollTop:document.body.scrollTop))+\'px\''); break;
					case 'right': style[side] = px+'px'; break;
					case 'bottom': this.div.style.setExpression('top', "__DFThemeObject__FixedDiv_setBottom(" + px + ',' + this.div.uniqueID + ")"); break;
					case 'left': style[side] = px+'px'; break;
				}
				
				
				if(style.size())
					this.div.setStyle(style);
				
				//alert(side + ' style: ' + this.div.style[side]);
				
				
				//Event.observe(window,'scroll',function(){alert('scroll');this.setPos(side,px);}.bindAsEventListener(this));
			}
		}
		else
		{
			style[side] = px+'px';
			this.div.setStyle(style);
		}
	},
	
	_mySetupObservers: function()
	{
		window.resize()
	}
});
	
function __DFThemeObject__FixedDiv_setBottom(dist,obj)
{
	return dist - obj.offsetHeight + ( document.documentElement.clientHeight ? document.documentElement.clientHeight : document.body.clientHeight ) 
			+ ( ignoreMe = document.documentElement.scrollTop ? document.documentElement.scrollTop : document.body.scrollTop );
}

com.digitalfruition.ThemeObject.LoadingIndicator = Class.create(com.digitalfruition.ThemeObject,
{
	initialize: function($super,options)
	{
		$super(options);
		
		this._myElements = {
			loading: false,
			container: false
		};
		
		this._mySetupElements();
	},
	
	defaultOptions: function()
	{
		return $H({
			cssClass:		'',
			imageSrc:		'/images/loading_01.gif',
			el:				false,
			bgcolor:		false,
			text:			'',
			center: 		true,
			reposInterval:	0.5
		});
	},
	
	show: function()
	{
		this.repos();
		this._myElements.container.show();
		this._myPE = new PeriodicalExecuter(this.repos.bind(this),this.reposInterval);
	},
	
	repos: function()
	{
		try
		{
			this._myElements.container.clonePosition(this.el);
		}
		catch(e) {}
	},
	
	hide: function()
	{
		this._myElements.container.hide();
		if(this._myPE)
			this._myPE.stop();
	},
	
	setText: function(text)
	{
		this.text = text;
		this._myElements.loading.down('p').update(this.text);
	},
	
	_mySetupElements: function()
	{
		this._myElements.loading = new Element('div');
		this._myElements.loading.addClassName('loading');
		
		
		this._myElements.loading.appendChild(new Element('img',{src: this.imageSrc}).wrap('div'));
		
		var style = {};
		
		if(this.bgcolor)
			style.backgroundColor =  this.bgcolor;
		
		this._myElements.loading.down().setStyle(style);
		
		this._myElements.loading.down().appendChild(new Element('p').update(this.text));
		
		if(this.center)
		{
			var d = this._myElements.loading.down();
			d.remove();
			this._myElements.loading.update('<table><tr><td>&nbsp;</td></tr></table>');
			this._myElements.loading.down('td').appendChild(d);
			this._myElements.loading.down('table').setStyle({
				position: 'relative',
				top: '50%',
				margin: 'auto',
				border: 'none',
				borderCollapse: 'collapse'
			});
		}
		
		this._myElements.container = new Element('div');
		this._myElements.container.addClassName('DFThemeObject-LoadingIndicator '+this.cssClass);
		
		this._myElements.container.hide();
		
		document.body.appendChild(this._myElements.container);
		
		this._myElements.container.appendChild(this._myElements.loading);
		
		var background = this._myElements.container.getStyle('background-image');
		background = background.substr(4,background.length-5);
		this._maskPreload = new Image(1,1);
		this._maskPreload.src = background;
	}
});

/**
 * Popin Theme Object
 *
 * Theme Object for creating a <div> which pops in
 */
com.digitalfruition.ThemeObject.Popin = Class.create(com.digitalfruition.ThemeObject,
{
	initialize: function($super,options)
	{
		$super(options);
		
		this._myElements = {
			popin: false,
			mask: false
		};
		
		this.pos = $H({});
		
		/**
		 * Visibility of the popin.
		 *
		 * Values are:
		 *
		 *  * H - Hidden
		 *  * h - being hidden
		 *  * S - shown
		 *  * s - being shown
		 */
		this._myPopinStatus = 'H';
		
		
		// handler for links/events which close this popin:
		
		var closeHandler = function(e) {
			this.fire('close');
			this.hide();
			e.stop();
		}
		
		this._myCloseHandler = closeHandler.bindAsEventListener(this);
		
		
		this._myCreateDivs();
		
		var afterShow = function() {
			this._myPopinStatus = 'S';
			this._myStartAutoShrink();
			this.fire('show');
		};
		
		var afterHide = function() {
			this._myElements.container.hide();
			this._myPopinStatus = 'H';
			this.fire('hide');
		};
		
		this._afterShow = afterShow.bind(this);
		this._afterHide = afterHide.bind(this);
		
		this._mySetupObservers();
		
		if(this.draggable && this._myElements.close)
		{
			new Draggable(this._myElements.popinContainer,{handle:this._myElements.close});
		}
		
		DFTools.console.debug("New com.digitalfruition.ThemeObject.Popin set up completed",this);
	},
	
	defaultOptions: function()
	{
		return $H(
		{
			cssClass:		'',
			innerHTML:		'&nbsp;',
			mask:			false,
			closeLink:		false,
			closeLinkText:	'Close',
			showEffect:		'BlindDown',
			hideEffect:		'BlindUp',
			showDuration:	1,
			hideDuration:	1,
			draggable:		false
		});
	},
	
	/**
	 * Is the popin visible?
	 *
	 * Returns true if the popin is visible
	 *
	 * @return boolean
	 */
	visible: function()
	{
		return this._myPopinStatus == 's' || this._myPopinStatus == 'S';
	},
	
	/**
	 * Show the popin (using an animation if configured to do so)
	 *
	 * @return void
	 */
	show: function()
	{
		if(this._myPopinStatus == 'h')
		{
			this._myCurrentEffect.cancel();
			this._myPopinStatus = 'H';
		}
		
		if(this._myPopinStatus == 'H')
		{
			this._myElements.container.show();
			
			this.fire('beforeShow');
			
			if(this._myElements.mask)
				this._myElements.mask.show();
			
			this._myStartAutoShrink()
			
			this._myPopinStatus = 's';
			
			new Effect[this.showEffect](this._myElements.popinContainer,{
				duration: this.showDuration,
				afterFinish: this._afterShow
			});
		}
	},
	
	/**
	 * Hide the popin (using an animation if configured to do so)
	 *
	 * @return void
	 */
	hide: function()
	{
		this.fire('beforeHide');
		
		if(this._myElements.mask)
			this._myElements.mask.hide.bind(this._myElements.mask).delay(this.hideDuration);
		
		this._myVisible = false;
		
		this._myCurrentEffect = new Effect[this.hideEffect](this._myElements.popinContainer,{
			duration: this.hideDuration,
			afterFinish: this._afterHide
		});
		
		this._myStopAutoShrink();
	},
	
	setContent: function(content)
	{
		this._myElements.popin.update(content);
	},
	
	element: function()
	{
		return this._myElements.popin;
	},
	
	setPos: function(side,px)
	{
		var style = {};
		
		if(BrowserDetect.browser == "Explorer" && parseInt(BrowserDetect.version) < 7)
		{
			if(this.bodyHack)
			{
				style[side] = px+'px';
				this._myElements.mask.setStyle(style);
			}
			else
			{
				switch(side)
				{
					case 'top':
						this._myElements.mask.style.setExpression(side, 
							'('+px+' + (tempVar=document.documentElement.scrollTop?document.documentElement.scrollTop:document.body.scrollTop))+\'px\'');
						break;
						
					case 'right':
						style[side] = px+'px';
						break;
					
					case 'bottom':
						this._myElements.mask.style.setExpression('top', "__DFThemeObject__FixedDiv_setBottom(" + px + ',' + this.div.uniqueID + ")");
						break;
					
					case 'left':
						style[side] = px+'px';
						break;
				}
				
				this._myElements.mask.setStyle(style);
			}
		}
		else
		{
			style[side] = px+'px';
			this._myElements.mask.setStyle(style);
		}
	},
	
	autoShrink: function(enabled)
	{
		this._myStopAutoShrink();
		
		this._autoShrink = enabled ? true : false;
		
		this._myStartAutoShrink()
	},
	
	shrinkToContent: function()
	{
		var restoreStyle = {},tempStyle={},sizeStyle = {};
		
		DFTools.console.debug("com.digitalfruition.ThemeObject.Popin.shrinkToContent() called on",this);
		
		switch(this._myPopinStatus)
		{
			case 'H':
				DFTools.console.debug("shrinkToContent temporarily resizing popin content");
				
				tempStyle = $H({
					'display':	BrowserDetect.browser == 'Explorer' ? 'inline' : 'inline-block'
				});
				
				restoreStyle.popin = $H();
				tempStyle.each(function(pair){
					var v = this._myElements.popin.getStyle(pair.key);
					if(v===null) v = 'auto'
					restoreStyle.popin.set(pair.key,v);
				},this);
				
				this._myElements.popin.setStyle(tempStyle.toObject());
				
				
				DFTools.console.debug("shrinkToContent temporarily showing popin container");
				
				tempStyle = $H({
					'left':		'-65534px',
					'top':		'-65534px',
					'display':	'block',
					'position':	'absolute'
				});
				
				restoreStyle.popinContainer = $H();
				tempStyle.each(function(pair){
					var v = this._myElements.popinContainer.getStyle(pair.key);
					if(v===null) v = 'auto'
					restoreStyle.popinContainer.set(pair.key,v);
				},this);
				
				this._myElements.popinContainer.setStyle(tempStyle.toObject());
				
				if(this.mask)
				{
					DFTools.console.debug("shrinkToContent temporarily showing mask container");
					
					tempStyle = $H({
						'left':		'-65534px',
						'top':		'-65534px',
						'bottom':	'auto',
						'right':	'auto',
						'height':	'1px',
						'width':	'1px',
						'display':	'block',
						'position':	'absolute'
					});
					
					restoreStyle.mask = $H();
					tempStyle.each(function(pair){
						var v = this._myElements.mask.getStyle(pair.key);
						if(v===null || (pair.key == 'height' || pair.key == 'width')) v = 'auto'
						restoreStyle.mask.set(pair.key,v);
					},this);
					
					this._myElements.mask.setStyle(tempStyle.toObject());
				}
				
				sizeStyle = this._myElements.popin.getDimensions();
				
				if(this.close)
				{
					sizeStyle.height += this._myElements.close.getHeight();
				}
				
				if(this.mask)
					this._myElements.mask.setStyle(restoreStyle.mask.toObject());
				
				this._myElements.popinContainer.setStyle(restoreStyle.popinContainer.toObject());
				this._myElements.popin.setStyle(restoreStyle.popin.toObject());
				break;
			
			case 'S':
				DFTools.console.debug("shrinkToContent temporarily resizing popin content");
				
				tempStyle = $H({
					'display':	BrowserDetect.browser == 'Explorer' ? 'inline' : 'inline-block'
				});
				
				restoreStyle.popin = $H();
				tempStyle.each(function(pair){
					var v = this._myElements.popin.getStyle(pair.key);
					if(v===null) v = 'auto'
					restoreStyle.popin.set(pair.key,v);
				},this);
				
				this._myElements.popin.setStyle(tempStyle.toObject());
				
				sizeStyle = this._myElements.popin.getDimensions();
				
				DFTools.console.debug("shrinkToContent: found popin dimensions to be ",sizeStyle);
				
				if(this.closeLink)
				{
					var closeHeight = this._myElements.close.getHeight();
					DFTools.console.debug("shrinkToContent: found height of close div to be ",closeHeight);
					sizeStyle.height += closeHeight;
				}
				
				this._myElements.popin.setStyle(restoreStyle.popin.toObject());
				break;
			
			case 's':
			case 'h':
				sizeStyle = this._myElements.popin.getDimensions('width');
				break;
		}
		 
		if(sizeStyle.width)		sizeStyle.width += 'px';
		if(sizeStyle.height)	sizeStyle.height += 'px';
		
		DFTools.console.debug("shrinkToContent setting style to",$H(sizeStyle).inspect());
		
		this._myElements.popinContainer.setStyle(sizeStyle);
	},
	
	_myCreateDivs: function()
	{
		var html,className, body = $(document.body);
		
		// the container for all of our elements:
		
		this._myElements.container = new Element('div').addClassName('DFThemeObject-Popin '+this.cssClass)
		this._myElements.container.hide();
		
		body.appendChild(this._myElements.container);
		
		// Build the popin container, this wraps the actual popin content
		// and contains any other elements we need to control (like the close div)
		
		this._myElements.popinContainer = new Element('div');
		this._myElements.popinContainer.setStyle({display:'block'});
		this._myElements.popinContainer.addClassName('container');
		this._myElements.popinContainer.hide();
		
		
		// Build the close link if requested
		
		if(this.closeLink)
		{
			this._myElements.close = new Element('div').addClassName('close');
			this._myElements.closeLink = new Element('a',{href:'#'}).update(this.closeLinkText);
			this._myElements.closeLink.observe('click',this._myCloseHandler);
			this._myElements.close.appendChild(this._myElements.closeLink);
			
			//this._myElements.close.setStyle({cursor: 'move'});
			
			this._myElements.popinContainer.appendChild(this._myElements.close);
		}
		
		// Build the actual popin
		
		this._myElements.popin = new Element('div');
		this._myElements.popin.addClassName('content');
		this._myElements.popinContainer.appendChild(this._myElements.popin);
		
		this.setContent(this.innerHTML);
		
		this._myElements.container.appendChild(this._myElements.popinContainer);
		
		// If masking is turned on, build the mask and install onto page
		
		if(this.mask)
		{
			this._myElements.mask = new Element('div');
			this._myElements.mask.addClassName('mask');
			
			this._myElements.mask.setStyle({
				position: 'fixed'
			});
			
			this._myElements.mask.hide();
			
			this._myElements.container.appendChild(this._myElements.mask);
		
			this._myFixMaskInIE6();
		}
	},
	
	_myStartAutoShrink: function()
	{
		if(this._autoShrink)
		{
			this.shrinkToContent();
			if(this.visible())
				this._myAutoshrinker = new PeriodicalExecuter(this.shrinkToContent.bind(this),0.5);
		}
	},
	
	_myStopAutoShrink: function()
	{
		if(this._myAutoshrinker)
			this._myAutoshrinker.stop();
	},
	
	_myFixMaskInIE6: function()
	{
		var bg = this._myElements.mask.getStyle('backgroundImage');
		
		if(bg && BrowserDetect.browser == 'Explorer' && BrowserDetect.version == 6)
		{
			this._myElements.mask.setStyle({
				filter:'progid:DXImageTransform.Microsoft.AlphaImageLoader(enabled=true, sizingMethod=scale, src=\''+bg+'\')',
				position: 'absolute'
			});
		}
	},
	
	_mySetupObservers: function()
	{
		//window.resize();
	}
});




FastInit.addOnLoad(DFTools.setupUrlBase);

