var APP_CONTEXT = "/FordUSCommApp";

/** Utilities for creating / manipulating JavaScript classes */
var ClassUtils = {
	/**
	 * Creates a new class. The created class will look to call a function
	 * called "initialize" when it's instantiated.
	 * @param [superClass] identifies a super class when creating a new class.
	 * 		This will copy the methods in the super class to the new class and
	 * 		create a new function called "_super" that enables you to call up
	 * 		to the parent classes method (if you've overridden that method).
	 * @return the newly created class
	 *
	 * @example
	 *		var Foo = ClassUtils.create();
	 *		Foo.prototype = {
	 * 			initialize: function(arg0, arg1) { this.a = arg0; this.b = arg1; },
	 *			test: function() { return this.getMessage(); },
	 *			getMessage: function() { return "a=" + this.a + ", b=" + this.b; }
	 *		}
	 *		var Bar = ClassUtils.create(Foo);
	 *		ClassUtils.extend(Bar.prototype, {
	 *			getMessage: function() { return "BAR - " + this._super("getMessage"); }
	 *		});
	 */
	create: function(superClass) {
		if(superClass) {
			var clazz = ClassUtils.create();
			ClassUtils.extend(clazz.prototype, superClass.prototype);
			clazz.prototype._superClass = superClass;
			clazz.prototype._super = function(func) {
				var args = [];
				for(var i=1; i<arguments.length; i++) {
					args[i-1] = arguments[i];
				}
				return this._superClass.prototype[func].apply(this, args);
			};
			return clazz;
		} else {
			return function() { this.initialize.apply(this, arguments); }
		}
	},
	/**
	 * Takes the properties of the source object and applies them to the
	 * destination object.
	 * @param destination the class/object to populate. When using the class as
	 * 		the destination, you'll want to use the prototype of the class
	 * 		(e.g. Object.prototype)
	 * @param source the object/class to retrieve the properties from
	 * @return the extended class/object
	 */
	extend: function(destination, source) {
		if(!destination) destination = {};
		if(!source) return destination;
		for(var property in source) destination[property] = source[property];
		return destination;
	},
	enclose: function(instance, func) {
		return function() {func.apply(instance, arguments);};
	}
};

/** Utilities for creating / manipulating hash objects (i.e. {}) */
var HashUtils = {
	/**
	 * Determines if the hash map is empty.
	 * @return true if the hash map is null or does not contain any values,
	 * 		false if it contains values.
	 */
	isEmpty: function(hashMap) {
		if(hashMap != null) for(var key in hashMap) return false;
		return true;
	},
	/**
	 * Determines the size of the hash map
	 * @return an integer of the size. If hashmap is null, -1 will be returned.
	 */
	size: function(hashMap) {
		if(!hashMap) return -1;
		var counter = 0;
		for(var prop in hashMap) counter++;
		return counter;
	},
	addAll: function(dest, src) {
		if(!dest || !src) return;
		for(item in src) {
			dest[item] = src[item];
		}
	}
};

function formatCurrency(num)  {
	if(!num) num = "0";
	else num = num.toString().replace(/\$|\,/g,'');
	if(isNaN(num))
		num = "0";
	sign = (num == (num = Math.abs(num)));
	num = Math.floor(num*100+0.50000000001);
	cents = num%100;
	num = Math.floor(num/100).toString();
	if(cents<10)
		cents = "0" + cents;
	for (var i = 0; i < Math.floor((num.length-(1+i))/3); i++)
		num = num.substring(0,num.length-(4*i+3))+','+num.substring(num.length-(4*i+3));
	return (((sign)?'':'-') + num );
}
//Remove all options from a select object
function clearSelectBoxOptions(objSelect){
	while (objSelect.options.length!=0)
      	objSelect.remove(0);
}

/**
 * Retrieves the named parameter.
 * @param [name] the name of the parameter to retrieve. If omitted, the ID
 * 		element name (i.e. #idElementName) in the location will be used.
 * @return the named parameter or null if not found.
 */
function gup(name) {
	var regex;
	if(arguments.length == 0) regex = /#([^&#\?]*)/;
	else regex = new RegExp("[\\?&]"+name+"=([^&#]*)");
	var results = regex.exec(window.location.href);
	if(results == null) return null;
	else return results[1];
}

/** @deprecated use gup(name) instead. This function is expected to return "0" if the parameter is not found. Legacy calls require this, but it makes more sense to return null. */
function getParameter(name) {
	var val = gup(name);
	return (val == null) ? "0" : val;
}

var ImageSource = ClassUtils.create();
/**
 * Parses the serialized image. This is normally returned as "SRC|NAME|COLOR",
 * but added additional attribute for angle at the end.
 * @param serializedImage either a pipe delimited string for the image parts or
 * 		just a name that will get set to the name attribute of the resulting
 * 		image.
 * @return an ImageSource object built from the serialized image.
 */
ImageSource.parse = function(serializedImage) {
	var props = (serializedImage) ? serializedImage.split("|") : [];
	var src, name, color, angle;
	if(props.length < 2) {
		src = null;
		name = serializedImage;
		color = null;
		angle = null;
	} else {
		src = props[0];
		name = props[1];
		if(props.length > 2) color = props[2];
		else color = null;
		if(props.length > 3) angle = props[3];
		else angle = null;
	}
	return new ImageSource(name, color, angle, src);
}
ClassUtils.extend(ImageSource.prototype, {
	initialize: function(name, color, angle, src) {
		this.name = name;
		this.color = color;
		this.angle = angle;
		this.src = (src) ? src : "EVD";
	},
	serialize: function() {
		var name = (this.name) ? this.name : "";
		var color = (this.color) ? this.color : "";
		var angle = (this.angle) ? this.angle : "";
		var src = (this.src) ? this.src : "";
		return src + "|" + name + "|" + color + "|" + angle;
	},
	toString: function() {
		this.serialize();
	}
});

var ImageBuilder = {
	cache: {},
	getColorizedImage: function(acode, serializedImage, width, height, options, callback) {
		if(!options) options = {};
		var url;
		var image = ImageSource.parse(serializedImage);
		if(!options.angle) options.angle = image.angle;
		if(options.color) image.color = options.color;
		
		if(image.src == "EVD") {
			var cached = this.cache[image.serialize()];
			if(cached) {
				callback.apply(window, [this.buildUrl(cached, width, height, options)]);
			} else {
				var _this = this;
				$.getJSON(APP_CONTEXT + "/imageAction/getColorizedImage.do", {acode:acode,colorCode:image.color}, function(json, status) {
					var imageName = (json.image) ? json.image : image.name;
					_this.cache[image.serialize()] = imageName;
					callback.apply(window, [_this.buildUrl(imageName, width, height, options)]);
				});
			}
		} else {
			// Not supported
		}
	},
	/*
	 * Builds the URL from a serlialized image
	 * @param serializedImage a pipe delimited serialized image
	 * @param width the width of the image
	 * @param height the height of the image
	 * @param [options] {asPng:true,autoTrim:true,angle:101}
	 */
	buildUrl: function(serializedImage, width, height, options) {
		var url;
		var image = ImageSource.parse(serializedImage);
		if(image.src = "EVD") {
			var img;
			if(options.asPng) img = this.png(image.name);
			else img = image.name;
			
			var props = "";
			if(options.autoTrim) props += "&AUTOTRIM=1";
			if(options.angle) {
				var regex = /[0-9]{3}(\.\w{3}$)/;
				if(regex.test(img)) img = img.replace(regex, options.angle + "$1");
			}
			url = "http://imageonthefly.autodatadirect.com/images/?USER=vmlcommtruck&PW=commtruck86235&IMG=" + img + "&HEIGHT=" + height + "&WIDTH=" + width + props;
		} else url = null;
		return url;
	},
	png: function(image) {
		if(image) return image.replace(/\.jpg$/, ".png");
		else return image;
	}
}

var FormUtils = {
	getForm: function(formElem) {
		var jqElem = $(formElem);
		if (jqElem.length > 0 && jqElem[0].tagName == "FORM") return jqElem[0];
		else {
			var jqForm = jqElem.parents("form");
			return (jqForm.length > 0) ? jqForm[0] : null;
		}
	},
	// Not happy with this function
	setValue: function(elem, value) {
		if(elem.length && elem.tagName != "SELECT") {
			for(var i=0; i<elem.length; i++) {
				this.setValue(elem[i], value);
			}
		} else {
			if (value == null) value = "";
			switch (elem.tagName) {
				case "INPUT":
					switch (elem.type) {
						case "button":
						case "radio":
						case "checkbox":
							// Add the empty string to convert booleans into strings
							elem.checked = (elem.value == (value + ""));
							return;
						case "text":
							// Continue
					}
				case "TEXTAREA":
					elem.value = value;
					return;
				case "SELECT":
					elem.value = value;
					return;
				default:
					elem.innerHTML = value;
			}
		}
	},
	setFormField: function(form, prefix, fieldName, obj) {
		var field = form[prefix+"."+fieldName];
		if(field) {
			var props = fieldName.split("\.");
			var value = obj;
			for(var i=0; i<props.length; i++) {
				value = value[props[i]];
				if(value == null) break;
			}
			this.setValue(field, value);
		}
	},
	/**
	 * Populates the select boxes that match the given selector with data
	 * retrieved from the request.
	 *
	 * @param req
	 *            a request object formatted as such:
	 *            {url:"/actionName/methodName.do",key:"keyFieldOfReturnedObject",value:"valueFieldOfReturnedObject"}
	 * @param a
	 *            jQuery selector for the select boxes to populate.
	 */
	populateSelectBox: function(req, selectBoxSelector, callback) {
		$.get(APP_CONTEXT + req.url, function(data) {
			var getArgs = arguments;
			if(!data || data == null) return;
			var optionsArray = eval("("+data+")");
			$(document).ready(function() {
				var jboxes = $(selectBoxSelector);
				jboxes.each(function(index, elem) {
					for(var i=0; i<optionsArray.length; i++) {
						var item = document.createElement("option");
						item.value = optionsArray[i][req.key];
						item.innerHTML = optionsArray[i][req.value];
						elem.appendChild(item);
					}
				});
				if(callback) callback.apply(jboxes, getArgs);
			});
		});
	},
	/**
	 * Use this function when setting the value of an element is dependent on
	 * a condition and the value to be available.
	 * @param elemSelector a jQuery element selector of the element[s] to set
	 * 		the value
	 * @param when a function that returns true or false whether one condition
	 * 		exists to set the value
	 * @param value the value to be set (could be a function)
	 * @param [attempts] the number of attempts to make
	 */
	tryToSetElement: function(elemSelector, when, value, attempts) {
		var getValue;
		if (typeof value == "function") {
			getValue = value;
		} else {
			getValue = function() {return value;};
		}
		if (!attempts) attempts = 10;
		var interval = setInterval(function() {
			var jelem = $(elemSelector);
			var value = getValue.apply(window, []);
			var complete = false;
			if (jelem.length > 0 && when.apply(window, []) && value != null) {
				jelem.each(function(index, elem) {
					elem.value = value;
				});
				complete = true;
			}
			if (complete || FormUtils.tryingToSetElement.attempts > attempts) {
				clearInterval(FormUtils.tryingToSetElement.interval);
			} else {
				FormUtils.tryingToSetElement.attempts++;
			}
		}, 100);
		FormUtils.tryingToSetElement = {interval:interval, attempts:0};
	}
}
var TextUtils = {
	htmlBreaks: function(text) {
		if(text) {
			text = text.replace(/(\r\n|\r|\n)/g, "<br\/>");
			/* TBD Verify the above line works across OS; otherwise, use following
			// Replacement for Windows
			text = text.replace(/\r\n/g, "<br\/>");
			// Replacement for OS X (although it doesn't seep to be true anymore)
			text = text.replace(/\r/g, "<br\/>");
			// Replacement for Linux
			text = text.replace(/\n/g, "<br\/>");
			*/
			// Replace back to back breaks with an open close paragraph
			text = "<p>" + text.replace(/<br\/><br\/>/g, "<\/p><p>") + "</p>";
			// Replace empty paragraph tags with breaks
			text = text.replace(/<p><\/p>/g, "<br\/><br\/>");
			// Keep breaks at the end. Happens with an even number of breaks in between. Minor cleanup, but not really necessary
			text = text.replace(/<p><br\/>/g, "<br\/><p>");
		}
		return text;
	},
	escapeHtml: function(text) {
		if (text) return text.replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/\"/g,"&quot;").replace(/&(?!([a-z]{2,4};))/g,"&amp;");
		else return text;
	},
	escapeJson: function(text) {
		if (text) return text.replace(/('|\")/g,"\\$1");
		else return text;
	},
	// This may handle quotes when dynamically writting inline JS better 
	jsonQuotes: function(text) {
		if (text) return text.replace(/([^\\])(')/g,"$1\\$2").replace(/\"/g,"&quot;");
		else return text;
	}
}
var DateUtils = {
	defaultMask :"mm/dd/yyyy",
	isValid: function(val) {
		return (this.getDate(val) != null);
	},
	getDate : function(val) {
		if (arguments.length == 0) return new Date();
		if (!val) return null;
		if (typeof val == "string") {
			var date;
			var dashRegex = /[\.|-]/g;
			if(dashRegex.test(val)) val = val.replace(dashRegex, "/");
			
			var patternRegex = /^([0-9]{1,2})\/([0-9]{1,2})\/?([0-9]{0,4})$/;
			if (patternRegex.test(val)) {
				var matcher = patternRegex.exec(val);
				var m = matcher[1];
				var d = matcher[2];
				var y = DateUtils.getFourDigitYear(matcher[3]);
				if (m < 1 || m > 12 || d < 1 || d > 31 || !y) return null;
				else return new Date(m + "/" + d + "/" + y);
			} else {
				return null;
			}
		} else if (val.getTime) {
			return val;
		} else {
			return null;
		}
	},
	/** Returns the date without any time information set. */
	getStartOfDay : function(date) {
		if (arguments.length == 0)
			date = new Date();
		var str = DateUtils.getDateString(date, "m/d/yyyy");
		if (!str)
			return null;
		return new Date(str);
	},
	getEndOfDay : function(date) {
		if (arguments.length == 0)
			date = new Date();
		var start = DateUtils.getStartOfDay(date);
		if (!start)
			return null;
		return new Date(start.getTime() + 86399999);

	},
	/** Formats the date into a string with the given mask. */
	getDateString : function(date, mask) {
		var obj = (arguments.length == 0) ? DateUtils.getDate() : DateUtils.getDate(date);
		if (!obj)
			return null;
		if (!mask)
			mask = DateUtils.defaultMask;
		var dashRegex = /[\.|\-|\/]/;
		var dash = DateUtils.getDash(mask);
		var split;
		if (dash) {
			split = mask.split(dash);
		} else {
			split = new Array();
			var ltr = "";
			var pos = 0;
			for ( var i = 0; i < mask.length; i++) {
				if (mask.substring(i, i + 1) != ltr) {
					ltr = mask.substring(i, i + 1);
				}
				if (split[pos])
					split[pos] += ltr;
				else
					split[pos] = ltr;
			}
		}
		var retVal = "";
		for ( var i = 0; i < split.length; i++) {
			var type = split[i].substring(0, 1).toLowerCase();
			var val;
			switch (type) {
			case "d":
				val = obj.getDate();
				break;
			case "m":
				val = obj.getMonth() + 1;
				break;
			case "y":
				val = obj.getFullYear();
				break;
			default:
				val = "";
			}
			if (split[i].length == 2)
				retVal += DateUtils.getTwoDigits(val);
			else
				retVal += val;

			if (i < split.length - 1)
				retVal += dash;
		}
		return retVal;
	},
	getDash : function(str) {
		var dashRegex = /[\.|\-|\/]/;
		var dash;
		if (dashRegex.test(str)) dash = dashRegex.exec(str)[0];
		else dash = "";
		return dash;
	},
	getTwoDigits : function(num) {
		num += "";
		if(num.length == 4) return num.substring(2);
		else return (num.length == 1) ? "0" + num : num;
	},
	getFourDigitYear : function(num) {
		if(!num) return new Date().getFullYear();
		num += "";
		if (num.length == 2) return ("20" + num);
		else if(num.length == 4) return num;
		else return null;
	}
}

var Url = ClassUtils.create();
Url.REGEX = /((^\w+:\/\/)([\w\d\.]*))?(\/?[^\?#]*)\??([^#]*)?#?(.*)?/;
ClassUtils.extend(Url.prototype, {
	initialize: function(url, params, secure) {
		this.protocol = url.replace(Url.REGEX, "$2");
		this.domain = url.replace(Url.REGEX, "$3");
		this.path = url.replace(Url.REGEX, "$4");
		var queryString = url.replace(Url.REGEX, "$5");
		var labelValue = queryString.split("&");
		this.params = {};
		for(var i=0; i<labelValue.length; i++) {
			var param = labelValue[i].split("=");
			var val = (param.length > 1) ? param[1] : null;
			if(/^(true|false)$/.test(val) || /^\d*\.?\d*$/.test(val)) val = eval(val);
			this.params[param[0]] = val;
		}
		this.id = url.replace(Url.REGEX, "$6");
		this.appendParams(params);
		// FIXME This should change the protocol, but for easier enabling, this will call the secure method
		this.secure = secure;
	},
	appendParams: function(params) {
		if(!params) return;
		for(var param in params) this.params[param] = params[param];
	},
	buildQueryString: function() {
		var qry = "";
		for(var nm in this.params) {
			if(nm == null || nm == "") continue;
			qry += "&" + escape(nm);
			var val = this.params[nm];
			if(val == null) val = "";
			else if(!/stateString/i.test(nm)) val = escape(val);
			qry += "=" + val;
		}
		return (qry.length > 0) ? qry.substring(1) : qry;
	},
	// IE doesn't like toString() for some reason
	build: function() {
		var url = "";
		if(this.domain) {
			if(!this.protocol) url += "http://";
			else url += this.protocol;
			url += this.domain;
		}
		if(url && !/^\//.test(this.path)) url += "/";
		url += this.path;
		var qry = this.buildQueryString();
		if(qry) url += "?" + qry;
		if(this.id) url += "#" + escape(this.id);
		if(this.secure) url = secure(url);
		// Explicitly test if it is false in case it was just not set
		else if(this.secure == false) url = unsecure(url);
		return url;
	},
	toString: function() {
		return this.build();
	}
});

var ConfigVehicle = ClassUtils.create();
ClassUtils.extend(ConfigVehicle.prototype, {
	initialize: function(modelYearId, acode, stateString, savedVehicleId) {
		this.modelYearId = modelYearId;
		this.acode = acode;
		this.stateString = stateString;
		this.savedVehicleId = savedVehicleId;
	},
	isValid: function() {
		return (this.modelYearId != null && this.modelYearId != "" && this.acode);
	},
	loadColorizedImage: function(imageSelector) {
		var _this = this;
		// It appears IE 6 doesn't like jQuery parameterizing the state string. Use this URL object instead
		var url = new Url(APP_CONTEXT + "/vehicleConfigurationAction/newCmd.do", {isPaint:true,modelYearId:this.modelYearId,aCode:this.acode,currentStateString:this.stateString});
		$.get(url.build(), null, function(xml) {
			var colorCode = $(xml).find("OPTION_GROUP[DESC='Primary Colors'] OPTION[STATE='S']").attr("CODE");
			colorCode = colorCode.replace(/_.*/, "");
			
			$(document).ready(function() {
				var img = $(imageSelector);
				ImageBuilder.getColorizedImage(_this.acode, null, img.attr("width"), img.attr("height"), {asPng:true,autoTrim:true,color:colorCode}, function(colorImage) {
					if(colorImage) $(imageSelector).attr("src", colorImage);
				});
			});
		}, "xml");
	},
	loadTitle: function(titleSelector) {
		$.getJSON(APP_CONTEXT + "/vehicleSelectionAction/getTrimInformation.do", {modelYearId:this.modelYearId,aCodeList:this.acode}, function(data) {
			$(document).ready(function() {
				$(titleSelector).text(data[0].modelDesc + " " + data[0].customTrimDesc);
			});
		});
	},
	// @param prev 'bp' if build and price or 'g' for my garage
	setBreadCrumb: function(prev, linkSelector, btnParentSelector) {
		var txt, url;
		if(prev == "bp") {
			url = new Url("/bp.html#summary", {modelYearId:this.modelYearId, aCode:this.acode, currentStateString:this.stateString});
			if(this.savedVehicleId) url.params.savedVehicleId = this.savedVehicleId;
			txt = "Build &amp; Price";
		} else if(prev == "g") {
			url = new Url("/my-garage.html", {vhclId:this.savedVehicleId});
			txt = "My Garage";
		}
		if(txt && url) {
			$(document).ready(function() {
				var crumb = $(linkSelector);
				crumb.html(txt.toUpperCase());
				crumb.attr("href", url.build());
				
				var btnParent = $(btnParentSelector);
				btnParent.prepend("<a href=\"" + url.build() + "\" class=\"alertContinue\">Continue</a>");
			});
		}
	}
});

function debug(msg) {
	if(window.console && console.firebug) {
        // Only works if Firebug is installed
        console.log(msg);
    } else {
    	var str;
    	if(msg != null && typeof msg == "object") {
    		str = "";
    		for(var prop in msg) {
    			str += ", " + prop + ":'" + msg[prop] + "'"
    		}
    		if(str.length == 0) str = "{}";
    		else str = "object = {" + str.substring(1) + " }";
    	} else {
    		str = msg;
    	}

        // Outputs to the Javascript shell if open
        if(this.Shell) print(str);
        // Only enable the following line for testing
        //else alert(str);
    }
}
function isDigit(num) {
	var string="1234567890";
	for(var i = 0; i < num.length; i++){
		if( string.indexOf(num.charAt(i)) == -1 ){
			return false;
		}
	}
	return true;
}

function isBlank(val){
	if(val==null){return true;}
	for(var i=0;i<val.length;i++) {
		if ((val.charAt(i)!=' ')&&(val.charAt(i)!="\t")&&(val.charAt(i)!="\n")&&(val.charAt(i)!="\r")){return false;}
		}
	return true;
}

// Extend jQuery
try {
	$.fn.indexOf = function(parentSelector, childSelector) {
		var thisElem = this[0];
		var index = -1;
		this.parent(parentSelector).children(childSelector).each(function(pos, elem) {
			if(thisElem == elem) {
				index = pos;
				return false;
			}
		});
		return index;
	}
} catch(e) {
	// Occurs if jQuery is not linked or the link is after this file
}