/**
 * @class HD.RidePlannerWidget
 * @description The purpose of RidePlannerWidget is to provide a mechanism for users to enter a location
 * 				and be taken into the Ride Planner application. 
 * 				Registered as hd_ride_planner_widget.
 * @constructor
 * @extends HD.Widget
 * @see HD.RidePlannerWidget.hooks
 * @see HD.RidePlannerWidget.templates
 * @see HD.util.Geocoder
 * @property {object} 	config
 *                    	The configuration for the widget.
 * @property {object} 	[config.hooks=HD.RidePlannerWidget.hooks]
 *                 		Customized subset of hooks (merged with the defaults).
 * @property {string} 	config.type
 *                    	Type identifier of the widget.
 *                    	Default is "hd_ride_planner_widget".
 * @property {number}	config.zoomLevel
 * 						Zoom level to load Ride Planner
 * 						Default is 10
 * @property {object} 	[config.templates=HD.RidePlannerWidget.templates]
 *                 		Customized subset of templates (merged with the defaults).
 * @property {boolean} 	config.showUserRoads
 *                 		Load ride planner with user submitted roads showing
 *                 		Default is false
 * @property {boolean} 	config.showGreatRoads
 * 						Load ride planner with great roads showing
 * 						Default is false       
 * @property {object} 	geocoder
 * 						Instance of geocoder
 * @property {string}	ridePlannerURL
 * 						URL of Ride Planner Application
 * 						Default is "http://rideplanner.harley-davidson.com/rideplanner/ridePlanner.jsp"					         
 */
HD.RidePlannerWidget = function(config) {
    config = config || {};
    var hasValue = HD.util.Common.hasValue;
    if(!hasValue(config.type)) {
    	config.type = "hd_ride_planner_widget";
    }
    if(!hasValue(config.zoomLevel)) {
    	config.zoomLevel = 10;
    }
    
    this.config = config || {};
    this.config.ridePlannerURL = this.config.ridePlannerURL || "http://rideplanner.harley-davidson.com/rideplanner/ridePlanner.jsp";
    
    /** Observer collection */
    this.observers = [];
	this.loadTemplates(arguments.callee);
    
    this.geocoder = new HD.util.Geocoder();
    this.geocoder.addObserver(this);
};

/**
 * The collection of default templates.
 * @fieldOf HD.RidePlannerWidget
 */
HD.RidePlannerWidget.templates = {
		
	/**
	 * Label for the widget
	 * @memberOf HD.RidePlannerWidget
	 */
	ridePlannerLabel : 'Look for Rides in Your Area',	
	
	/**
	 * Label for the location entry box submit button
	 * @memberOf HD.RidePlannerWidget
	 */
	buttonText : 'Go',
	
	/**
	 * Instructional text for location entry box
	 * @memberOf HD.RidePlannerWidget
	 */
	inputText : 'address, city, state or zip',
	
	/**
	 * Error text for an invalid location entry
	 * @memberOf HD.RidePlannerWidget
	 */
	invalidLocationText : 'Please enter a valid location',
	
	/**
	 * Error text for a location entry that could not be geocoded
	 * @memberOf HD.RidePlannerWidget
	 */
	geocodeErrorText : 'Could not resolve location ',
	
	/**
	 * @constant
	 * @return {string} Label for the widget: ridePlannerLabel
	 * @memberOf HD.RidePlannerWidget
	 */
	getLabelText : function() {
		return this.ridePlannerLabel;
	},
	
	/**
	 * @constant
	 * @return {string} Label for the location submit button: buttonText
	 * @memberOf HD.RidePlannerWidget
	 */
	getButtonText : function() {
		return this.buttonText;
	},
	
	/**
	 * @constant
	 * @return {string} Instructional text for location entry box: inputText
	 * @memberOf HD.RidePlannerWidget
	 */
	getInputText : function() {
		return this.inputText;
	},
	
	/**
	 * @constant
	 * @return {string} Error text for invalid location entry: invalidLocationText
	 * @memberOf HD.RidePlannerWidget
	 */
	getInvalidLocationErrorMessage : function() {
		return this.invalidLocationText;
	},
	
	/**
	 * @constant
	 * @return {string} Error text for location that could not be geocoded: geocodeErrorText
	 * @memberOf HD.RidePlannerWidget
	 */
	getGeocodeErrorMessage : function(location) {
		return this.geocodeErrorText + location;
	},
	
	/**
	 * @constant
	 * @return {string} HTML for ambiguous location results: disambiguateHtml
	 * @memberOf HD.RidePlannerWidget
	 */
	getDisambiguateHtml : function() {
		return this.disambiguateHtml;
	},
	
	/**
	 * @constant
	 * @return {string} Main template for the widget
	 * @memberOf HD.RidePlannerWidget
	 */
	getHtml : function() {
		return this.html;
	},
	
	/** 
	 * Ambiguous location results template for the widget
	 * @memberOf HD.RidePlannerWidget
	 * @type jst_template
	 */
	disambiguateHtml : '\
		{for geocode in geocodes}\
			<a href="javascript:void();" class="${classes.ITEM} ${hooks.AMBIGLINK}">${geocode.name}</a><br/>\
		{/for}',
	
	/** 
	 * Main template for the widget
	 * @memberOf HD.RidePlannerWidget
	 * @type jst_template
	 */		
	html :  '\
		<div class="${classes.RIDE_PLANNER} ${classes.WIDGET}">\
       	 	<form class="${hooks.LOCATIONFORM}">\
       	 		<div class="${classes.LABEL}">${templates.getLabelText()}</div>\
       	 		<div class="${classes.ERROR} ${hooks.ERROR}"></div>\
       	 		<input class="${classes.VALUE} ${hooks.LOCATIONINPUT}" value="${templates.getInputText()}"/>\
       	 		<input class="${classes.SUBMIT} ${hooks.LOCATIONBUTTON}" value="${templates.getButtonText()}" type="submit"/>\
       	 		<div class="${classes.MORE} ${classes.HIDDEN} ${hooks.MORE}"></div>\
       	 	</form>\
		</div>'
};

(function() {
	var classes = HD.CSS_CLASSES;
	
	/**
	 * The collection of hooks
	 */
	HD.RidePlannerWidget.hooks = {
		
		/**
		 * Standalone: (1)<br />
		 * Type: form<br />
		 * Use: user location entry container<br />
		 * @memberOf HD.RidePlannerWidget
		 */
		LOCATIONFORM : classes.RIDE_PLANNER + '-locform',
		
		/**
		 * Standalone: (1)<br />
		 * Type: text input<br />
		 * Use: user location entry box<br />
		 * @memberOf HD.RidePlannerWidget
		 */
		LOCATIONINPUT : classes.RIDE_PLANNER + '-locinput',
		
		/**
		 * Standalone: (1)<br />
		 * Type: submit <br />
		 * Use: user location entry submission<br />
		 * @memberOf HD.RidePlannerWidget
		 */
		LOCATIONBUTTON : classes.RIDE_PLANNER + '-locbutton',
		
		/**
		 * Standalone: (1)<br />
		 * Use: error container<br />
		 * @memberOf HD.RidePlannerWidget
		 */
		ERROR : classes.RIDE_PLANNER + '-error',
		
		/**
		 * Standalone: (1)<br />
		 * Use: ambiguous location link container<br />
		 * @memberOf HD.RidePlannerWidget
		 */
		MORE : classes.RIDE_PLANNER + '-more',
		
		/**
		 * Dependencies: 1:MORE <br/>
		 * Use: ambiguous location listing<br />
		 * @memberOf HD.RidePlannerWidget
		 */
		AMBIGLINK : classes.RIDE_PLANNER + '-ambilink'
	};
})();

HD.RidePlannerWidget.prototype = {
    
	/**
	 * Sets event listeners for the widget.
	 * @param {object} [data]
	 *                 Event data from the render phase
	 */
    setListeners : function(data) {
	
		// Get the form elements
		var parentEl = this.getParent();
		var inputEl = HD.getByClass(this.config.hooks.LOCATIONINPUT, '*', parentEl)[0];
		var formEl = HD.getByClass(this.config.hooks.LOCATIONFORM, '*', parentEl)[0];
		
		var defaultText = HD.RidePlannerWidget.templates.getInputText();
		var geocoder = this.geocoder;
		
		// Toggle the instructional text
		inputEl.onfocus = function() {
			if(this.value == defaultText) {
				this.value = "";
			}
		};
		
		inputEl.onblur = function() {
			if(this.value == "") {
				this.value = defaultText;
			}
		};
		
		// Try and geocode the entered location
		var cb = this;
		formEl.onsubmit = function() {
			cb.clearDisambiguate.call(cb);
			cb.clearError.call(cb);
			var location = HD.util.Common.stripHtmlTags(inputEl.value);
			if(!cb.isValidLocation.call(cb, location)) {
				cb.showError.call(cb, HD.RidePlannerWidget.templates.getInvalidLocationErrorMessage());
				return false;
			}
			
			geocoder.geocode.call(geocoder, location);
			return false;
		}
    },
    
    /**
	 * Determines a valid geographic location entry.
	 * @param {string} location
	 *                 Location passed in by the user
	 * @returns {boolean} valid geographic location to be geocoded                
	 */
    isValidLocation : function(location) {
    	if(!HD.util.Common.hasValue(location)) {
    		return false;
    	} if (location == HD.RidePlannerWidget.templates.getInputText()) {
    		return false;
    	} if (!location.match(/[\d\w]+/)) {
    		return false;
    	}
     	return true;
    },
    
    /**
	 * Monitors events by HD.RidePlannerWidget.
	 * @param {string} eventName
	 *                 The name of the event
	 * @param {object} [eventData]
	 *                 Data for the event
	 */
    update : function(eventName, eventData) {
    	if(eventName == "geocode_Start") {
    		this.clearError();
    		this.loading(true);
    	} else if(eventName == "geocode_Finish") {
    		this.loading(false);
    		this.clearError();
    		this.renderResults(eventData.geocodes);
    	} else if(eventName == "geocode_Error") {
    		this.loading(false);
    		this.showError(HD.RidePlannerWidget.templates.getGeocodeErrorMessage(eventData.location));
    	}
    },
    
    /**
	 * Renders the HTML for the widget based on dynamic data.
	 * @param {object} [data]
	 *                 Event data from the render phase
	 * @returns {string} HTML for the widget
	 */
    getHtml : function(data) {
		return this.processTemplate(this.config.templates.getHtml());
    },
    
    /**
	 * Creates the value for the widget analytics based on dynamic data.
	 * @param {object} [data]
	 *                 Event data from the render phase
	 * @returns {string} Analytics value for the widget
	 */
    analyticsString : function(data) {
    	return "default_analyticsRidePlannerWidgetString";
    },
    
    /**
	 * Either takes the user to the Ride Planner or shows the ambiguous locations.
	 * @param {object} [geocodes]
	 *                 Geocoded locations
	 */
    renderResults : function(geocodes) {
    	var classes = HD.CSS_CLASSES;
		var parentEl = this.getParent();
		var inputEl = HD.getByClass(this.config.hooks.LOCATIONINPUT, '*', parentEl)[0];
		
		if(geocodes.length == 1) {
			var geocode = geocodes[0];
			inputEl.value = geocode.name;
			this.openRidePlanner(geocode.latLon);
		} else {
			this.showDisambiguate(geocodes);
		}
    },
    
    /**
	 * Opens the Ride Planner in a new window
	 * @param {object} [latLong]
	 *                 Latitude/Longitude for geocoded location
	 */
    openRidePlanner : function(latLon) {
    	this.clearDisambiguate();
    	var config = this.config;
    	var extraParams = "";
    	if(config.showUserRoads) {
    		extraParams += "&showUserRoads=true";
    	}
    	if(config.showGreatRoads) {
    		extraParams += "&showGreatRoads=true";
    	}
    	
    	var url =  this.config.ridePlannerURL + "?latitude=" + latLon.latitude + "&longitude=" + latLon.longitude + "&zoomLevel=" + config.zoomLevel + extraParams;
    	window.open(url, '', 'scrollbars=yes,resizable=yes,toolbar=no,location=no,menubar=no,directories=no,status=no');
    },
    
    /**
	 * Shows returned errors
	 * @param {string} errorMessage
	 *                 Error returned from trying to find location
	 */
    showError : function(errorMessage) {
    	var parentEl = this.getParent();
    	
    	var errorEl = HD.getByClass(this.config.hooks.ERROR, '*', parentEl)[0];
    	errorEl.innerHTML = errorMessage;
    },
    
    /**
	 * Clears any returned errors
	 */
    clearError : function() {
    	var parentEl = this.getParent();
    	
    	var errorEl = HD.getByClass(this.config.hooks.ERROR, '*', parentEl)[0];
    	errorEl.innerHTML = "";
    },
    
    /**
	 * Shows the ambiguous locations.
	 * @param {object} [geocodes]
	 *                 Geocoded locations
	 */
    showDisambiguate : function(geocodes) {
    	var parentEl = this.getParent();
    	var classes = HD.CSS_CLASSES;
    	
		var disambiguateEl = HD.getByClass(this.config.hooks.MORE, '*', parentEl)[0];
		HD.removeClass(disambiguateEl, classes.HIDDEN);
		disambiguateEl.innerHTML = this.processTemplate(this.config.templates.getDisambiguateHtml(), {
			geocodes : geocodes
		});
		
		var cb = this;
		var itemEls =  HD.getByClass(this.config.hooks.AMBIGLINK, '*', parentEl)
		for(var i = 0, len = geocodes.length; i < len; i++) {
			(function (){
				// Attach click events for links
				var geocode = geocodes[i];
				itemEls[i].onclick = function() {
					cb.clearDisambiguate.call(cb);
					cb.renderResults.call(cb, [geocode]);
				}
			}());
		}
    },
    
    /**
	 * Clears any returned ambiguous locations
	 */
    clearDisambiguate : function() {
    	var parentEl = this.getParent();
    	var classes = HD.CSS_CLASSES;
    	
		var disambiguateEl = HD.getByClass(this.config.hooks.MORE, '*', parentEl)[0];
    	HD.addClass(disambiguateEl, classes.HIDDEN);
    	disambiguateEl.innerHTML = "";
    }
};

HD.extend(HD.RidePlannerWidget, [HD.Widget]);

HD.register('hd_ride_planner_widget', 'HD.RidePlannerWidget', {version: "1.0", build: "1"});
