/*
 *	Api 0.75 - The Dealer.com API
 *	Copyright (C) 2007-2008 Dealer.com
 *
 *	Created:  Randall Morey, Web Developer (ddcrandallm)
*	Last Modified:  19 June 2008 (ddcrandallm)
 */

/* TODO:
 * 	- add JASH-based console for cross-browser debugging
 * 	- Rewrite the if-block in Api.get() as a switch
 * 	
 */
(function () {

	var window = this,
		$ = window.jQuery,
		DDC = (window.DDC = (window.DDC || {})),
		Api = {
			version: '0.75',
			/**
			 * Set to true if Api should log messages.
			 * @type {boolean}
			 */
			debugMode: false,
			/**
			 * Logs debug messages to the console.
			 * @param {string} msg Message to be logged
			 */
			debug: function (msg) {
				if (window.console && Api.debugMode) {
					console.log('[api] ' + msg);
				} else if (window.$ && Api.debugMode) {
					$(function () {
						if ($('#apiConsole').size() === 0) {
							$('body').append('<div id="apiConsole" />');
						}
						$('<p>[api] ' + msg + '</p>').appendTo('#apiConsole');
						$('#apiConsole')[0].scrollTop = $('#apiConsole')[0].scrollHeight;
					});
				}
			},
			/**
			 * Extends an object with one or more additional objects.
			 * If only one argument is passed, Api.extend extends Api itself.
			 * @param {object} Object to extend
			 * @param {object} Object with which to extend former
			 */
			extend: function () {
				var target = arguments[0] || {}, a = 1, al = arguments.length,
					prop,
					i;
				if (al === 1) {
					target = this;
					a = 0;
				}
				for (; a < al; a++) {
					if ((prop = arguments[a]) !== null) {
						for (i in prop) {
							if (!(target === prop[i])) {
								if ((typeof prop[i] === 'object') && target[i]) {
									Api.extend(target[i], prop[i]);
								} else if (prop[i] !== undefined) {
									target[i] = prop[i];
								}
							}
						}
					}
				}
				return target;
			},
			/**
			 * Gets a script or style sheet cross-domain.
			 * @param {string} type type of file to get (style or script)
			 * @param {string} url url of file to get
			 * @param {object} params object consisting of key/value pairs of url parameters
			 * @param {function} callback called upon successful completetion of file get
			 */
			get: function (type, url, params, success, esc) {
				type = type || 'script';
				esc = esc === undefined ? true : esc;
				url = (url || '') + Api.urlize(params || {}, null, null, null, esc);
				success = success || function () {};
				var el = document.createElement(type === 'script' ? type : 'link'),
					head = document.getElementsByTagName('head')[0],
					finished = false;
				
				if (type === 'script') {
					el.src = url;
					el.type = 'text/javascript';
					el.onload = el.onreadystatechange = function () {
						if (!finished && (!this.readyState || this.readyState === 'loaded' || 
						this.readyState === 'complete')) {
							finished = true;
							Api.debug('Api.get() - done loading ' + type + ': ' + url);
							head.removeChild(el);
							success();
						}
					};
				} else if (type === 'stylesheet') {
					el.href = url;
					el.type = 'text/css';
					el.rel = type;
				}
		
				head.appendChild(el);
				Api.debug('Api.get() - loading ' + type + ': ' + url);
			},
			/**
			 * Loads a style sheet.
			 * @param {string} url path to style sheet
			 */
			getStyle: function (url) {
				Api.get('stylesheet', url);
			},
			/**
			 * Loads a script cross-domain.
			 * @param {string} src path to script file (may be cross-domain)
			 * @param {object} object representation of URL parameters to pass to server
			 * @param {function} success called after successful script load
			 */
			getScript: function (url, params, success, esc) {
				Api.get('script', url, params, success, esc);
			},
			/**
			 * Concatenates arguments into a string using the fast Array.join() method.
			 * Depends on a magical conversion of the native array-like object 'arguments' into
			 * a true array with Array.prototype.slice.call().
			 * http://shifteleven.com/articles/2007/06/28/array-like-objects-in-javascript
			 * @param any number of string arguments
			 * 
			 * Use ['str1','str2'].join(''); string concatentation instead.
			 */
			//concat: function () { return Array.prototype.slice.call(arguments).join(''); },
			/**
			 * Converts an object consisting of key/value pairs to a URL parameter list.
			 * Currently Api.urlize() handles objects only one level deep.  Attempting to
			 * encode objects which contain other objects will result in bad URLs.
			 * {foo: 'bar', world: 'hello'} becomes '?foo=bar&world=hello'
			 * @param {object} object whose values are to be converted
			 * @param {string} optional x is appended to the beginning (default: '?')
			 * @param {string} optional y delimits key/value pairs (default: '&')
			 * @param {string} optional z delimits keys and values (default: '=')
			 * @return {string} string representation of object, in url paramaeter format
			 */
			urlize: function (arg, x, y, z, esc) {
				x = x || '?';	// parameter string prefix
				y = y || '&';	// parameter pair delimiter
				z = z || '=';	// key / value delimiter
				esc = esc === undefined ? true : esc;
				var c, i, l, s = '', v,
					escape = window.escape,
					urlencode = function (str) {
						return esc ? escape(str).replace(/\+/g, '%2B').replace(/\"/g, '%22').replace(/\'/g, '%27') : str;
					};
				switch (typeof arg) {
				case 'object':
					if (arg) {
						for (i in arg) {
							if (typeof (v = Api.urlize(arg[i])) !== 'function') {
								if (s) {
									s += y;
								}
								s += urlencode(Api.urlize(i)) + z + urlencode(v);
							}
						}
						return (s.length >= 1 ? x : '') + s;
					} else {
						return 'null';
					}
				case 'string':
					return arg;
				case 'number':
					return String(arg);
				default:
					return 'null';
				}
			}
		},
		/**
		 * DataAccessManager provides cross-domain AJAX (XJAX) with call concurrency and response caching.
		 */
		DataAccessManager = (function () {
			var callCache = {},	// cache of url keys / response values
				callbacks = {},	// internal array of callback functions
				callbacksPrefix  = 'fn',
				callbacksIndex = 0;// current index of callbacks hash (for producing unique key values)
	
			return {
				callback: callbacks,
				/**
				 * Get provides managed access to scripts across domains,
				 * allowing true function callbacks and request caching.
				 * @param {settings} Object named settings parameters
				 * @param {settings.url} String url to which to make request
				 * @param {settings.params} Object hash table of key/value pairs to send as parameters with request
				 * @param {settings.callback} Function callback executed upon successful completion of call
				 * @param {settings.scope} Object scope within which to execute callback.  Defaults to window.
				 */	
				get: function (settings) {
					settings = Api.extend({
						url: null,
						escape: true,
						cache: true,	// not yet implemented
						params: {},
						callback: function (data) {},
						scope: window
					}, settings);
			
					var url = settings.url.toString() || '',
						parameterizedUrl = settings.url + Api.urlize(settings.params, null, null, null, settings.escape),
						callbackKey;
			
					if (!callCache[parameterizedUrl]) {
						callbackKey = callbacksPrefix + callbacksIndex++;
						// add callback to params object
						settings.params = Api.extend(settings.params, {
							callback: 'DDC.Api.DataAccessManager.callback.' + callbackKey
						});
						// add callback to callback stack
						callbacks[callbackKey] = function (data) {
							Api.debug('DDC.Api.DataAccessManager.get() - Response from url: ' + parameterizedUrl);
							callCache[parameterizedUrl] = data;
							settings.callback(data);
						};
						
						// make xjax call
						Api.getScript(settings.url, settings.params, function () {}, settings.escape);
						Api.debug('DDC.Api.DataAccessManager.get() - Beginning Xjax request');
					} else {
						setTimeout(function () {	// don't fire immediately or firefox will choke
							var start = new Date(),
								stop;
							settings.callback(callCache[parameterizedUrl]);
							stop = new Date();
							Api.debug('Retrieving data access result from cache for url: ' + parameterizedUrl);
							Api.debug('Cached data access callback took: ' + (stop.getTime() - start.getTime()) + 'ms');
						}, 10);
					}
				}
			};
		}());

	// export packages
	window.DDC.Api = Api;
	window.DDC.Api.DataAccessManager = DataAccessManager;
}());
