/**
 * GenericSmartbrowse provides core functionality useful in building many types
 * of Smartbrowsing tools regardless of presentation.
 * @overview
 * @author Randall Morey
 * @version 0.1
 */
(function () {
	var window = this,
		$ = window.jQuery,
		/**
		 * The global Dealer.com namespace
		 * @name DDC
		 * @namespace
		 */
		DDC = (window.DDC = (window.DDC || {})),
		/**
		 * Makes an error constructor function.
		 * @private
		 * @returns {function}
		 * @param {string} name the name of the error
		 */
		makeError = function (name) {
			return function () {
				var error = Error.apply(this, arguments);
				error.name = name;
				return error;
			};
		};
	
	/**
	 * Creates a new Smartbrowse instance.
	 * @class
	 * @constructor
	 */
	DDC.GenericSmartbrowse = function (settings) {
		var self = this,
			/**
			 * Key/value pairs that store the current state of Smartbrowse.
			 * This value changes during execution.
			 * @private
			 * @type object
			 */
			params = {},
			/**
			 * The processed result of the last makeRequest call.
			 * @private
			 * @type object
			 */
			data = null,
			/**
			 * Current inventory type name used to access inventory type
			 * information from inventoryTypes.
			 * @private
			 * @type string
			 * @see DDC.GenericSmartbrowse#inventoryTypes
			 */
			inventoryType = '',
			/**
			 * Pairs of inventory type names and associated metadata, such as
			 * inventory and Smartbrowse URLs.
			 * @private
			 * @type object
			 */
			inventoryTypes = {},
			/**
			 * An array of functions used to process the response of
			 * makeRequest.  Processors may be added using addResponseProcessor.
			 * @private
			 * @type array
			 * @see DDC.GenericSmartbrowse#makeRequest
			 * @see DDC.GenericSmartbrowse#addResponseProcessor
			 */
			responseProcessors = [],
			/**
			 * An array of functions used to process the request of
			 * makeRequest.  Processors may be added using addRequestProcessor.
			 * @private
			 * @type array
			 * @see DDC.GenericSmartbrowse#makeRequest
			 * @see DDC.GenericSmartbrowse#addRequestProcessor
			 */
			requestProcessors = [],
			/**
			 * @private
			 * @returns {object} 
			 * @see DDC.GenericSmartbrowse#requestProcessors
			 * @see DDC.GenericSmartbrowse#addRequestProcessor
			 */
			processRequest = function (request) {
				var cumulativeRequest = request,
					processedRequest,
					i;
				
				for (i = 0; i < requestProcessors.length; i++) {
					if ($.isFunction(requestProcessors[i])) {
						processedRequest = requestProcessors[i]
							.apply(this, [cumulativeRequest]);
						if (processedRequest &&
							(typeof processedRequest === 'object') &&
							!$.isArray(processedRequest)) {
							cumulativeRequest = processedRequest;
						} else {
							throw new DDC.GenericSmartbrowse.errors.
								ObjectExpectedError('Request processor did ' +
								'not return an object.  ' +
								requestProcessors[i] + ' returned:  "' + 
								processedRequest + '"');
						}
					}
				}
				
				return cumulativeRequest;
			},
			/**
			 * @private
			 * @returns {object} 
			 * @see DDC.GenericSmartbrowse#responseProcessors
			 * @see DDC.GenericSmartbrowse#addResponseProcessor
			 */
			processResponse = function (response) {
				var cumulativeResponse = response,
					processedResponse,
					i;
				
				for (i = 0; i < responseProcessors.length; i++) {
					if ($.isFunction(responseProcessors[i])) {
						processedResponse = responseProcessors[i]
							.apply(this, [cumulativeResponse]);
						if (processedResponse &&
							(typeof processedResponse === 'object') &&
							!$.isArray(processedResponse)) {
							cumulativeResponse = processedResponse;
						} else {
							throw new DDC.GenericSmartbrowse.errors.
								ObjectExpectedError('Response processor did ' +
								'not return an object.  ' + 
								responseProcessors[i] + ' returned:  "' + 
								processedResponse + '"');
						}
					}
				}
				
				return cumulativeResponse;
			},
			/**
			 * Sets the private data variable.  Used internally by commit.
			 * This function should not be called by other functions.
			 * @private
			 * @function
			 * @param {object} data The data returned after all processors have
			 * run on the result of a call to makeRequest.
			 */
			setData = function (response) {
				data = response;
			},
			/**
			 * @public
			 * @function
			 * @name DDC.GenericSmartbrowse.getData
			 * @returns {object} A copy of the data returned by the last call
			 * to makeRequest.
			 */
			getData = function () {
				return $.extend(true, {}, data);
			},
			/**
			 * Gets the current parameters of this instance of Smartbrowse.
			 * @public
			 * @function
			 * @name DDC.GenericSmartbrowse.getParams
			 * @returns {object} A copy of the current Smartbrowse params.
			 * @see DDC.GenericSmartbrowse#params
			 */
			getParams = function () {
				return $.extend(true, {}, params);
			},
			/**
			 * Sets the parameters of this instance of Smartbrowse using
			 * a non-destructive merge or, if the 'clear' argument is true,
			 * a destructive copy is used.
			 * @public
			 * @function
			 * @name DDC.GenericSmartbrowse.setParams
			 * @param {object} obj An object of key/value pairs of Smartbrowse
			 * parameters used to set this Smartbrowse instance's parameters.
			 * @param {boolean} clear when true performs a desctructive
			 * copy-over operation.  The default operation (when clear is not
			 * passed or evaluates to false) is a non-destructive merge.
			 * @see DDC.GenericSmartbrowse#params
			 */
			setParams = function (obj, clear) {
				if ((typeof obj === 'object') && !clear) {
					// non-destructively merge argument onto existing params
					params = $.extend(true, {}, params, obj);
				} else if ((typeof obj === 'object') && clear) {
					// destructively copy argument over existing params
					params = $.extend(true, {}, obj);
				} else {
					throw new DDC.GenericSmartbrowse.errors.
						ObjectExpectedError('setParams() expected one ' +
						'argument, an object.');
				}
			},
			/**
			 * Gets a copy of the current inventory type information, including
			 * name and associated metadata.
			 * @public
			 * @function
			 * @name DDC.GenericSmartbrowse.getInventoryType
			 * @returns {object} An object with at least one member:  'name',
			 * which is a string representing the name of the selected
			 * inventory type.  All other parameters are copied from
			 * inventoryTypes.
			 * @see DDC.GenericSmartbrowse#inventoryType
			 * @see DDC.GenericSmartbrowse#inventoryTypes
			 */
			getInventoryType = function () {
				// return a copy of the current inventory type object
				// don't return the actual object else it could be modified
				return $.extend(true, {}, inventoryTypes[inventoryType]);
			},
			/**
			 * Sets the current inventory type.
			 * @public
			 * @function
			 * @name DDC.GenericSmartbrowse.setInventoryType
			 * @param {string} type The name of the inventory type to which to
			 * set this instance of Smartbrowse.
			 * @see DDC.GenericSmartbrowse#inventoryType
			 * @see DDC.GenericSmartbrowse#inventoryTypes
			 * @see DDC.GenericSmartbrowse#getInventoryType
			 */
			setInventoryType = function (type) {
				if (inventoryTypes[type]) {
					inventoryType = type;
				} else {
					throw new DDC.GenericSmartbrowse.errors.
						InventoryTypeDoesNotExistError('Specified type: ' +
							type + ' does not exist.');
				}
			},
			/**
			 * Adds a new function to the request processor stack.
			 * @public
			 * @function
			 * @name DDC.GenericSmartbrowse.addRequestProcessor
			 * @param {function} fn A function which accepts one argument, an
			 * object containing request parameters.  The function must return
			 * an object containing request parameters.
			 */
			addRequestProcessor = function (fn) {
				if ($.isFunction(fn)) {
					requestProcessors.push(fn);
				} else {
					throw new DDC.GenericSmartbrowse.errors.
						FunctionExpectedError('addRequestProcessor' +
						'() expected one argument, a function.');
				}
			},
			/**
			 * Adds a new function to the response processor stack.
			 * @public
			 * @function
			 * @name DDC.GenericSmartbrowse.addResponseProcessor
			 * @param {function} fn A function which accepts one argument,
			 * the partially processed data, and returns the data after
			 * further processing.
			 */
			addResponseProcessor = function (fn) {
				if ($.isFunction(fn)) {
					responseProcessors.push(fn);
				} else {
					throw new DDC.GenericSmartbrowse.errors.
						FunctionExpectedError('addResponseProcessor' +
						'() expected one argument, a function.');
				}
			},
			/**
			 * Executes this Smartbrowse instance's makeRequest function and
			 * passes the response through any response processors, finally,
			 * calls this instance's view function passing processed response.
			 * @public
			 * @function
			 * @name DDC.GenericSmartbrowse.commit
			 * @param {string} settings.inventoryType optionally specify an
			 * inventory type to set just before the request is made
			 * @param {string} settings.params optionally specifies params
			 * to set just before the request is made
			 * @params {boolean} settings.clear specifies the "clear" argument
			 * in the setParams call.  If true, settings.params desctructively
			 * overwrites existing parameters.  Otherwise, a non-destructive
			 * merge is performed.
			 * @see DDC.GenericSmartbrowse#makeRequest
			 * @see DDC.GenericSmartbrowse#responseProcessors
			 * @see DDC.GenericSmartbrowse#processResponse
			 */
			commit = function (settings) {
				if (settings && settings.inventoryType) {
					this.setInventoryType(settings.inventoryType);
				}
				if (settings && settings.params) {
					this.setParams(settings.params, settings.clear);
				}
				
				this.makeRequest({
					params: processRequest.apply(self, [getParams()]),
					inventoryType: getInventoryType(),
					callback: function (response) {
						if (response && (typeof response === 'object') &&
							!$.isArray(response)) {
							setData(processResponse.apply(self, [response]));
							if ($.isFunction(self.view)) {
								self.view.apply(self, [getData()]);
							}
						} else {
							throw new DDC.GenericSmartbrowse.errors.
								ObjectExpectedError('makeRequest() did not ' +
								'return an object.  Got "' +
								response + '" instead.');
						}
					}
				});
			},
			/**
			 * Initializes a new instance of GenericSmartbrowse.  This function
			 * is the constructor.
			 * @private
			 * @function
			 */
			init = function (settings) {
				var i;
				
				// expose privileged methods
				this.getData = getData;
				this.getParams = getParams;
				this.setParams = setParams;
				this.getInventoryType = getInventoryType;
				this.setInventoryType = setInventoryType;
				this.addResponseProcessor = addResponseProcessor;
				this.addRequestProcessor = addRequestProcessor;
				this.commit = commit;
				
				// store inventory types
				// there are no defaults for these as they change site-to-site
				inventoryTypes = $.extend(true, {}, (settings && settings.inventoryTypes));
				// add request processors
				if (settings && settings.requestProcessors) {
					for (i = 0; i < settings.requestProcessors.length; i++) {
						this.addRequestProcessor(settings.requestProcessors[i]);
					}
				}
				// add response processors
				if (settings && settings.responseProcessors) {
					for (i = 0; i < settings.responseProcessors.length; i++) {
						this.addResponseProcessor(settings.responseProcessors[i]);
					}
				}
				// if a view was passed through settings, use it
				if (settings && settings.view) {
					this.view = settings.view;
				}
				// set initial params
				setParams((settings && settings.params) || {});
				
				return this;
			};
		
		if (this !== DDC) {
			return init.apply(this, arguments);
		} else {
			throw new DDC.GenericSmartbrowse.errors.
				InvalidInvocationError('DDC.GenericSmartbrowse is a ' +
				'constructor and may not be called directly.');
		}
	};
	
	/**
	 * A namespace for errors used by GenericSmartbrowse.
	 * @name DDC.GenericSmartbrowse.errors
	 * @namespace
	 */
	DDC.GenericSmartbrowse.errors = {
		/**
		 * @name DDC.GenericSmartbrowse.errors.InvalidInvocationError */
		InvalidInvocationError: makeError('InvalidInvocationError'),
		/**
		 * @name DDC.GenericSmartbrowse.errors.ObjectExpectedError */
		ObjectExpectedError: makeError('ObjectExpectedError'),
		/**
		 * @name DDC.GenericSmartbrowse.errors.ArrayExpectedError */
		ArrayExpectedError: makeError('ArrayExpectedError'),
		/**
		 * @name DDC.GenericSmartbrowse.errors.FunctionExpectedError */
		FunctionExpectedError: makeError('FunctionExpectedError'),
		/**
		 * @name DDC.GenericSmartbrowse.errors.SelectExpectedError */
		SelectExpectedError: makeError('SelectExpectedError'),
		/**
		 * @name DDC.GenericSmartbrowse.errors.ArgumentsExpectedError */
		ArgumentsExpectedError: makeError('ArgumentsExpectedError'),
		/**
		 * @name DDC.GenericSmartbrowse.errors.URLExpectedError */
		URLExpectedError: makeError('URLExpectedError'),
		/**
		 * @name DDC.GenericSmartbrowse.errors.InventoryTypeDoesNotExistError */
		InventoryTypeDoesNotExistError: makeError('InventoryTypeDoesNotExistError'),
		/**
		 * @name DDC.GenericSmartbrowse.errors.PackageMissingError */
		PackageMissingError: makeError('PackageMissingError')
	};
	
	DDC.GenericSmartbrowse.errors.ObjectExpectedError.prototype = Error;
	DDC.GenericSmartbrowse.errors.ArrayExpectedError.prototype = Error;
	DDC.GenericSmartbrowse.errors.FunctionExpectedError.prototype = Error;
	DDC.GenericSmartbrowse.errors.SelectExpectedError.prototype = Error;
	DDC.GenericSmartbrowse.errors.ArgumentsExpectedError.prototype = Error;
	DDC.GenericSmartbrowse.errors.URLExpectedError.prototype = Error;
	DDC.GenericSmartbrowse.errors.InventoryTypeDoesNotExistError.prototype = Error;
	DDC.GenericSmartbrowse.errors.PackageMissingError.prototype = Error;
	
	/**
	 * Requests are standard data accessor methods which may be used as the
	 * makeRequest method on a GenericSmartbrowse instance.
	 * @name DDC.GenericSmartbrowse.requests
	 * @namespace
	 * @see DDC.GenericSmartbrowse#makeRequest
	 * @example
	 * mySmartbrowse.makeRequest = DDC.GenericSmartbrowse.requests.ajax;
	 * apiSmartbrowse.makeRequest = DDC.GenericSmartbrowse.requests.api;
	 */
	DDC.GenericSmartbrowse.requests = {
		/**
		 * Requests Smartbrowse data through an ajax call.  This is the default
		 * makeRequest function.
		 * @name DDC.GenericSmartbrowse.requests.ajax
		 * @borrows DDC.GenericSmartbrowse#makeRequest
		 * as DDC.GenericSmartbrowse.requests#ajax
		 * @see DDC.GenericSmartbrowse#makeRequest
		 */
		ajax: function (settings) {
			settings = $.extend(true, {
				params: {},
				inventoryType: {},
				callback: null
			}, settings);
			
			$.ajax({
				dataType: 'json',
				success: settings.callback,
				url: settings.inventoryType.ajax +
					DDC.GenericSmartbrowse.url.urlize(settings.params)
			});
		},
		/**
		 * Requests Smartbrowse data through an API call.
		 * @name DDC.GenericSmartbrowse.requests.api
		 * @borrows DDC.GenericSmartbrowse#makeRequest
		 * as DDC.GenericSmartbrowse.requests#api
		 * @see DDC.GenericSmartbrowse#makeRequest
		 * @see DDC.Api.DataAccessManager
		 */
		api: function (settings) {
			settings = $.extend(true, {
				params: {},
				service: null,
				callback: null,
				url: 'http://apis.dealer.com/services/inventory/v1/smartbrowse'
			}, settings);
			
			if (DDC.Api && DDC.Api.DataAccessManager) {
				DDC.Api.DataAccessManager.get(settings);
			} else {
				throw new DDC.GenericSmartbrowse.errors.
					PackageMissingError('DDC.Api.DataAccessManager is ' +
						'required to use the api request type.');
			}
		}
	};
	
	/**
	 * Request processors process parameters used to make a request.  They fall
	 * into categories which correspond to the request type.  For example,
	 * request processors in DDC.GenericSmartbrowse.requestProcessors.ajax
	 * may be used to process requests for the request method
	 * DDC.GenericSmartbrowse.requests.ajax.
	 * @namespace
	 * @see DDC.GenericSmartbrowse#requestProcessors
	 * @example
	 * mySmartbrowse.addRequestProcessor(DDC.GenericSmartbrowse
	 * .requestProcessor.ajax.removeNullKeyEntries);
	 */
	DDC.GenericSmartbrowse.requestProcessors = {
		/**
		 * AJAX request processors process parameters used to make an
		 * AJAX request.
		 * @namespace
		 * @see DDC.GenericSmartbrowse#requestProcessors
		 */
		ajax: {
			/**
			 * Enforces compliance logic on requests.
			 * @public
			 * @function
			 * @param {makes} array of franchises for which to apply
			 * compliance logic
			 * @returns {function} returns a request processor which performs
			 * compliance logic on all requests according to the
			 * specified franchises
			 * @see DDC.GenericSmartbrowse#requestProcessors
			 */
			compliance: function (makes) {
				if (!$.isArray(makes)) {
					throw new DDC.GenericSmartbrowse.errors.
						ArrayExpectedError('compliance() expected one ' +
						'argument, an array of franchises.');
				}
				
				return function (request) {
					// compliance logic applies only when there is 1 franchise
					if (makes.length === 1) {
						// if bmw is the only franchise and new inventory
						// is requested, then default to bmw
						if (($.inArray('bmw', makes) !== -1) &&
						(this.getInventoryType().name === 'new')) {
							this.setParams({SBmake: 'BMW'});
							request.SBmake = 'BMW';
						}
						// if acura is the only franchise,
						// than all requests must default to acura
						if ($.inArray('acura', makes) !== -1) {
							this.setParams({SBmake: 'Acura'});
							request.SBmake = 'Acura';
						}
						// if honda is the only franchise,
						// than all requests must default to honda
						if ($.inArray('honda', makes) !== -1) {
							this.setParams({SBmake: 'Honda'});
							request.SBmake = 'Honda';
						}
					}
					
					return request;
				};
			}
		}
	};
	
	/**
	 * Response processors process data returned from a request.  They fall
	 * into categories which correspond to the request type.  For example,
	 * response processors in DDC.GenericSmartbrowse.responseProcessors.ajax
	 * may be used to process responses from the request method
	 * DDC.GenericSmartbrowse.requests.ajax.
	 * @namespace
	 * @see DDC.GenericSmartbrowse#responseProcessors
	 * @example
	 * mySmartbrowse.addResponseProcessor(DDC.GenericSmartbrowse
	 * .responseProcessor.ajax.removeNullKeyEntries);
	 */
	DDC.GenericSmartbrowse.responseProcessors = {
		/**
		 * Ajax response processors process data returned from an ajax request.
		 * @namespace
		 * @see DDC.GenericSmartbrowse#responseProcessors
		 * @example
		 * mySmartbrowse.addResponseProcessor(DDC.GenericSmartbrowse
		 * .responseProcessor.ajax.removeNullKeyEntries);
		 */
		ajax: {
			/**
			 * Enforces compliance logic on responses.
			 * @public
			 * @function
			 * @param {makes} array of franchises for which to apply
			 * compliance logic
			 * @returns {function} returns a response processor which performs
			 * compliance logic on all responses according to the
			 * specified franchises
			 * @see DDC.GenericSmartbrowse#responseProcessors
			 */
			compliance: function (makes) {
				if (!$.isArray(makes)) {
					throw new DDC.GenericSmartbrowse.errors.
						ArrayExpectedError('compliance() expected one ' +
						'argument, an array of franchises.');
				}
				
				return function (response) {
					var SBmakeBMWFirst, make;
					
					// compliance logic applies only when there is 1 franchise
					if (makes.length === 1) {
						// if bmw is the only franchise and new inventory
						// is requested, then list only BMW makes
						if (($.inArray('bmw', makes) !== -1) &&
						(this.getInventoryType().name === 'new') &&
						response.SBmake && response.SBmake.BMW) {
							response.SBmake = {BMW: response.SBmake.BMW};
						}
						// if bmw is the only franchise and used inventory
						// is requested, then list the BMW make first
						if (($.inArray('bmw', makes) !== -1) &&
						(this.getInventoryType().name === 'used') &&
						response.SBmake && response.SBmake.BMW) {
							SBmakeBMWFirst = {BMW: response.SBmake.BMW};
							for (make in response.SBmake) {
								if (make !== 'BMW') {
									SBmakeBMWFirst[make] =
										response.SBmake[make];
								}
							}
							response.SBmake = SBmakeBMWFirst;
						}
						// if acura is the only franchise, but not honda,
						// than all requests must default to acura
						if (($.inArray('acura', makes) !== -1) &&
						response.SBmake && response.SBmake.Acura) {
							response.SBmake = {Acura: response.SBmake.Acura};
						}
						// if honda is the only franchise, but not acura,
						// than all requests must default to honda
						if (($.inArray('honda', makes) !== -1) &&
						response.SBmake && response.SBmake.Honda) {
							response.SBmake = {Honda: response.SBmake.Honda};
						}
					}
					
					return response;
				};
			},
			/**
			 * Performs a simple replace operation on name values.
			 * Does not affect key values.
			 * @public
			 * @function
			 * @param {regexp} search is the matching regular expression
			 * @param {string} replace is the string with which to replace
			 * matches to "search"
			 * @returns {function} returns a response processor which performs
			 * the specified replacement on name values
			 * @see DDC.GenericSmartbrowse#responseProcessors
			 */
			nameValueReplacement: function (search, replace) {
				if (!(search && (replace || (replace === '')))) {
					throw new DDC.GenericSmartbrowse.errors.
						ArgumentsExpectedError('nameValueReplacement() ' +
						'expected two arguments, a regular expression or ' +
						'string and a string.');
				}
				
				return function (rsp) {
					var k, k2;
					
					for (k in rsp) {
						if ((k !== 'SBdefaults') &&
							(typeof rsp[k] === 'object') &&
							!(rsp[k] instanceof Array)) {
							for (k2 in rsp[k]) {
								if (rsp[k][k2]) {
									rsp[k][k2] = rsp[k][k2]
										.replace(search, replace);
								}
							}
						}
					}
					
					return rsp;
				};
			},
			/**
			 * Delete response entries with null key values.
			 * @public
			 * @function
			 * @param {object} rsp is the object passed from the response
			 * processor
			 * @returns {object} returns rsp after moving null key entries
			 * @see DDC.GenericSmartbrowse#responseProcessors
			 */
			removeNullKeyEntries: function (rsp) {
				var k, k2;
				
				for (k in rsp) {
					if ((k !== 'SBdefaults') &&
						(typeof rsp[k] === 'object') &&
						!$.isArray(rsp[k])) {
						for (k2 in rsp[k]) {
							if (!k2) {
								delete rsp[k][k2];
							}
						}
					}
				}
				return rsp;
			}
		}
	};
	
	DDC.GenericSmartbrowse.prototype = {
		/**
		 * @public
		 * @function
		 * @param {object} params The current Smartbrowse parameters.
		 * @param {object} inventoryType The current inventory type as an
		 * {@link inventoryType} object.
		 * @param {function} callback Called with the data resulting from
		 * the request.
		 */
		makeRequest: DDC.GenericSmartbrowse.requests.ajax
	};
	
	// static functions
	$.extend(DDC.GenericSmartbrowse, {
		/**
		 * URL utility functions are stored in this namespace.  These functions
		 * are intended for use externally by developer code.
		 * @name DDC.GenericSmartbrowse.url
		 * @namespace
		 */
		url: {
			/**
			 * @public
			 * @static
			 * @function
			 * @name DDC.GenericSmartbrowse.url.redirect
			 * @param {string} url The URL to which to redirect the
			 * current window.
			 */
			redirect: function (url) {
				if (url) {
					window.location = url;
				} else {
					throw new DDC.GenericSmartbrowse.errors.
						URLExpectedError('redirect() expected one ' +
						'argument: a url.');
				}
			},
			/**
			 * Converts an object of key/value pairs to a URL parameter string.
			 * @public
			 * @static
			 * @function
			 * @name DDC.GenericSmartbrowse.url.urlize
			 * @param {object} params is an object of key/value pairs to
			 * convert to url parameters
			 * @returns {string} Returns the URL parameter string
			 * @example
			 * DDC.GenericSmartbrowse.url.urlize({color: 'blue'})
			 */
			urlize: function (arg, x, y, z) {
				x = x || '?';	// parameter string prefix
				y = y || '&';	// parameter pair delimiter
				z = z || '=';	// key / value delimiter
				var i, s = '', v,
					urlize = DDC.GenericSmartbrowse.url.urlize,
					urlencode = function (str) {
						return encodeURIComponent(str)
							.replace(/\+/g, '%2B')
							.replace(/\"/g, '%22')
							.replace(/\'/g, '%27');
					};
				
				switch (typeof arg) {
				case 'object':
					if (arg) {
						for (i in arg) {
							if (typeof (v = urlize(arg[i])) !== 'function') {
								if (s) {
									s += y;
								}
								s += urlencode(urlize(i)) + z + urlencode(v);
							}
						}
						return (s.length >= 1 ? x : '') + s;
					} else {
						return 'null';
					}
					break;
				case 'string':
					return arg;
				case 'number':
					return String(arg);
				case 'boolean':
					return arg.toString();
				default:
					return 'null';
				}
			},
			/**
			 * Converts a standard set of GenericSmartbrowse parameters to
			 * Helios-compatible parameters and returns a complete
			 * Helios-compatible URL.
			 * @public
			 * @static
			 * @function
			 * @name DDC.GenericSmartbrowse.url.heliosUrlize
			 * @param {object} params is an object of key/value pairs to
			 * convert to url parameters
			 * @returns {string} Returns the URL with
			 */
			heliosUrlize: function (params, url) {
				var key,
					map = {
						SByear: 'year',
						SBmake: 'make',
						SBmodel: 'model',
						SBbodystyle: 'bodyStyle',
						SBprice: 'internetPrice',
						SBhighwayMPG: 'highwayMpg'
					},
					rangeMap = ['SBprice', 'SBhighwayMPG'],
					heliosParams = {},
					heliosUrl = url.toString();
				
				if (params &&
					(typeof params === 'object') &&
					!$.isArray(params)) {
					for (key in params) {
						if ($.inArray(key, rangeMap) !== -1) {
							// parameter value is a range requiring conversion
							if (map[key]) {
								heliosParams[map[key]] = params[key].toString();
								heliosParams[map[key]] = '[' +
									heliosParams[map[key]].
										replace(/\s?\-\s?/g, ' TO ') + ']';
							}
						} else {
							// parameter value is not a range; map directly
							if (map[key]) {
								heliosParams[map[key]] = params[key].toString();
							}
						}
					}
				} else {
					throw new DDC.GenericSmartbrowse.errors.
						ObjectExpectedError('heliosUrlize() expected an ' +
						'object but got: ' + params);
				}
				
				if (heliosUrl) {
					// remove trailing delimeters from Helios URL
					heliosUrl.replace(/[&?]$/, '');
					// urlize Helios parameters
					heliosParams =
						DDC.GenericSmartbrowse.url.urlize(heliosParams);
					if (heliosUrl.indexOf('?') !== -1) {
						heliosParams = heliosParams.replace('?', '&');
					}
					heliosUrl += heliosParams;
				} else {
					throw new DDC.GenericSmartbrowse.errors.
						URLExpectedError('heliosUrlize() expects a URL as ' +
							'its second argument.');
				}
				
				return heliosUrl;
			}
		},
		/**
		 * UI utility functions are stored in this namespace.  These functions
		 * are intended for use externally by developer code.  These are never
		 * called directly by GenericSmartbrowse itself.
		 * @name DDC.GenericSmartbrowse.ui
		 * @namespace
		 */
		ui: {
			/**
			 * Removes all options from a select element.
			 * @public
			 * @static
			 * @function
			 * @name DDC.GenericSmartbrowse.ui.clearOptions
			 * @param {regexp} search is the matching regular expression
			 * @returns {select} returns the select element
			 */
			clearOptions: function (el) {
				if (el && (el.tagName.toLowerCase() === 'select')) {
					for (var i = (el.options.length - 1); i >= 1; i--) {
						el.options[i] = null;
					}
				} else {
					throw new DDC.GenericSmartbrowse.errors.
						SelectExpectedError('clearOptions() expects ' +
						'one argument: a `select` element.');
				}
				
				return el;
			}
		},
		/**
		 * General utility functions are stored in this namespace.  These
		 * are intended for use externally by developer code.  These are never
		 * called directly by GenericSmartbrowse itself.
		 * @name DDC.GenericSmartbrowse.util
		 * @namespace
		 */
		util: {
			/**
			 * Capitalizes a string.
			 * @public
			 * @static
			 * @function
			 * @name DDC.GenericSmartbrowse.util.capitalize
			 * @param {string} String to capitalize.
			 * @return {string} Returns a capitalized string.
			 */
			capitalize: function (str) {
				return str.replace(/\w+/g, function (a) {
					return a.charAt(0).toUpperCase() + a.substr(1).toLowerCase();
				});
			}
		}
	});
}());
