// This file depends on localized strings and functions currently located in:
// resources/new/<locale>/js/properties.js

var ALL_VALUE = "";
var ALL_VALUES_RE = /^(All||Any)?$/i;
var DEFAULT_ALL_OPTION = new Option(ANY, ALL_VALUE, true, true);
// Chained basic search criteria: used for dropdown names, HTML div names, and InvSearch property names
var LOCATION_NAME = "location";
var LOCATION_ROW = LOCATION_NAME + "Row";
var MAKE_NAME = "make";
var MAKE_ROW = MAKE_NAME + "Row";
var MODEL_NAME = "model";
var MODEL_ROW = MODEL_NAME + "Row";
var TRIM_NAME = "trim";
var TRIM_ROW = TRIM_NAME + "Row";
var VEHICLETYPE_NAME = "bodyType";
var VEHICLETYPE_ROW = VEHICLETYPE_NAME + "Row";
// Other basic search criteria: used for dropdown names, HTML div names, and InvSearch property names
var CERTIFIED_NAME = "certified";
var MAXODOMETER_NAME = "miles";
var MAXPRICE_NAME = "maxPrice";
var MAXYEAR_NAME = "maxYear";
var MINYEAR_NAME = "minYear";
// Advanced search criteria: used for dropdown names, HTML div names, and InvSearch property names
var COLOREXTERIOR_NAME = "colorExterior";
var COLORINTERIOR_NAME = "colorInterior";
var COLOROEM_NAME = "colorOEM";
var CYLINDERS_NAME = "cylinders";
var DRIVEWHEELS_NAME = "drivetrain";
var ENGINE_NAME = "engine";
var FUELTYPE_NAME = "fuelType";
var LOCATIONSTATE_NAME = "locationState";
var STOCKNUMBER_NAME = "stockNumber";
var TRANSMISSION_NAME = "transmission";
var TRANSMISSIONSPEEDS_NAME = "transmissionSpeeds";
var VIN_NAME = "vin";
// Handy collections of fields
var CHAINED_FIELDS = [LOCATION_NAME, VEHICLETYPE_NAME, MAKE_NAME, MODEL_NAME, TRIM_NAME];
var RANGE_FIELDS = [MAXODOMETER_NAME, MAXPRICE_NAME, MINYEAR_NAME, MAXYEAR_NAME];
var ADVANCED_FIELDS = [COLOREXTERIOR_NAME, COLORINTERIOR_NAME, COLOROEM_NAME, CYLINDERS_NAME, DRIVEWHEELS_NAME, ENGINE_NAME, FUELTYPE_NAME, LOCATIONSTATE_NAME, TRANSMISSION_NAME, TRANSMISSIONSPEEDS_NAME];
var allFields = null;

/** Encapsulates everything public in the search API. **/
function InvSearch(jsDataObj) {
	this.jsdata = jsDataObj;
	this.searchForm = null;
	this.paramsForm = null;
	this.advancedFactory = null;
	this.isAdvancedDisplayed = false;
	this.hasAdvancedSearch = false;
}
InvSearch.prototype.init = function(searchForm, priceType, paramsForm) {
	this.searchForm = (searchForm) ? searchForm : document.forms[0];
	this.paramsForm = (paramsForm) ? paramsForm : ((document.forms.searchParamsForm) ? document.forms.searchParamsForm : null);
	this.advancedFactory = new AdvancedValueFactory(this, this.searchForm, priceType);
	// verify the jsdata object has data
	var hasData = false;
	for (var prop in this.jsdata){
		hasData = true;
		break;
	}
	// if jsdo has no data, disable all fields and display "no inventory" message on search pages
	if (!hasData) {
		this.jsdata = new Object();
		if (document.getElementById("noInventoryTextContainer")) document.getElementById("noInventoryTextContainer").style.display = "block";
		this.disableFields();
		return;
	}
	// register presence of advanced search
	this.advancedFactory.hasAdvancedSearch = (document.getElementById("newAdvancedSearchContainer") || document.getElementById("preownedAdvancedSearchContainer")) ? true : false;
	// initialize search criteria
	this.initCriteria();
}
InvSearch.prototype.initCriteria = function() {
	// initialize allFields to have advanced search criteria if necessary
	allFields = (this.advancedFactory.hasAdvancedSearch) ? CHAINED_FIELDS.concat(RANGE_FIELDS.concat(ADVANCED_FIELDS)) : CHAINED_FIELDS.concat(RANGE_FIELDS);
	// populate properties with any preselected values, or "all"
	this.criteriaList = new CriteriaList();
	for (var i=0; i<allFields.length; i++) {
		if (i < CHAINED_FIELDS.length) this.criteriaList.add(new InvCriteria(this, allFields[i], this.getFieldValue(allFields[i]), i));
		else this.criteriaList.add(new InvAdvancedCriteria(this, allFields[i], this.getFieldValue(allFields[i]), i));
	}
	// set up chained criteria customized "All" options
	this.criteriaList[LOCATION_NAME].setAllOption(new Option(i18nLabels.getLocation(), ALL_VALUE, true, true));
	this.criteriaList[VEHICLETYPE_NAME].setAllOption(new Option(i18nLabels.getSearchType(), ALL_VALUE, true, true));
	this.criteriaList[MAKE_NAME].setAllOption(new Option(i18nLabels.getMake(), ALL_VALUE, true, true));
	this.criteriaList[MODEL_NAME].setAllOption(new Option(i18nLabels.getModel(), ALL_VALUE, true, true));
	this.criteriaList[TRIM_NAME].setAllOption(new Option(i18nLabels.getTrim(), ALL_VALUE, true, true));
	// set up customized sorting rules
	for (var j=0; j<RANGE_FIELDS.length; j++) this.criteriaList[RANGE_FIELDS[j]].setIsPreSorted(true);
	if (this.advancedFactory.hasAdvancedSearch) {
		this.criteriaList[TRANSMISSIONSPEEDS_NAME].setComparator(numberComparator);
		this.criteriaList[CYLINDERS_NAME].setComparator(numberComparator);
	}
	// hide the location dropdown if there is only one location
	if (document.getElementById(LOCATION_ROW)) {
		var locationValues = toArray(this.jsdata);
		if (locationValues.length <= 1) document.getElementById(LOCATION_ROW).style.display = "none";
	}
	// set up initial location values, and kick off the chained population
	var isPreselect = (this.paramsForm) ? true : false;
	this.criteriaList[LOCATION_NAME].nodeList = [this.jsdata];
	this.criteriaList[LOCATION_NAME].addToValueMap(this.jsdata);
	this.criteriaList[LOCATION_NAME].resetNodeLists(true);
	this.criteriaList[LOCATION_NAME].set(this.searchForm, new Option(i18nLabels.getLocation(), ALL_VALUE, true, true));
}

/* Call in the onClick of the location dropdown */
InvSearch.prototype.setLocation = function(newValue) {
	this.criteriaList[LOCATION_NAME].value = newValue;
	this.criteriaList[LOCATION_NAME].resetNodeLists(false);
	this.criteriaList[VEHICLETYPE_NAME].set(this.searchForm);
}
/* Call in the onClick of the vehicleType dropdown */
InvSearch.prototype.setVehicleType = function(newValue) {
	this.criteriaList[VEHICLETYPE_NAME].value = newValue;
	this.criteriaList[VEHICLETYPE_NAME].resetNodeLists(false);
	this.criteriaList[MAKE_NAME].set(this.searchForm);
}
/* Call in the onClick of the make dropdown */
InvSearch.prototype.setMake = function(newValue) {
	this.criteriaList[MAKE_NAME].value = newValue;
	this.criteriaList[MAKE_NAME].resetNodeLists(false);
	this.criteriaList[MODEL_NAME].set(this.searchForm);
}
InvSearch.prototype.getMake = function() {
	return this.criteriaList[MAKE_NAME].value;
}
/* Call in the onClick of the model dropdown */
InvSearch.prototype.setModel = function(newValue) {
	this.criteriaList[MODEL_NAME].value = newValue;
	this.criteriaList[MODEL_NAME].resetNodeLists(false);
	this.criteriaList[TRIM_NAME].set(this.searchForm);
}
InvSearch.prototype.getModel = function() {
	return this.criteriaList[MODEL_NAME].value;
}
/* Call in the onClick of the trim dropdown */
InvSearch.prototype.setTrim = function(newValue) {
	this.criteriaList[TRIM_NAME].value = newValue;
	this.criteriaList[TRIM_NAME].resetNodeLists(false);
	this.advancedFactory.setAll();
}
InvSearch.prototype.getTrim = function() {
	return this.criteriaList[TRIM_NAME].value;
}
/* Call this in the onClick of the minYear dropdown */
InvSearch.prototype.setMinYear = function(newValue) {
	this.criteriaList[MINYEAR_NAME].value = newValue;
	var maxYearCriteria = this.criteriaList[MAXYEAR_NAME];
	maxYearCriteria.valueMap = {};
	var startingIndex = (this.searchForm[MINYEAR_NAME].selectedIndex == 0) ? this.searchForm[MINYEAR_NAME].options.length - 1 : this.searchForm[MINYEAR_NAME].selectedIndex;
	for (var i=1; i<=startingIndex; i++) maxYearCriteria.valueMap[this.searchForm[MINYEAR_NAME].options[i].value] = this.searchForm[MINYEAR_NAME].options[i].text;
	if ((maxYearCriteria.value != ALL_VALUE) && !maxYearCriteria.valueMap[maxYearCriteria.value]) maxYearCriteria.value = ALL_VALUE;
	maxYearCriteria.setOptions(this.searchForm[MAXYEAR_NAME], maxYearCriteria.getOptions(), new Option(ANY, ALL_VALUE, true, true));
}
/** Submits the search form. **/
InvSearch.prototype.submitForm = function() {
	try {
		if (window.validateVehicleSearch) validateVehicleSearch(this.searchForm);
		this.searchForm.submit();
	} catch (err) {
		handleException(err);
	} finally {
		return false;
	}
}
/** Disable all the fields in the search form. **/
InvSearch.prototype.disableFields = function() {
    for (var i=0; i<this.searchForm.elements.length; i++) this.searchForm.elements[i].disabled = true;
}
/** Get the initial value for a field: returns a preselected value if one exists,
	ALL_VALUE if the preselected value matches one of the known "all" values,
	and ALL_VALUE as a last resort. **/
InvSearch.prototype.getFieldValue = function(fieldName) {
	if (!this.paramsForm) return ALL_VALUE;
	if (!this.paramsForm[fieldName]) return ALL_VALUE;
	if (ALL_VALUES_RE.test(this.paramsForm[fieldName].value)) return ALL_VALUE;
	return this.paramsForm[fieldName].value;
}

/** Encapsulates one criteria in the inventory search dropdown. **/
function InvCriteria(invSearch, name, value, index) {
	this.invSearch = invSearch;
	this.name = name;
	this.value = value;
	this.index = index;
	this.option = null;
	this.valueMap = {};
	this.nodeList = [];
	this.isPreSorted = false;
	return this;
}
/** Add values in an array to this criteria's valueMap. **/
InvCriteria.prototype.addToValueMap = function(node) {
	for (var key in node) this.valueMap[key] = node[key][0];
}
/** Do not populate model/trim dropdowns till a make has been selected. **/
InvCriteria.prototype.doPopulate = function() {
	return (!(((this.name == MODEL_NAME) && (this.invSearch.criteriaList[MAKE_NAME].value == ALL_VALUE)) || ((this.name == TRIM_NAME) && (this.invSearch.criteriaList[MODEL_NAME].value == ALL_VALUE))));
}
InvCriteria.prototype.doTruncate = function(optionsLength) { return (((this.name == MAKE_NAME) || (this.name == MODEL_NAME)) && (optionsLength == 1)); }
/** Create list of Options for this criteria, from a key/value map. **/
InvCriteria.prototype.getOptions = function() {
	var optionList = new Array();
	for (var key in this.valueMap) {
		if (key.trim()) optionList[optionList.length] = new Option(this.valueMap[key], key);
	}
	return optionList;
}
/** Reset all fields holding selection and state values. **/
InvCriteria.prototype.reset = function() {
	this.nodeList = [];
	this.valueMap = {};
	this.value = ALL_VALUE;
}
/** Call this once per user click, for the criteria selected.  Initiate the node recursion. **/
InvCriteria.prototype.resetNodeLists = function(isPreselect) {
	// reset the nodeList of every criteria below the one being set (the one the user clicked).
	if (!isPreselect) this.invSearch.criteriaList.resetCriteria(allFields[this.index + 1]);
	this.invSearch.advancedFactory.clearAggregateMaps();
	// start recursing through each node in the nodeList, to set nodeLists down the food chain.
	for (var j=0; j<this.nodeList.length; j++) {
		this.recurseNodes(this.nodeList[j], (this.index + 1));
	}
	this.invSearch.advancedFactory.setCalculatedMaps();
}
/** Create a list of nodes for each criteria following the one clicked, filtered by selected values.
	Optionally populate valueMaps with the node data.  Also initiate advanced criteria population. **/
InvCriteria.prototype.recurseNodes = function(node, nextIndex) {
	var nextCriteria = this.invSearch.criteriaList[CHAINED_FIELDS[nextIndex]];
	if (this.value == ALL_VALUE) {
		for (var key in node) {
			if (nextCriteria) {
				nextCriteria.nodeList[nextCriteria.nodeList.length] = node[key][1];
				if (nextCriteria.doPopulate()) nextCriteria.addToValueMap(node[key][1]);
				nextCriteria.recurseNodes(node[key][1], (nextIndex + 1));
			} else {
				this.invSearch.advancedFactory.addToValueMaps(node[key]);
			}
		}
	} else if (node[this.value]) {
		if (nextCriteria) {
			nextCriteria.nodeList[nextCriteria.nodeList.length] = node[this.value][1];
			if (nextCriteria.doPopulate()) nextCriteria.addToValueMap(node[this.value][1]);
			nextCriteria.recurseNodes(node[this.value][1], (nextIndex + 1));
		} else {
			this.invSearch.advancedFactory.addToValueMaps(node[this.value]);
		}
	}
}
/** Populate this criteria's dropdown with the values in its valueMap; call set for the next criteria in the chain. **/
InvCriteria.prototype.set = function(someForm) {
	// the requested value doesn't exist in the list of options (as from a bogus preselect)
	if ((this.value != ALL_VALUE) && !this.valueMap[this.value]) {
		this.value = ALL_VALUE;
		// reset all criteria under this one to ALL
		if (this.invSearch.criteriaList.resetCriteria(allFields[this.index + 1]));
	}
	// retrieve the list of dropdown options
	var options = (this.isPreSorted) ? this.getOptions() : this.getOptions().sort(optionComparator);
	// make and model should not have "all" option if list is length 1
	var allOption = (this.doTruncate(options.length)) ? null : this.option;
	if (this.doTruncate(options.length)) {
		this.value = options[0].value;
		// manually create next criteria's valueMap from its nodeList, as this will not have been done in recurseNodes()
		var nextCriteria = this.invSearch.criteriaList[CHAINED_FIELDS[this.index + 1]];
		for (var i=0; i<nextCriteria.nodeList.length; i++) nextCriteria.addToValueMap(nextCriteria.nodeList[i]);
	}
	// put this criteria's options in the appropriate dropdown
	this.setOptions(someForm[this.name], options, allOption);
	// set the next dropdown in the chained hierarchy, except trim which sets all advanced search criteria
	if ((this.index + 1) < CHAINED_FIELDS.length) this.invSearch.criteriaList[CHAINED_FIELDS[this.index + 1]].set(someForm);
	else this.invSearch.advancedFactory.setAll();
}
/** Set a custom "All" option for this criteria. **/
InvCriteria.prototype.setAllOption = function(option) { this.option = option; }
/** Set to true when valueMap is already in desired sort order (e.g. year). **/
InvCriteria.prototype.setIsPreSorted = function(isPreSorted) { this.isPreSorted = isPreSorted; }
/** Set Options for criteria's dropdown.  Optionally pass an "all" option if required. **/
InvCriteria.prototype.setOptions = function(dropdown, options, allOption) {
	if (!dropdown) return;
	if(dropdown.nodeName.toLowerCase() == 'input') return;
	dropdown.length = 0;
	var selectedIndex = 0;
	if (allOption) dropdown.options[0] = new Option(allOption.text, allOption.value);
	if (options && options.length) {
		for (var i=0; i<options.length; i++) {
			if (options[i].value == this.value) selectedIndex = dropdown.options.length;
			dropdown.options[dropdown.options.length] = options[i];
		}
	} else if (dropdown.options[0].value != ALL_VALUE) {
		dropdown.options[0] = new Option(ANY, ALL_VALUE, true, true);
	}
	dropdown.options[selectedIndex].selected = true;
}

/** Subclass of InvCriteria, for calculated and advanced search criteria. **/
function InvAdvancedCriteria(invSearch, name, value, index) {
	this.invSearch = invSearch;
	this.name = name;
	this.value = value;
	this.index = index;
	this.valueMap = {};
	this.isPreSorted = false;
	this.comparator = alphaComparator;
	return this;
}
InvAdvancedCriteria.prototype = new InvCriteria;
/** Add values in an array to this criteria's valueMap. **/
InvAdvancedCriteria.prototype.addToValueMap = function(criteriaArray) {
	var valueArray = criteriaArray[AdvancedValueFactory.DATA_INDEXES[this.name]];
	for (var i=0; i<valueArray.length; i++) this.valueMap[valueArray[i]] = valueArray[i];
}
var myTemp = 0;
/** Create list of Options for this criteria, from a key/value map. **/
InvAdvancedCriteria.prototype.getOptions = function() {
	var optionList = new Array();
	if (!this.isPreSorted) {
		var valueList = toArray(this.valueMap).sort(this.comparator);
		
		for (var i=0; i<valueList.length; i++) {
			if (valueList[i].trim()) optionList[optionList.length] = new Option(valueList[i], valueList[i]);
		}
	} else {
		for (var key in this.valueMap) {
			if (key.trim()) optionList[optionList.length] = new Option(this.valueMap[key], key);
		}
	}
	return optionList;
}
/** Populate this criteria's dropdown with the values in its valueMap **/
InvAdvancedCriteria.prototype.set = function(someForm) {
	this.setOptions(someForm[this.name], this.getOptions(), new Option(ANY, ALL_VALUE, true, true));
}
/** Set a comparator to use when sorting; default is alpha. **/
InvAdvancedCriteria.prototype.setComparator = function(comparator) { this.comparator = comparator; }

/** Parses the jsdo's calculated and advanced value arrays, and populates the corresponding Criteria valueMaps. **/
function AdvancedValueFactory(invSearch, searchForm, priceType) {
	this.invSearch = invSearch;
	this.searchForm = searchForm;
	this.priceType = priceType;
	this.trimNodesArray = [];
	this.hasAdvancedSearch = false;
	this.priceType = (priceType) ? priceType : "priceRetail";
}
// dataIndexes is used to access value data from the array passed in the jsdata object.
// Some of the prices are double mapped, as the config can save 2 different keys for the same data.
AdvancedValueFactory.DATA_INDEXES = {
	"priceDiscountMin":1, "price2Min":1, "discountPriceMin":1,
	"priceDiscountMax":2, "price2Max":2, "discountPriceMax":2,
	"priceInternetMin":3, "internet_priceMin":3, "internetPriceMin":3,
	"priceInternetMax":4, "internet_priceMax":4, "internetPriceMax":4,
	"priceInvoiceMin":5, "invoice_priceMin":5, "invoicePriceMin":5,
	"priceInvoiceMax":6, "invoice_priceMax":6, "invoicePriceMax":6,
	"priceMsrpMin":7, "msrpMin":7,
	"priceMsrpMax":8, "msrpMax":8,
	"priceRetailMin":9, "priceMin":9, "retailPriceMin":9,
	"priceRetailMax":10, "priceMax":10, "retailPriceMax":10,
	"priceWholesaleMin":11, "wholesalePriceMin":11,
	"priceWholesaleMax":12, "wholesalePriceMax":12,
	"minYear":13,
	"maxYear":14,
	"minOdometer":15, 
	"maxOdometer":16,
	"engine":17,
	"transmission":18,
	"transmissionSpeeds":19,
	"drivetrain":20,
	"colorOEM":21,
	"colorExterior":22,
	"colorInterior":23,
	"fuelType":24,
	"cylinders":25,
	"locationState":26
}
/** Collect unique year, odometer and price values while recursing through data nodes. **/
AdvancedValueFactory.prototype.addToAggregateMap = function(sourceValueArray, targetValueMap) {
	if (sourceValueArray && sourceValueArray.length) targetValueMap[sourceValueArray[0]] = sourceValueArray[0];
}
/** Called from InvCriteria.recurseNodes(): parses an array of criteria values, adds them to the correct criteria maps. **/
AdvancedValueFactory.prototype.addToValueMaps = function(criteriaArray) {
	if (!criteriaArray) return;
	if (this.searchForm[MINYEAR_NAME] && this.searchForm[MAXYEAR_NAME]) {
		this.addToAggregateMap(criteriaArray[AdvancedValueFactory.DATA_INDEXES["minYear"]], this.years);
		this.addToAggregateMap(criteriaArray[AdvancedValueFactory.DATA_INDEXES["maxYear"]], this.years);
	}
	if (this.searchForm[MAXODOMETER_NAME]) {
		this.addToAggregateMap(criteriaArray[AdvancedValueFactory.DATA_INDEXES["minOdometer"]], this.odometers);
		this.addToAggregateMap(criteriaArray[AdvancedValueFactory.DATA_INDEXES["maxOdometer"]], this.odometers);
	}
	if (this.searchForm[MAXPRICE_NAME]) {
		this.addToAggregateMap(criteriaArray[AdvancedValueFactory.DATA_INDEXES[this.priceType + "Min"]], this.prices);
		this.addToAggregateMap(criteriaArray[AdvancedValueFactory.DATA_INDEXES[this.priceType + "Max"]], this.prices);
	}
	if (this.hasAdvancedSearch) {
		for (var i=0; i<ADVANCED_FIELDS.length; i++) this.invSearch.criteriaList[ADVANCED_FIELDS[i]].addToValueMap(criteriaArray);
	}
}
/** Reset maps containing unique year, odometer and price values. **/
AdvancedValueFactory.prototype.clearAggregateMaps = function() {
	this.years = {};
	this.odometers = {};
	this.prices = {};
}
/** Set options for calculated and advanced search criteria. **/
AdvancedValueFactory.prototype.setAll = function() {
	for (var i=CHAINED_FIELDS.length; i<allFields.length; i++) this.invSearch.criteriaList[allFields[i]].set(this.searchForm);
}
/** Execute year, odometer and price population **/
AdvancedValueFactory.prototype.setCalculatedMaps = function() {
	if (this.searchForm[MINYEAR_NAME] && this.searchForm[MAXYEAR_NAME]) this.setYearMaps();
	if (this.searchForm[MAXODOMETER_NAME]) this.setMilesMap();
	if (this.searchForm[MAXPRICE_NAME]) this.setPriceMap();
}
/** Populate the miles criteria's valueMap **/
AdvancedValueFactory.prototype.setMilesMap = function() {
	var allOdometerValues = toArray(this.odometers).sort(numberComparator);
	// empty values will be sorted to the start of the array; we need to get rid of them.
	var trueStartIndex = 0;
	for (trueStartIndex; trueStartIndex<allOdometerValues.length; trueStartIndex++) {
		if (allOdometerValues[trueStartIndex].trim()) break;
	}
	var odometerValues = allOdometerValues.slice(trueStartIndex, allOdometerValues.length);
	var increment = 5000;
	var milesCriteria = this.invSearch.criteriaList[MAXODOMETER_NAME];
	// when no miles data, or minMiles is less than the increment amount, use 0
	var minMiles = (odometerValues.length && (parseInt(odometerValues[0]) >= increment)) ? parseInt(odometerValues[0]) : 0;
	// when no miles data, use increment * 9; when maxMiles is less than minMiles, use minMiles
	var maxMiles = (odometerValues.length) ? ((parseInt(odometerValues[odometerValues.length - 1]) < minMiles) ? minMiles : parseInt(odometerValues[odometerValues.length - 1])) : increment * 9;
	var firstOptionMiles =  (Math.floor(minMiles / increment) * increment) + increment;
	var lastOptionMiles = (Math.floor(maxMiles / increment) * increment) + increment;
	for (var miles=firstOptionMiles; miles<=lastOptionMiles; miles+=increment) milesCriteria.valueMap[miles] = UP_TO + getFormattedNumber(miles);
}
/** Populate the price criteria's valueMap **/
AdvancedValueFactory.prototype.setPriceMap = function() {
	var allPriceValues = toArray(this.prices).sort(numberComparator);
	// empty values will be sorted to the start of the array; we need to get rid of them.
	var trueStartIndex = 0;
	for (trueStartIndex; trueStartIndex<allPriceValues.length; trueStartIndex++) {
		if (allPriceValues[trueStartIndex].trim()) break;
	}
	var priceValues = allPriceValues.slice(trueStartIndex, allPriceValues.length);
	var increment = 5000;
	var priceCriteria = this.invSearch.criteriaList[MAXPRICE_NAME];
	// when minPrice is less than the increment amount, use 0
	var minPrice = (priceValues.length && (parseInt(priceValues[0]) >= increment)) ? parseInt(priceValues[0]) : 0;
	// when no maxPrice data, use increment * 11; when maxPrice is less than minPrice, use minPrice
	var maxPrice = (priceValues.length) ? ((parseInt(priceValues[priceValues.length - 1]) < minPrice) ? minPrice : parseInt(priceValues[priceValues.length - 1])) : increment * 11;
	var firstOptionPrice =  (Math.floor(minPrice / increment) * increment) + increment;
	var lastOptionPrice = (Math.floor(maxPrice / increment) * increment) + increment;
	for (var price=firstOptionPrice; price<=lastOptionPrice; price+=increment) priceCriteria.valueMap[price] = UP_TO + payCalc.getFormattedPrice(price);
}
/** Populate the year criterias' valueMaps **/
AdvancedValueFactory.prototype.setYearMaps = function() {
	var allYearValues = toArray(this.years).sort(numberComparator);
	// empty values will be sorted to the start of the array; we need to get rid of them.
	var trueStartIndex = 0;
	for (trueStartIndex; trueStartIndex<allYearValues.length; trueStartIndex++) {
		if (allYearValues[trueStartIndex].trim()) break;
	}
	var yearValues = allYearValues.slice(trueStartIndex, allYearValues.length);
	var minYear = (yearValues.length) ? parseInt(yearValues[0]) : 0;
	var maxYear = (yearValues.length) ? parseInt(yearValues[yearValues.length - 1]) : 0;
	var minYearCriteria = this.invSearch.criteriaList[MINYEAR_NAME];
	var maxYearCriteria = this.invSearch.criteriaList[MAXYEAR_NAME];
	if (!minYear || !maxYear) {
		minYearCriteria.setOptions(this.searchForm[MINYEAR_NAME], null, new Option(ANY, ALL_VALUE, true, true));
		this.invSearch.criteriaList[MAXYEAR_NAME].setOptions(this.searchForm[MAXYEAR_NAME], null, new Option(ANY, ALL_VALUE, true, true));
		return;
	}
	for (var year=maxYear; year>=minYear; year--) {
		minYearCriteria.valueMap[year] = year;
		maxYearCriteria.valueMap[year] = year;
	}
}

/** Vehicle photo urls, used by taxonomy data */
VehiclePhotoUtility = {
	thumbnailUrlIndex : 1,
	photoUrlIndex : 2,
	jsdata : null,
	trimNode : null,
	vehicleSearch : null,
	init : function(vehicleSearch) {
		VehiclePhotoUtility.vehicleSearch = vehicleSearch;
	},
	getTrimNode : function() {
		if (!VehiclePhotoUtility.vehicleSearch || !VehiclePhotoUtility.vehicleSearch.jsdata) return null;
		var dataNode = VehiclePhotoUtility.vehicleSearch.jsdata[""][1][""][1];
		var make = VehiclePhotoUtility.vehicleSearch.criteriaList[MAKE_NAME].value;
		var model = VehiclePhotoUtility.vehicleSearch.criteriaList[MODEL_NAME].value;
		var trim = VehiclePhotoUtility.vehicleSearch.criteriaList[TRIM_NAME].value;
		if (!make || !model || !trim) return null;
		if (!dataNode[make] || !dataNode[make][1][model] || !dataNode[make][1][model][1][trim]) return null;
		return dataNode[make][1][model][1][trim];
	},
	getPhotoUrl : function() {
		var trimNode = VehiclePhotoUtility.getTrimNode();
		if (!trimNode || !trimNode[VehiclePhotoUtility.photoUrlIndex]) return "";
		return trimNode[VehiclePhotoUtility.photoUrlIndex];
	},
	getThumbnailUrl : function() {
		var trimNode = VehiclePhotoUtility.getTrimNode();
		if (!trimNode || !trimNode[VehiclePhotoUtility.thumbnailUrlIndex]) return "";
		return trimNode[VehiclePhotoUtility.thumbnailUrlIndex];
	}
}

/** Associative array of all criteria in the search form. **/
function CriteriaList() {}
/** Add a criteria to the list. **/
CriteriaList.prototype.add = function(criteria) {
	if (!criteria.name) return;
	this[criteria.name] = criteria;
}
/** Cycle through each criteria in the list and reset its values **/
CriteriaList.prototype.resetCriteria = function(startName) {
	for (var i=this[startName].index; i<allFields.length; i++) this[allFields[i]].reset();
}

/** Case-insensitive alpha sort, e.g. White, white, Yellow **/
function alphaComparator(a, b) {
	if (a.toLowerCase() > b.toLowerCase()) return 1;
	else if (a.toLowerCase() < b.toLowerCase()) return -1;
	else return 0;
}
/** Numeric sort, e.g. 1, 2, 10 **/
function numberComparator(n1, n2) {
	if(isNaN(n1) || isNaN(n2)){ return false; }
	else{ return n1 - n2; }
}
/** Case-insensitive alpha sort on Options **/
function optionComparator(a, b) { return alphaComparator(a.text, b.text); }
/** Trim whitespace off the start and end of a string **/
String.prototype.trim = function() { return this.replace(/^\s*([^\s]*)\s$/g, "$1"); }
/** Return an array of keys in a map **/
function toArray(map) {
	var array = [];
	for (var key in map) array[array.length] = key;
	return array;
}

