/**
 * The search widget for specifying search criteria and displaying error/warning messages generates
 * by the search.
 * @extends HD.Widget
 * 
 * @param {Object} config configuration object containing configurable properties 
 * @param {HD.REO.Model} model the model object, signals model events
 * @param {HD.REO.View} view the view object, facilitates the communication of events between widgets 
 */
HD.REO.View.Search = function(config, model, view) {
	this.observers = [];
	this.config = config || {};
	
	if(!this.config.pageSize) {
		this.config.pageSize = 15;
	}
	
	if(!this.config.pageNumber) {
		this.config.pageNumber = 1;
	}
	
	if(!this.config.sortType) {
		this.config.sortType = HD.REO.template.sortByDate;
	}
    
	this.pageNumber = this.config.pageNumber;
	this.pageSize = this.config.pageSize;
	this.sortType = this.config.sortType;
	
	if(model != null) {
		this.model = model;
		this.model.addObserver(this);
	}
	
	if(view != null) {
		this.view = view; 
	    this.view.addObserver(this);
	}
	
	//this.getStatesWithNoRe();
	
	HD.util.Analytics.track(HD.REO.analytics.getSearchPage(HD.REO.experienceLevel));
};

/**
 * Template object containing overrideable methods and properties.
 * @static
 */
HD.REO.View.Search.template = {
	noReStatesUrl : 	'/wcm/en_US/Media/xml/Travel_Search/remapping.hdxml',
	noReStatesUrlLocal :'/en_US/Content/Advanced_Features/riders_edge_online/remapping.hdxml',
	nearbyStatesTooltip:'Each state has different rules about motorcycle licenses and training.  It may be in your best interest to take a class in the state you are licensed to drive.  Please contact your state\' DMV to find out more about completing a course out-of-state.',
	fullClassesTooltip :'By checking this box, your search results will show all classes, including classes with no open seats.',
	noReStates : 		'HI,OK,ND,WY,NY,RI,DC,VT',
	//noReStates : 		'',
	formId :			'hdLocSearch',
	zip : 				'ZIP',
	city : 				'City',
	state : 			'State',
	initial : 			'<div style="text-align: center;" class="hdSearchInitialMessage">' +
							'<h2>Start Search for Classes Here.</h2>' +
							'<p>Enter your desired location and distance, then click Search Classes.<br/>' +
							'You can always change you or search options to find the date and location that works best for you.</p>' +
						'</div>',
	yesClasses :		'<div class="hdNumOfClasses"><h2>_NUM_OF_CLASSES classes match this search</h2></div>',
	yesOneClass :		'<div class="hdNumOfClasses"><h2>_NUM_OF_CLASSES class matches this search</h2></div>',
	dealerClasses :		'<div class="hdNumOfClasses"><h3>There are _NUM_OF_CLASSES classes scheduled at _DEALER_NAME</h3></div>',
	dealerOneClass :	'<div class="hdNumOfClasses"><h3>There is _NUM_OF_CLASSES class scheduled at _DEALER_NAME</h3></div>',
	otherDealers :		'<div class="hdSuggestion hdMessage"><h3>Or, we invite you to reach out to the Rider\'s Edge Program Manager at any of these participating Harley-Davidson dealerships to talk more about learning to ride.</h3></div>',

	message : {
		zipAndCityState		: 'Please note, you can only search by ZIP Code or by city and state, but not both.',
		noLocation 			: 'Sorry, you must enter a valid ZIP code or city and state to search classes.',
		noState 			: 'Sorry, no state was selected.',
		invalidZip 			: 'Sorry, we could not find the ZIP code you searched for.',
		invalidCity 		: 'Sorry, we could not find the city you searched for.',
		noneInState			: 'Sorry, this course is not available in _STATE.',
		noCourses1 			: 'Sorry, no classes are scheduled in your state within _DISTANCE miles of _LOCATION in the next _DAYS days.',
		noCourses2 			: 'Sorry, no classes are scheduled in your state or nearby states within _DISTANCE miles of _LOCATION in the next _DAYS days.',
		noCourses3 			: 'Sorry, no classes are scheduled in your state within _DISTANCE miles of _LOCATION.',
		noCourses4 			: 'Sorry, no classes are scheduled in your state or nearby states within _DISTANCE miles of _LOCATION.',
		msf 				: 'You may want to consider taking a Motorcycle Safety Foundation Course.',
		contactDealer 		: 'No classes are scheduled at this time. For more information on future class availability please contact _NAME at <a href="mailto:_EMAIL">_EMAIL</a> or _PHONE.'
	},
	
	suggestions : {
		usedZip				: 'We\'ve automatically searched for classes near the ZIP code you entered.',
		locationNearYou		: 'Try searching for a location near you',
		selectState			: 'Try searching again with a state selected',
		enterZip			: 'Or, try searching by ZIP code instead',
		verifyZip			: 'Verify the ZIP code and try searching again',
		enterCityState		: 'Or, try searching by city and state instead',
		checkSpelling		: 'Check the spelling and try searching again',
		maxDistanceAndDates	: 'We’ve automatically increased your search to include the maximum distance and date range',
		includeNearbyStates : 'You may also wish to select "include nearby states" and search again',
		maxDistance 		: 'We’ve automatically increased your search to include the maximum distance',
		maxDates 			: 'We’ve automatically increased your search to include all future dates',
		msf 				: 'The Motorcycle Safety Foundation Basic Rider Course is offered in many states. Visit the Motorcycle Safety Foundation website for additional course offerings.',
		msfLink 			: '<a class="msfCourse" href="javascript:void(0)"><span>Find MSF Courses</span></a>'
	},
	
	getZipAndCityStateWarning : function() {
		return this.getMessage("hdWarning", this.message.zipAndCityState, [this.suggestions.usedZip]);
	},
	
	getNoLocationError : function() {
		return this.getMessage("hdError", this.message.noLocation, [this.suggestions.locationNearYou]);
	},
	
	getNoStateError : function() {
		return this.getMessage("hdError", this.message.noState, [this.suggestions.selectState, this.suggestions.enterZip]);
	},
	
	getInvalidZipError : function() {
		return this.getMessage("hdError", this.message.invalidZip, [this.suggestions.verifyZip, this.suggestions.enterCityState]);
	},
	
	getInvalidCityError : function() {
		return this.getMessage("hdError", this.message.invalidCity, [this.suggestions.checkSpelling, this.suggestions.enterZip]);
	},
	
	getNoneInStateError : function(state) {
		return this.getMessage("hdError", this.message.noneInState.replace(/_STATE/, state), [this.suggestions.includeNearbyStates]);
	},
	
	getMsfMessage : function() {
		return ['<div class="hdSuggestion hdMessage">' ,
					'<h3>', this.message.msf ,'</h3>' ,
					'<p>', this.suggestions.msf ,'</p>' ,
					this.suggestions.msfLink ,
				'</div>'].join('');
	},
	
	getContactDealerMessage : function(dealer) {
		return this.message.contactDealer.replace(/_NAME/, dealer.name).
					replace(/_PHONE/, HD.REO.formatPhone(dealer.phone)).
					replace(/_EMAIL/g, dealer.contactEmail);
	},
	
	getCourseResultsMessage : function(request, response, dealer, isDealerSearch) {
		var messages = [];
		var common = HD.util.Common;
		
		if(response.errors.length > 0) {
			if(response.errors[0].errorMessage == "invalidZipCode") {
				return this.getInvalidZipError();
			}
		}
		
		if(response.statusCode == HD.REO.ENROLLMENT_STATUS_CODES.MAPPOINT_VALIDATION_FAILED) {
			return this.getNoLocationError();
		}
		
		var cityZipMessage = null;
		if(common.hasValue(request.zip) && common.hasValue(request.city) && common.hasValue(request.state)) {
			//messages.push(this.getZipAndCityStateWarning());
			cityZipMessage = this.getZipAndCityStateWarning();
		}
		
		var className = response.courses.length < 1 || response.showMsf ? "hdError" : "hdWarning";
		
		// if both radius and num of days were expanded and 'include nearby states' wasn't selected
		if(response.radiusExpanded && response.numOfDaysExpanded && !request.nearbyStates) {
			messages.push(this.getMessage(className, this.getNoCourseText(this.message.noCourses1, request), [this.suggestions.maxDistanceAndDates, this.suggestions.includeNearbyStates]));
		} // if both radius and num of days were expanded and 'include nearby states' was selected
		else if(response.radiusExpanded && response.numOfDaysExpanded && request.nearbyStates) {
			messages.push(this.getMessage(className, this.getNoCourseText(this.message.noCourses2, request), [this.suggestions.maxDistanceAndDates]));
		} // if radius was expanded and 'include nearby states' wasn't selected
		else if(response.radiusExpanded && !response.numOfDaysExpanded && !request.nearbyStates) {
			messages.push(this.getMessage(className, this.getNoCourseText(this.message.noCourses3, request), [this.suggestions.maxDistance, this.suggestions.includeNearbyStates]));
		} // if radius was expanded and 'include nearby states' was selected
		else if(response.radiusExpanded && !response.numOfDaysExpanded && request.nearbyStates) {
			messages.push(this.getMessage(className, this.getNoCourseText(this.message.noCourses4, request), [this.suggestions.maxDistance]));
		} // if num of days was expanded and 'include nearby states' wasn't selected
		else if(!response.radiusExpanded && response.numOfDaysExpanded && !request.nearbyStates) {
			messages.push(this.getMessage(className, this.getNoCourseText(this.message.noCourses1, request), [this.suggestions.maxDates, this.suggestions.includeNearbyStates]));
		} // if num of days was expanded and 'include nearby states' was selected
		else if(!response.radiusExpanded && response.numOfDaysExpanded && request.nearbyStates) {
			messages.push(this.getMessage(className, this.getNoCourseText(this.message.noCourses2, request), [this.suggestions.maxDates]));
		}
		
		if(response.showMsf) { // display msf message
			if(messages.length == 0) {
				messages.push(this.getMessage(className, this.getNoCourseText(this.message.noCourses3, request), request.nearbyStates ? [] : [this.suggestions.includeNearbyStates]));
			}
			messages.push(this.getMsfMessage());
		}
		
		if(response.courses.length > 0) {
			if(response.showMsf) {
				if(isDealerSearch) {
					messages = [];
					messages.push(this.getMessage("", this.getContactDealerMessage(dealer), []));
				} else {
					messages.push(this.getOtherDealersMessage());
				}
			} else {
				messages.push(this.getYesClassesMessage(response.totalItems, dealer));
			}
		}
		
		if(cityZipMessage != null) {
			messages = messages.reverse();
			messages.push(cityZipMessage);
			messages = messages.reverse();
		}
		
		return messages.join('');
	},
	
	getNoCourseText : function(message, request) {
		return message.
			replace(/_DISTANCE/, request.radius).
			replace(/_LOCATION/, this.getLocationText(request)).
			replace(/_DAYS/, request.numOfDays);
	},
	
	getLocationText : function(request) {
		return (request.zip + "") != "0" ? request.zip : request.city + ', ' + request.state;
	},
	
	getMessage : function(classs, header, contents) {
		var contentsHtml = '';
		if(contents.length > 0) {
			contents = ['<ul>',
							'<li>', contents.join('</li><li>'), '</li>',
						'</ul>'].join('');
		}
		return ['<div class="', classs, ' hdMessage">',
					'<h3>', header, '</h3>',
					contents,
				'</div>'].join('');
	},					
	
	getYesClassesMessage : function(numOfClasses, dealer) {
		if(dealer == null) {
			var dealerMessage =  numOfClasses > 1 ? HD.REO.View.Search.template.yesClasses : numOfClasses > 1 ? HD.REO.View.Search.template.yesClass : HD.REO.View.Search.template.yesOneClass;
			return  dealerMessage.replace(/_NUM_OF_CLASSES/, numOfClasses);
		} else {
			var message =  numOfClasses > 1 ? HD.REO.View.Search.template.dealerClasses : HD.REO.View.Search.template.dealerOneClass;
			message = message.replace(/_NUM_OF_CLASSES/, numOfClasses);
			message = message.replace(/_DEALER_NAME/, dealer.name);
			return message;
		}
	},
	
	getInitialMessage : function() {
		return  HD.REO.View.Search.template.initial;
	},
	
	getOtherDealersMessage : function() {
		return  HD.REO.View.Search.template.otherDealers;
	},
	
	getNearbyStatesTooltip : function() {
		return '<div>' + HD.REO.View.Search.template.nearbyStatesTooltip + '</div>';
	},
	
	getFullClassesTooltip : function() {
		return '<div>' + HD.REO.View.Search.template.fullClassesTooltip + '</div>';
	},
	
	getLoadingIndicator : function() {
		return '<div class="hdNumOfClasses"><h2>Searching...</h2></div>';
	},
	
	getDealerLinkName : function(dealer) {
		return "Back to " + (dealer != null ? dealer.name : "dealer") + " website";
	},
	
	getDealerLink : function(dealer, relativeUrl) {
		if(dealer != null && HD.util.Common.hasValue(dealer.url)) {
			return (dealer != null && relativeUrl != null) ? dealer.url.replace(/\/$/, "") + "/" + relativeUrl.replace(/^\//, "") : "#";
		}
		return null;
	}
};

HD.REO.View.Search.prototype = {
	getStatesWithNoRe : function() {
		var url = HD.util.Common.isLocal() ? HD.REO.View.Search.template.noReStatesUrlLocal : HD.REO.View.Search.template.noReStatesUrl;
		HD.util.XML.getAsJson(url, function(response) {
			var noReStates = [];
			var states = response.remapping.USA.State;
			for(var i = 0, len = states.length; i < len; i++) {
				var state = states[i];
				if(parseInt(state['#text']) < 1) {
					noReStates.push(state['@code']);
				}
			}
			HD.REO.View.Search.template.noReStates = noReStates.join(",");
		});
	},
	
	/**
	 * Sets the message in the element provided as part of configuration.
	 * 
	 * @param {String} message the message to set
	 */
	setMessage : function(message) {
		var messageEl = HD.util.Dom.get(this.config.message);
		if(messageEl.innerHTML != message) {
			messageEl.innerHTML = message;
		}
	},
	
	/**
	 * Sets the page size.
	 * 
	 * @param {int} pageSize the page size to set
	 */
	setPageSize : function(pageSize) {
		this.pageSize = pageSize;
		this.executeSearch(null, false);
	},
	
	/**
	 * Sets the sort type.
	 * 
	 * @param {String} sortType the sort type to set
	 */
	setSortType : function(sortType) {
		this.sortType = sortType;
		this.executeSearch(null, false);
	},
	
	/**
	 * Sets the dealer.
	 * 
	 * @param {Object} dealer the dealer object
	 */
	setDealer : function(dealer) {
		this.view.addClass(HD.REO.View.CSS_CLASSES.DEALER_VIEW);
		this.dealer = dealer;
		this.executeSearch(null, true);
	},
	
	/**
	 * @see HD.Widget#update 
	 */
	update : function(eventName, eventData) {
		if(eventName == "model_GetCourseList_Start") {
			this.loading(true);
		} else if(eventName == "model_GetCourseList_Finish") {
			this.loading(false);
			this.handleSearchFinish(eventData);
		} else if(eventName == "model_GetCourseList_Error") {
			this.loading(false);
			this.handleSearchFinish(eventData);
		} else if(eventName == "paging_Next") {
			this.executeSearch(eventData, false);
		} else if(eventName == "paging_Previous") {
			this.executeSearch(eventData, false);
		} else if(eventName == "paging_Page") {
			this.executeSearch(eventData, false);
		} else if(eventName == "listView_ViewAll_Click") {
			this.setPageSize(-1);
		} else if(eventName == "listView_ViewSome_Click") {
			this.setPageSize(this.config.pageSize);
		} else if(eventName == "listView_Sort_Change") {
			this.setSortType(eventData);
		} else if(eventName == "scheduleView_Dealer_Selected") {
			this.setDealer(eventData);
		}
	},
	
	/**
	 * @see HD.Widget#loading 
	 */
	loading : function(isLoading) {
		if(isLoading) {
			this.setMessage(HD.REO.View.Search.template.getLoadingIndicator());
		}
	},
	
	/**
	 * Handles the end of the search by expanding search parameters and displaying 
	 * error/warning messages if necessary.
	 * 
	 * @param {Object} data search response
	 */
	handleSearchFinish : function(data) {
		var template = HD.REO.View.Search.template;
		
		var dealer = null;
		if(data.courses.length > 0 && this.dealer != null) {
			dealer = data.courses[0].dealer;
		}
		
		var message = template.getCourseResultsMessage(this.lastRequest, data, dealer, this.dealer != null);
		this.setMessage(message);
		
		var dom = HD.util.Dom;
		var common = HD.util.Common;
		var messageEl = dom.get(this.config.message);
		var parentEl = this.getParent();
		var msfEls = dom.getByClass("msfCourse", "a", messageEl);
		if(msfEls.length > 0) {
			msfEls[0].onclick = function() {
				HD.util.Analytics.track(HD.REO.analytics.getMsfLink(HD.REO.experienceLevel));
				window.location="http://www.msf-usa.org/";
			};
		}
		
		if(data.errors.length > 0) {
			this.view.notifyObservers("searchView_Validation_Error");
		}
		
		if(data.showMsf) {
			this.view.addClass(HD.REO.View.CSS_CLASSES.MSF_VIEW);
		} else {
			this.view.removeClass(HD.REO.View.CSS_CLASSES.MSF_VIEW);
		}
		
		if(data.radiusExpanded || data.numOfDaysExpanded) {
			if(data.radiusExpanded) {
				dom.setValue("radius", "100");
			}
			if(data.numOfDaysExpanded) {
				dom.setValue("numOfDays", "-1");
			}
			this.persistSearchParams();
		}
		
		var returnToDealerEls = dom.getByClass("hdReturnToDealer", "a", parentEl);
		if(data.courses.length > 0 && returnToDealerEls.length > 0) {
			var hrefEl = returnToDealerEls[0];
			var dealer = data.courses[0].dealer;
			var dealerLink = template.getDealerLink(dealer, this.dealerReturnUrl);
			if(dealerLink == null) {
				dom.hide(hrefEl);
			} else {
				var dealerLinkName = template.getDealerLinkName(dealer);
				hrefEl.href = dealerLink;
				hrefEl.innerHTML = dealerLinkName;
				SessionService.setAttributes({dealerLink : dealerLink, dealerLinkName : dealerLinkName});
			}
		}
	},
	
	/**
	 * @see HD.Widget#setListeners 
	 */
	setListeners : function(data) {
		var parentEl = this.getParent();
		var formEl = parentEl.getElementsByTagName("form")[0];
		
		var modelCb = this.model;
		var cb = this;
		formEl.onsubmit = function() {
			cb.executeSearch.call(cb, null, false);
			return false;
		};
		
		var template = HD.REO.View.Search.template;
		HD.util.Common.defaultValue(formEl.zip, template.zip);
		HD.util.Common.defaultValue(formEl.city, template.city);
		
		var dom = HD.util.Dom;
		var nearbyStatesTipEl = dom.getByClass("hdNearbyStates", "span", parentEl)[0];
		var fullClassesTipEl = dom.getByClass("hdFullClasses", "span", parentEl)[0];
		var newSearchEl = dom.getByClass("hdNewSearch", "a", parentEl)[0];
		var returnToSearchEl = dom.getByClass("hdReturnToSearch", "a", parentEl)[0];
		var tooltipEls = dom.getByClass("hdTooltip", "div", parentEl);
		
		nearbyStatesTipEl.onmouseover = function() {
			dom.show(tooltipEls[0]);
		};
		
		nearbyStatesTipEl.onmouseout = function() {
			dom.hide(tooltipEls[0]);
		};
		
		fullClassesTipEl.onmouseover = function() {
			dom.show(tooltipEls[1]);
		};
		
		fullClassesTipEl.onmouseout = function() {
			dom.hide(tooltipEls[1]);
		};
		
		newSearchEl.onclick = function() {
			cb.view.notifyObservers("searchView_New_Search");
			cb.resetForm.call(cb);
			cb.setMessage.call(cb, HD.REO.View.Search.template.getInitialMessage());
			delete cb.dealer;
			cb.view.removeClass(HD.REO.View.CSS_CLASSES.DEALER_VIEW);
		};
		
		returnToSearchEl.onclick = function() {
			delete cb.dealer;
			cb.view.removeClass(HD.REO.View.CSS_CLASSES.DEALER_VIEW);
			cb.executeSearch.call(cb, null, true);
		};
	},
	
	/**
	 * Resets the form inputs to default values.
	 * 
	 */
	resetForm : function() {
		var template = HD.REO.View.Search.template;
		var dom = HD.util.Dom;
		dom.setValue("zip", template.zip);
		dom.setValue("city", template.city);
		dom.setValue("state", template.state);
		dom.setValue("radius", 25);
		dom.setValue("nearbyStates", false);
		dom.setValue("numOfDays", 90);
		dom.setValue("fullClasses", false);
	},
	
	/**
	 * Executes a search.
	 * 
	 * @param {int} pageNumber the page number to retrieve
	 * @param {boolean} skipErrorMessaging whether to skip displaying error messages 
	 */
	executeSearch : function(pageNumber, skipErrorMessaging) {
		var parentEl = this.getParent();
		var formEl = parentEl.getElementsByTagName("form")[0];
		
		this.cleanForm(formEl);
		var errorMessage = this.validate(formEl);
		if(errorMessage == null) {
			//this.setMessage("");
			var zip = formEl.zip.value;
			if(!zip.match(/^\d{5}$/)) {
				zip = 0;
			}
			
			if(pageNumber == null) {
				pageNumber = this.config.pageNumber;
			}
			
			var template = HD.REO.View.Search.template;
			
			var courseRequest = {
				zip : zip,
				city : this.getValue(formEl.city.value, template.city),
				state : this.getValue(formEl.state.value, template.state),
				radius : formEl.radius.value,
				numOfDays : formEl.numOfDays.value,
				nearbyStates : formEl.nearbyStates.checked,
				fullClasses : formEl.fullClasses.checked,
				pageNumber : pageNumber,
				pageSize : this.pageSize,
				sortType : this.sortType
			};
			
			if(this.dealer != null) {
				courseRequest.dealer = this.dealer;
			}
			
			this.lastRequest = courseRequest;

			this.persistSearchParams();
			this.model.getCourseList(courseRequest);
		} else {
			if(!skipErrorMessaging) {
				this.view.notifyObservers("searchView_Invalid_Parameters");
				this.setMessage(errorMessage);
			}
		}
	},
	
	/**
	 * Persists in session search parameters used to generate class listing.
	 */
	persistSearchParams : function() {
		var template = HD.REO.View.Search.template;
		var formEl = HD.util.Dom.get(template.formId);
		HD.util.Common.submitForm(formEl, HD.REO.sessionServletUrl + "?objectKey=" + formEl.id);
	},
	
	/**
	 * If the input's value equals to the default value, then user didn't provide data, return null.
	 * Otherwise, return input's value.
	 * 
	 * @param {String} value the value to test
	 * @param {String} defaultValue the default value
	 * @return input's value if not equal to default, otherwise null
	 */
	getValue : function(value, defaultValue) {
		return value == defaultValue ? null : value;
	},
	
	/**
	 * Cleans the form.
	 * 
	 * @param {HTMLForm} form the form to clean
	 */
	cleanForm : function(form) {
		var common = HD.util.Common;
		form.zip.value = common.stripHtmlTags(form.zip.value);
		form.city.value = common.stripHtmlTags(form.city.value);
		form.state.value = common.stripHtmlTags(form.state.value);
	},
	
	/**
	 * Validates the search form.
	 * 
	 * @param {HTMLForm} form the form to validate
	 * @return whether the form is valid
	 */
	validate : function(form) {
		var template = HD.REO.View.Search.template;
		
		if(this.dealer != null && HD.util.Common.hasValue(this.dealer.id)) {
			return null;
		}
		
		var zip = form.zip.value;
		var city = form.city.value;
		var state = form.state.value;
		var isNearbyStates = form.nearbyStates.checked;
		
		
		var isValidZip = this.isValidValue(zip, template.zip);
		var isValidCity = this.isValidValue(city, template.city);
		var isValidState = this.isValidValue(state, template.state);
		
		if(!isValidZip && !isValidCity && !isValidState) {
			return template.getNoLocationError();
		} else if(!isValidZip && isValidCity && !isValidState) {
			return template.getNoStateError();
		} else if(!isValidZip && !isValidCity && isValidState) {
			return template.getNoLocationError();
		} else if(isValidZip && !zip.match(/^\d{5}$/)) {
			return template.getInvalidZipError();
		} else if(isValidState) {
			if(template.noReStates.indexOf(state) > 0 && !isNearbyStates) {
				if(!isValidZip || !zip.match(/^\d{5}$/)) {
					var cityState = (city + ", " + state).replace(/ /g, '+');
					HD.util.Analytics.track(HD.REO.analytics.getNoResultsPage(HD.REO.experienceLevel, cityState));
					return template.getNoneInStateError(state);
				}
			}
		}
		
		return null;
	},
	
	/**
	 * Determine whether the value provided is valid, not valid if null, empty, or equals to the default value.
	 * 
	 * @param {String} value the value to test
	 * @param {String} defaultValue the default value
	 * @return whether the value is valid
	 */
	isValidValue : function(value, defaultValue) {
		return value != null && value != "" && value != defaultValue;
	},
	
	/**
	 * @see HD.Widget#getHtml 
	 */
	getHtml : function(data) {
		this.setMessage(HD.REO.View.Search.template.getInitialMessage());
		
    	var classes = HD.CSS_CLASSES;
    	var reoClasses = HD.REO.View.CSS_CLASSES;
    	var template = HD.REO.View.Search.template;
    	
    	var states = HD.util.Location.states;
    	
    	var statesHtml = [];
    	statesHtml.push('<option value="' + template.state + '">' + template.state + '</option>');
    	for(var i = 0, len = states.length; i < len; i++) {
    		statesHtml.push('<option value="' + states[i] + '">' + states[i] + '</option>');
		}
    	
    	return [
			'<div class="', classes.WIDGET, ' ', reoClasses.SEARCH ,'">',
				'<form id="', template.formId, '">' , 
					'<div class="hdLocSearch">',
						'<div class="formline">',
							'<input type="text" size="5" class="hdZip" name="zip" value="', template.zip, '"/>',
							'<span> -or- </span>',
							'<input type="text" size="20" class="hdCity" name="city" value="', template.city, '"/> ',
							'<select class="hdState" name="state">',
								statesHtml.join(''),
							'</select>',
						'</div>',
						'<div class="formline">',
							'<select class="hdDistance" name="radius">',
								'<option value="25">Up to 25 miles away</option>',
								'<option value="50">Up to 50 miles away</option>',
								'<option value="75">Up to 75 miles away</option>',
								'<option value="100">Up to 100 miles away</option>',
							'</select> ',
							'<input type="checkbox" name="nearbyStates" /> <label for="nearbyStates">Include nearby states in results <span class="hdLink hdNearbyStates">(?)</span></label>',
							'<div class="hdTooltip hdNearbyStatesTooltip hdHidden">',
								template.getNearbyStatesTooltip(),
							'</div>',
						'</div>',
					'</div>',
					'<div class="hdDateSearch">',
						'<div class="formline">',
							'<select class="hdDateSelect" name="numOfDays">',
								'<option value="15">Next 15 days</option>',
								'<option value="30">Next 30 days</option>',
								'<option value="60">Next 60 days</option>',
								'<option value="90" selected>Next 90 days</option>',
								'<option value="-1">All future dates</option>',
							'</select>',
						'</div>',
						'<div class="formline">',
							'<input type="checkbox" name="fullClasses" /> <label for="fullClasses">Include full classes in results <span class="hdLink hdFullClasses">(?)</span></label>',
							'<div class="hdTooltip hdFullClassesTooltip hdHidden">',
								template.getFullClassesTooltip(),
							'</div>',
						'</div>',
					'</div>',
					'<div class="hdClassSearch">',
						'<div class="formline">',
							'<input class="hdSearchButton" type="submit" value=""/>',
						'</div>',
					'</div>',
				'</form>',
				'<div class="hdSearchLinks">',
					'<a class="hdNewSearch hdLink">Start a New Search</a>',
					'<a class="hdReturnToSearch hdLink">Back to Your Search Results</a>',
					(this.dealer != null ? 
							'<a class="hdReturnToDealer hdLink" href="' + template.getDealerLink() + '">' + template.getDealerLinkName() + '</a>' : ''),
				'</div>',
			'</div>'].join('');
	}
};

HD.util.Common.extend(HD.REO.View.Search, [HD.Widget]);
