/*

GawkerMap / v 1.0
a Google Maps mashup for displaying XML data from wists.com

GawkerMap makes use of the following JS libraries:
- Protoype: http://prototype.conio.net/
- PdMarker: http://www.pixeldevelopment.com/pdmarker.asp

create a new GawkerMap like so:

	var my_map = new GawkerMap({
		map_dom_id : 'StalkerMap',
		locations_container_id : 'Locations',
	});

*/

/* ======================================================================== */

Object.extend(String.prototype, {
  urlEncode: function() {
    return this.replace(/([^A-Za-z0-9])/g,
			function (matched_substring, paren1, match_offset, orig_string) {
	        return "%" + paren1.charCodeAt(0).toString(16);
	     }
		);
  }

,	truncate: function(num_chars) {
		if (this.length <= num_chars) { return this; }

		var word_break_position = -1;
		for (i=0; i < num_chars; i++) {
			if (this.charAt(i).match(/[.,;!? ]/)) { word_break_position = i; }
		}
		if (word_break_position == -1) { word_break_position = size - 1; }

		return this.substring(0, word_break_position) + '&hellip;';
	}
});

/* ======================================================================== */

var GawkerMap = Class.create();

GawkerMap.prototype = {

	// this holds all of the configuration for each instance of the object
	// these values may be overridden by the user-supplied config data
	CONFIG : {
		// user-configurable stuff
		map_dom_id : "map"
,		map_data_url : "xml_proxy.php"
,		map_center_point : new GLatLng(0, 0)
,		map_zoom : 5

,		wists_user : ""
,		wists_tag : ""

,		active_class : "Active"

,		locations_container_id : "Locations" // the DOM node that holds the location blocks
,		location_item_class : "Item"
,		location_marker_class : "Marker"
,		inline_ad_id : "AdWrapper"

,		item_title_class : "Title"
,		item_desc_class : "Description"
,		item_geo_class : "geo"
,		item_lat_class : "latitude"
,		item_lng_class : "longitude"
,		item_datetime_class : "DateTime"
,		item_address_class : "Address"
,		item_image_class : "Thumbnail"

//		this.image_base isn't valid in these settings, must hard code. =(
//,		image_base			: "/assets/v3.gridskipper.com/img/"
,		marker_base_image   : "/sites/finearts/map/grid/images/icons/" + "icon.marker.default.off.png"
,		marker_hover_image  : "/sites/finearts/map/grid/images/icons/" + "icon.marker.default.hover.png"
,		marker_click_image  : "/sites/finearts/map/grid/images/icons/" + "icon.marker.default.on.png"
,		marker_shadow_image : "/sites/finearts/map/grid/images/icons/" + "icon.marker.shadow.png" // not currently used
,		marker_max_numbered_images : 25

,		info_window_class : "MapInfoWindow"
,		info_window_width : 275 //316 // in pixels

,		description_max_chars : 130

		// to-be-used in the future
,		locality : "NYC"

		// internal stuff
,		dom_id_regex  : /(\d+)$/ // pulls the index out of the location dom element
,		index_regex   : /\.default\./ // replaces the placeholders in the marker image URLs
,		address_regex : /\s*(((new\syork|brooklyn|queens|staten\sisland|bronx)\s*)?((ny|nj|ct)\s*)?(([0-9]{5}(\-[0-9]{4})?)\s*)?(us\s*)?)$/i // cleans out crap like: NEW YORK NY 10018-2301 US from the address

	} // END: CONFIG


	// each property here corresponds to a property in the CONFIG object.
	// these methods check the validity of the user-specified config vars
,	CONFIG_TESTS : {
		_string                : function(thing) { return ( (typeof(thing) == 'string') && (thing.length > 0) ); }
,		_dom_node              : function(thing) { return ( (typeof(thing) == 'string') && $(thing) ); }

,		map_dom_id             : "_dom_node"
,		map_zoom               : function(thing) { return ( !isNaN(parseInt(thing)) ); }
,		map_center_point       : function(thing) { return ( thing instanceof GPoint ); }
,		locations_container_id : "_dom_node"
,		wists_user             : "_string"
,		wists_tag              : "_string"
	} // END: CONFIG_TESTS


	// apply the user's custom config for this instance
,	applyCustomConfig : function(user_config) {
		// loop through the user_config data and validate each setting
		for (var item in user_config) {
			// remove the setting if...
			if ( 
				// setting does not exist in CONFIG
				(typeof(this.CONFIG[item]) == 'undefined')

				// or validation fails against the custom vaidation function
			|| ( (typeof(this.CONFIG_TESTS[item]) == 'function')  && !this.CONFIG_TESTS[item](user_config[item]) )

				// or validation fails against a predefined type
			|| ( (typeof(this.CONFIG_TESTS[item]) == 'string')    && !this.CONFIG_TESTS[this.CONFIG_TESTS[item]](user_config[item]) )

				// or validation fails against an setting without a validation config (a string)
			|| ( (typeof(this.CONFIG_TESTS[item]) == 'undefined') && !this.CONFIG_TESTS['_string'](user_config[item]) )
			) {
				debug('WARNING', 'could not verify key "' + item + '" with value "' + user_config[item] + '" ... deleting');
				delete(user_config[item]);
			}
		}

		// merge the objects
		this.CONFIG = $H(this.CONFIG).merge(user_config);

	} // END: applyCustomConfig()


	// this is run at class creation time
,	initialize : function(user_config) {

		// apply the user's config
		this.applyCustomConfig(user_config);

		// create the map instance and clear the
		this.gmap = new GMap2($(this.CONFIG['map_dom_id']));
		
		//start breaking
		//this.gmap.enableGoogleBar();
		//end breaking
		
		
		
		//this.gmap.addControl(new GLargeMapControl());
		this.gmap.addControl(new GSmallMapControl()); /*, new GControlPosition(G_ANCHOR_BOTTOM_LEFT, new GSize(10, 50))); */
		this.gmap.addControl(new GMapTypeControl());
		if (this.CONFIG['map_center_point']) { this.gmap.setCenter(this.CONFIG['map_center_point'], this.CONFIG['map_zoom']); }
		this.clearMapData();

		// get the container for the locations list
		this.locations_container = $(this.CONFIG['locations_container_id']);

		// if document has content in it already, just parse it
		var all_locations = document.getElementsByClassName(this.CONFIG['location_item_class'], this.locations_container);
		if (all_locations.length) {
			this.parseDataInDocument(all_locations);

		// otherwise, load the data via XHR
		} else {
			//this.requestData(this.CONFIG['wists_tag']);
		}

		// create a closure so the onresize can call this map obj
		// this is needed to recenter the map when the window resizes
		var map_obj = this;
		addWindowEvent('onresize', function() { map_obj.gmap.checkResize(); map_obj.gmap.returnToSavedPosition(); });
		
		// clean up memory (maps API2 only)
		Event.observe(window, 'unload', function() { GUnload(); });

		// remove onerror handler set by google maps js if we are in debugging mode
		if (DEBUG) {
			window.onerror = function(){ };
		}

	} // END: initialize


,	parseDataInDocument : function(locations_list) {
		debug('STATUS', 'parseDataInDocument(' + locations_list.length + ')');

		// go through all the Items and attach the event handlers to enable gmaps functionailty
		for (var i=0; i < locations_list.length; i++) {
			// only process items that have contain the geo microformat
			if (document.getElementsByClassName(this.CONFIG['item_geo_class'], locations_list[i]).length < 1 ) { continue; }

			// process each location to add click and mouseover stuff
			this._attachEventHandlers(locations_list[i]);

			// extract the data, then create and add the marker to the map
			this.addMarkerToMap(this.createMarker(this._extractDataFromHTML(locations_list[i])));
		}

		this.gmap.zoomToMarkers(5);
		this.gmap.savePosition();
	} // END: parseDataInDocument


,	loadNewMapData : function (anchor_obj) {
		var q = anchor_obj.href.substring(anchor_obj.href.indexOf('?')).toQueryParams(';');

		debug('INFO', "loadNewMapData()" + "\n" + "q['tag'] = " + q['tag'] + "\n" + "q['user'] = " + q['user']);

		if (q['tag'] != "") {
			this.clearMapData();
			this.requestData(q['tag'], q['user']);
		}
	} // END: loadNewMapData()


,	clearMapData : function() {
		this.gmap.clearOverlays();
		this.map_markers = []; // holds all the markers on the current map
	} // END:clearMapData()


,	requestData : function(tag, user) {
		debug('STATUS', 'requestData(' + tag + "," + user + ')');

		var q = window.location.search + "";

		// use tag and user if it's passed in, otherwise pull it from normal place
		this.CONFIG['wists_tag'] = ( (typeof(tag) == 'string') && (tag.length > 0) ) ? tag : q.toQueryParams(';')['tag'];
		this.CONFIG['wists_user'] = ( (typeof(user) == 'string') && (user.length > 0) ) ? user : this.CONFIG['wists_user'];

		debug('INFO', 'wists_tag = ' + this.CONFIG['wists_tag'] + "\n" + "wists_user = " + this.CONFIG['wists_user']);

		if (this.locations_container.innerHTML != "" && $(this.CONFIG['inline_ad_id'])) {
			this.locations_container.parentNode.appendChild($(this.CONFIG['inline_ad_id']));
		}

		// clear out the current location list
		this.locations_container.innerHTML = '';

		// create a closure for the XHR obj
		var map_obj = this;

		// create the XHR obj and set it up
		var xml_data_request = GXmlHttp.create();
		xml_data_request.open("GET", ( this.CONFIG['map_data_url'] + "?" + "user=" + this.CONFIG['wists_user'] + ( (this.CONFIG['wists_tag'] == "") ? "" : ";tag=" + this.CONFIG['wists_tag'] ) ), true);

		xml_data_request.onreadystatechange = function() {
			if (xml_data_request.readyState == 4) {
				var all_markers = xml_data_request.responseXML.documentElement.getElementsByTagName("marker");
				if (all_markers.length > 0) {
					map_obj.processMarkers(all_markers);
				} else {
					map_obj.locations_container.innerHTML = '<p class="ErrorMessage">Sorry, no data is available.<\/p>';
				}
			}
		} // end onreadystatechange()

		// send the xml data request
		xml_data_request.send(null);

	} // END: requestData


,	_attachEventHandlers : function(element) {
		var map_obj = this;
		// using a regex on the dom id to grab the index
		element.onclick     = function() { GEvent.trigger(map_obj.map_markers[map_obj.CONFIG['dom_id_regex'].exec(this.id)[1] - 1], 'click') };
		element.onmouseover = function() { GEvent.trigger(map_obj.map_markers[map_obj.CONFIG['dom_id_regex'].exec(this.id)[1] - 1], 'mouseover') };
		element.onmouseout  = function() { GEvent.trigger(map_obj.map_markers[map_obj.CONFIG['dom_id_regex'].exec(this.id)[1] - 1], 'mouseout') };
		} // END: _attachEventHandlers


,	_extractDataFromHTML : function(element) {
		return {
			num         : parseInt(this.CONFIG['dom_id_regex'].exec(element.id)[1])

			// required
,			lat         : parseFloat(document.getElementsByClassName(this.CONFIG['item_lat_class'], element)[0].getAttribute('title'))
,			lng         : parseFloat(document.getElementsByClassName(this.CONFIG['item_lng_class'], element)[0].getAttribute('title'))

			// suggested
//,			title       : ( (document.getElementsByClassName(this.CONFIG['item_title_class'], element).length) ? document.getElementsByClassName(this.CONFIG['item_title_class'], element)[0].textContent || document.getElementsByClassName(this.CONFIG['item_title_class'], element)[0].innerText : "")
,			title : ( (document.getElementsByClassName(this.CONFIG['item_title_class'], element).length) ? document.getElementsByClassName(this.CONFIG['item_title_class'], element)[0].innerHTML : "")
,			url         : ( (document.getElementsByClassName(this.CONFIG['item_title_class'], element).length) ? document.getElementsByClassName(this.CONFIG['item_title_class'], element)[0].firstChild.href : "")
//,			description : ( (document.getElementsByClassName(this.CONFIG['item_desc_class'], element).length) ? document.getElementsByClassName(this.CONFIG['item_desc_class'], element)[0].textContent || document.getElementsByClassName(this.CONFIG['item_desc_class'], element)[0].innerText : "")
,			description : ( (document.getElementsByClassName(this.CONFIG['item_desc_class'], element).length) ? document.getElementsByClassName(this.CONFIG['item_desc_class'], element)[0].innerHTML : "")

			// optional
,			datetime    : ( (document.getElementsByClassName(this.CONFIG['item_datetime_class'], element).length) ? document.getElementsByClassName(this.CONFIG['item_datetime_class'], element)[0].textContent || document.getElementsByClassName(this.CONFIG['item_datetime_class'], element)[0].innerText : "")
,			address     : ( (document.getElementsByClassName(this.CONFIG['item_address_class'], element).length) ? document.getElementsByClassName(this.CONFIG['item_address_class'], element)[0].textContent || document.getElementsByClassName(this.CONFIG['item_address_class'], element)[0].innerText : "")
,			img         : ( (document.getElementsByClassName(this.CONFIG['item_image_class'], element).length) ? document.getElementsByClassName(this.CONFIG['item_image_class'], element)[0].src : "")
		}
	} // END: _extractDataFromHTML


,	_extractDataFromXML : function(current_marker) {
		return {
			num         : 0
,			lat         : parseFloat(current_marker.getAttribute("lat"))
,			lng         : parseFloat(current_marker.getAttribute("lng"))
,			title       : current_marker.getAttribute("title")
,			datetime    : current_marker.getAttribute("date")
,			description : current_marker.getAttribute("description")
,			url         : current_marker.getAttribute("link")
,			address     : current_marker.getAttribute("address").replace(this.CONFIG['address_regex'], "") // filter the ugly address format for locals
,			img         : current_marker.getAttribute("img")
		};
	} // END: _extractDataFromHTML


	// store the marker in an array and add the marker to the map
,	addMarkerToMap : function(marker) {
		this.map_markers.push(marker);
		this.gmap.addOverlay(marker);
	} // END: addMarkerToMap


, _createDetailHTML : function(marker_data) {
		if( marker_data['img'] != '' )
		{
			return [
						"<div id=\"MapWindow\" class=\"Item\">"
			,			"<div class=\"Content\">"
			//,				"<h2 class=\"Title\"><a href=\"" + marker_data['url'] + "\" target=\"_blank\">" + marker_data['title'] + "<\/a><\/h2>"
			,				"<h2 class=\"Title\">" + marker_data['title'] + "<\/h2>"
			,				"<p class=\"Address\">" + marker_data['address'] + "<\/p>"
			,				"<p class=\"DateTime\">" + marker_data['datetime'] + "<\/p>"
			,			"<div class=\"Image\" id=\"Image" + marker_data['num'] + "\"><img src=\"" + marker_data['img'] + "\" class=\"Thumbnail\" border=\"0\" alt=\"\" \/><\/div>"
			,				"<p class=\"Description\">" + marker_data['description'] + "<\/p>"
			,	 		"<\/div><\/div>"
			].join("\n");			
		}
		else
		{
			return [
						"<div id=\"MapWindow\" class=\"Item\">"
			,			"<div class=\"Content\">"
			//,				"<h2 class=\"Title\"><a href=\"" + marker_data['url'] + "\" target=\"_blank\">" + marker_data['title'] + "<\/a><\/h2>"
			,				"<h2 class=\"Title\">" + marker_data['title'] + "<\/h2>"
			,				"<p class=\"Address\">" + marker_data['address'] + "<\/p>"
			,				"<p class=\"DateTime\">" + marker_data['datetime'] + "<\/p>"
			,				"<p class=\"Description\">" + marker_data['description'] + "<\/p>"
			,	 		"<\/div><\/div>"
			].join("\n");	
		}
	} // END: _createDetailHTML


,	processMarkers : function(all_markers) {

		// local loop vars
		var new_location, locations_html, current_marker, temp_data, temp_marker;

		// at which index the ad is placed
		var ad_placement_index = 2;

		// loop through the marker nodes, pull out the data, and create markers on the map
		for (var i = 0; i < all_markers.length; i++) {
			// grab the current marker
			current_marker = all_markers[i];

			// pull out the props for convenience
			temp_data = this._extractDataFromXML(current_marker);
			temp_data['num'] = i + 1;

			// create the node this way in order to assign an onclick with a closure
			new_location = document.createElement('div');
			new_location.className = this.CONFIG['location_item_class'];
			new_location.id = this.CONFIG['location_item_class'] + temp_data['num'];

			// normal innerHTML stuff
			locations_html = [
				"<img src=\"" + ( (temp_data['num'] <= this.CONFIG['marker_max_numbered_images']) ? this.CONFIG['marker_base_image'].replace(this.CONFIG['index_regex'], "." + temp_data['num'] + ".") : this.CONFIG['marker_base_image'] ) + "\" alt=\"" + temp_data['num'] + "\" id=\"Marker" + temp_data['num'] + "\" width=\"20\" height=\"20\" border=\"0\" class=\"Marker\" />"
,				this._createDetailHTML(temp_data)
,				"<a href=\"http://wists.com/r.php?c=null&r=" + temp_data['url'].urlEncode() + "&u=" + temp_data['image'].urlEncode() + "&title=" + temp_data['title'].urlEncode() + "\" target=\"_blank\" title=\"Add to wists\" class=\"WistsAddLink\"><img src=\"./img/icon.wists.add.gif\" border=\"0\" width=\"16\" height=\"17\" class=\"Icon\" alt=\"wists icon\" \/><\/a>"
			].join('\n');

			// add the event handler so mouseover and click stuff works
			this._attachEventHandlers(new_location);

			// assign the HTML and append the node
			new_location.innerHTML = locations_html;
			this.locations_container.appendChild(new_location);

			// insert the ad if the time is right
			if ( (i == ad_placement_index) && $(this.CONFIG['inline_ad_id'])) {
				// this moves the ad from its original place *outside* the locations_container to *inside* the locations container
				this.locations_container.appendChild($(this.CONFIG['inline_ad_id']));
			}

			this.addMarkerToMap(this.createMarker(temp_data));

			// reset local loop vars for next iteration
			temp_data = current_marker = temp_marker = locations_html = new_location = null;

		} // end for() all marker nodes

		// re-center the map on the points
		this.gmap.zoomToMarkers(5);
		this.gmap.saveMapState();

	} // END: processMarkers()


,	createMarker : function(marker_data) {
		var icon, marker;

		// make the icon for the marker, using a filename template
		icon = new GIcon();
		icon.image = (marker_data['num'] <= this.CONFIG['marker_max_numbered_images']) ? this.CONFIG['marker_base_image'].replace(this.CONFIG['index_regex'], "." + marker_data['num'] + ".") : this.CONFIG['marker_base_image'];

		// NOTE: these dimensions are tied to the exact image used
		icon.iconSize = new GSize(20,20);
		icon.iconAnchor = new GPoint(10,10);
		icon.infoWindowAnchor = new GPoint(17,5);

		// create a new marker with some custom props
		marker = new PdMarker(new GLatLng(marker_data['lat'], marker_data['lng']), icon, marker_data['title']);

		// store the number of the marker so we can create DOM IDs to grab elements in the HTML
		marker.marker_num = marker_data['num'];

		// the html that gets stuffed into the info window
		marker.info_win_html = [
			"<div class=\"" + this.CONFIG['info_window_class'] + "\">"
,				this._createDetailHTML(marker_data)
,			"<\/div>"
		].join('\n');

//		debug('INFO', "marker[" + marker.marker_num + "].info_win_html = " + marker.info_win_html);

		var map_obj = this; // create a closure

		// set up event listeners for the markers and info window
		GEvent.addListener(marker, "click", function() {
			if (this.getMouseOutEnabled()) {
				this.setImage( ( (this.marker_num <= map_obj.CONFIG['marker_max_numbered_images']) ? map_obj.CONFIG['marker_click_image'].replace( map_obj.CONFIG['index_regex'], "." + this.marker_num + ".") : map_obj.CONFIG['marker_click_image'] ) );
				this.setMouseOutEnabled(false);
				this.topMarkerZIndex();
				this.openInfoWindowHtml(this.info_win_html, map_obj.CONFIG['info_window_width']);

				Element.addClassName($(map_obj.CONFIG['location_item_class'] + this.marker_num), map_obj.CONFIG['active_class']);
				// debug('INFO', "click: " + $(map_obj.CONFIG['location_item_class'] + this.marker_num).id + " = "+ $(map_obj.CONFIG['location_item_class'] + this.marker_num).className);
			}
		});

		GEvent.addListener(marker, "mouseover", function() {
			if (this.getMouseOutEnabled()) {
				this.setImage( ( (this.marker_num <= map_obj.CONFIG['marker_max_numbered_images']) ? map_obj.CONFIG['marker_hover_image'].replace( map_obj.CONFIG['index_regex'], "." + this.marker_num + ".") : map_obj.CONFIG['marker_hover_image'] ) );
				this.topMarkerZIndex();
				$(map_obj.CONFIG['location_marker_class'] + this.marker_num).src = (this.marker_num <= map_obj.CONFIG['marker_max_numbered_images']) ? map_obj.CONFIG['marker_hover_image'].replace( map_obj.CONFIG['index_regex'], "." + this.marker_num + ".") : map_obj.CONFIG['marker_hover_image'];

				Element.addClassName($(map_obj.CONFIG['location_item_class'] + this.marker_num), map_obj.CONFIG['active_class']);
				// debug('INFO', "mouseover: " + $(map_obj.CONFIG['location_item_class'] + this.marker_num).id + " = "+ $(map_obj.CONFIG['location_item_class'] + this.marker_num).className);
 			}
		});

		GEvent.addListener(marker, "mouseout", function() {
			if (this.getMouseOutEnabled()) {
				this.restoreImage();
				this.restoreMarkerZIndex();
				$(map_obj.CONFIG['location_marker_class'] + this.marker_num).src = (this.marker_num <= map_obj.CONFIG['marker_max_numbered_images']) ? map_obj.CONFIG['marker_base_image'].replace( map_obj.CONFIG['index_regex'], "." + this.marker_num + ".") : map_obj.CONFIG['marker_base_image'];

				Element.removeClassName($(map_obj.CONFIG['location_item_class'] + this.marker_num), map_obj.CONFIG['active_class']);
				// debug('INFO', "mouseout: " + $(map_obj.CONFIG['location_item_class'] + this.marker_num).id + " = "+ $(map_obj.CONFIG['location_item_class'] + this.marker_num).className);
			}
		});

		GEvent.addListener(marker, "infowindowopen", function() {
			this.setMouseOutEnabled(false);
			$(map_obj.CONFIG['location_marker_class'] + this.marker_num).src = (this.marker_num <= map_obj.CONFIG['marker_max_numbered_images']) ? map_obj.CONFIG['marker_click_image'].replace( map_obj.CONFIG['index_regex'], "." + this.marker_num + ".") : map_obj.CONFIG['marker_click_image'];

			Element.addClassName($(map_obj.CONFIG['location_item_class'] + this.marker_num), map_obj.CONFIG['active_class']);
			// debug('INFO', "infowindowopen: " + $(map_obj.CONFIG['location_item_class'] + this.marker_num).id + " = "+ $(map_obj.CONFIG['location_item_class'] + this.marker_num).className);
		});

		GEvent.addListener(marker, "infowindowclose", function() {
			this.setMouseOutEnabled(true);
			this.restoreImage();
			$(map_obj.CONFIG['location_marker_class'] + this.marker_num).src = (this.marker_num <= map_obj.CONFIG['marker_max_numbered_images']) ? map_obj.CONFIG['marker_base_image'].replace( map_obj.CONFIG['index_regex'], "." + this.marker_num + ".") : map_obj.CONFIG['marker_base_image'];

			Element.removeClassName($(map_obj.CONFIG['location_item_class'] + this.marker_num), map_obj.CONFIG['active_class']);
			// debug('INFO', "infowindowclose: " + $(map_obj.CONFIG['location_item_class'] + this.marker_num).id + " = "+ $(map_obj.CONFIG['location_item_class'] + this.marker_num).className);
		});

		return marker;

	} // END: createMarker()

}; // END: GawkerMap.prototype