/**
 * This object contains static public fields with name of events generated by the application
 * @return Nothing
 */
/** Event fired whenever new search must be done due to change of search criterion. */
EVT_ON_REQUEST_NEW_SEARCH = "onRequestNewSearch";
/** Event fired whenever a new server-request for points must be done. */
EVT_ON_RECEIVE_POINTS = "onReceivePoints";
/* Event fired when PointLoader switches to "busy" or "idle" state - useful for updating "loading" graphic */
EVT_BUSY = "busy";

var GLOBAL_EVENT = new Object();

// how long do we wait before we respond to user interaction?
// this is useful to throttle down repeated zoom / pan actions
UI_RELOAD_DELAY = 300; //milliseconds


function PointLoader(settings, mapControl, searchInput) {
	this.searchCriteria = new SearchCriteria();
	this.useSearchStringLocation = true;

	this.settings = settings;
	this.mapControl = mapControl;
	this.searchInput = searchInput;
	this.busy = false;

	function onReloadPoints(use_location_string) {
		if(use_location_string)
			this.useSearchStringLocation = true;
		this.setBusy(true);
		this.requestPointsDelayed();
	}
	GEvent.addListener(mapControl, EVT_ON_REQUEST_NEW_SEARCH, GEvent.callback(this, onReloadPoints));
	GEvent.addListener(searchInput, EVT_ON_REQUEST_NEW_SEARCH, GEvent.callback(this, onReloadPoints));

	this.mapControl.bindPointLoader(this);
}

PointLoader.prototype.constructSearchParams = function() {
	var bounds = this.mapControl.getBounds();
	return constructSearchUrl({
		'search_string': this.searchInput.getSearchString(),
		'search_location' : this.searchInput.getSearchStringLocation(),
		'map': {
			'zoom': this.mapControl.getCurrentZoomLevel(),
			'ne_lat': bounds[0],
			'ne_lng': bounds[1],
			'sw_lat': bounds[2],
			'sw_lng': bounds[3],
			'pixel_width': this.mapControl.getMapWidth()
		},
		'center_lat' : this.mapControl.map.getCenter().lat(),
		'center_lng' : this.mapControl.map.getCenter().lng(),
		'location_types': this.searchInput.getSelectedLocationTypes(),
		'technology_types': this.searchInput.getSelectedTechnologyTypes()
	})
}

PointLoader.prototype.requestPoints = function() {
	try {
		//Geocode location and try to focus on it
		var ss_location = this.searchInput.getSearchStringLocation();
		if(this.useSearchStringLocation && ss_location != "") {
			//Generate an event to reload map and try to reposition on a location.
			this.requestGeocoding();
		} else {
			//Do the server request for points and process. Workflow is HERE->EVT_ON_RECEIVE_POINTS
			var url = this.settings.xmlrpc_filter_endpoint + "?" + this.constructSearchParams();
			log.logUrl(url);
			AjaxRequest.get({
					"url" : url,
					"onSuccess" : GEvent.callback(this, function(req) {
						GEvent.trigger(this, EVT_ON_RECEIVE_POINTS, req, this.searchInput.getSelectedLocationTypes());
						this.setBusy(false);
					}),
					"onError" : log_http_error
				}
			);
		}
	} catch(e) {
		log.fatal("Fatal error while requesting for points (" + e + ")" + e.message);
	}
}

PointLoader.prototype.requestGeocoding = function() {
	this.setBusy(true);
	try {
		var ss_location = this.searchInput.getSearchStringLocation();
		url = getAtlasGeocoderURL(ss_location, "", "", "google");
		AjaxRequest.get({
			"url" : url,
			"onSuccess" : GEvent.callback(this, function(req) {
				var items = req.responseXML.getElementsByTagName("item");
				if(!items.length) {
					log.debug("[MapControl.reloadMapWithGeocoding]Geocoding returned "
						+ "no options, map stays in same position. No marker refresh, no repositioning");
					return;
				}

				tag = items[0];
				var lat = parseFloat(tag.getAttribute("lat"));
				var lng = parseFloat(tag.getAttribute("lng"));
				var accuracy = parseFloat(tag.getAttribute("accuracy"));

				zoom = accuracy + 6;
				if (accuracy == 1)
					zoom = accuracy + 5;
				if (accuracy > 8)
					zoom = accuracy + 7;

				this.useSearchStringLocation = false;
				this.mapControl.moveMap(lat, lng, zoom);
				this.requestPoints();
			}),
			"onError" : log_http_error
		});
	} catch(e) {
		log.fatal("Fatal error while requesting for points (using geocoding)" + e);
	}
}

/**
 * Request points delayed - set a timer for reloading the map points
 */
PointLoader.prototype.requestPointsDelayed = function() {
	this.setBusy(true);

	if(this.timerId)
		clearTimeout(this.timerId);
	this.timerId = setTimeout(GEvent.callback(this, function() {
		this.timerId = null;
		this.requestPoints();
	}), UI_RELOAD_DELAY);
}

PointLoader.prototype.setBusy = function(busy) {
	if(this.busy == busy)
		return;
	this.busy = busy;
	GEvent.trigger(this, EVT_BUSY, busy);
}


function SearchInput(settings, tree_history) {
	this.settings = settings;
	this.technologyTree = new TechnologyTree(settings, tree_history);
	GEvent.addListener(this.technologyTree, "onChange", GEvent.callback(this, function() {
		GEvent.trigger(this, EVT_ON_REQUEST_NEW_SEARCH);
	}));
}

SearchInput.prototype.doSearch = function() {
	GEvent.trigger(this, EVT_ON_REQUEST_NEW_SEARCH, true);
}

SearchInput.prototype.getSelectedTechnologyTypes = function() {
	str = this.technologyTree.getChecked();
	ret = new Array();
	if(str.length > 0) {
		lst = str.split(",");
		for(i = lst.length - 1; i >= 0 ; i--) {
			if(lst[i].indexOf("ignore") < 0) {
				ret.push(lst[i]);
			}
		}
	}
	return ret;
}


SearchInput.prototype.getSelectedLocationTypes = function() {
	ret = new Array();
	filterListCtrl = document.getElementsByName("filter_location_type:list");
	for(i = 0; i < filterListCtrl.length; i++ ) {
		if (filterListCtrl[i].value != "") {
			ret.push(filterListCtrl[i].value);
		}
	}
	return ret;
}


SearchInput.prototype.onClickLocationType = function(id_lt) {
	var ctrlHidden = document.getElementById("lt_" + id_lt);
	var checked = ctrlHidden.value == "";
	ctrlHidden.value = checked ? id_lt : "";
	var ctrlImg = document.getElementById("img_lt_" + id_lt);
	ctrlImg.src = checked ? this.settings.base_url + "/var/icons/small/iconCheckAll.gif" :
		ctrlImg.src = this.settings.base_url + "/var/icons/small/iconUncheckAll.gif";
	GEvent.trigger(this, EVT_ON_REQUEST_NEW_SEARCH);
}


SearchInput.prototype.getSearchStringLocation = function() {
	var value =trim(document.getElementById("search_location").value);
	if (value == "Type area name here...") value = "";
	return value;
}


SearchInput.prototype.getSearchString = function() {
	var value =trim(document.getElementById("search_string").value);
	if (value == "Type keyword here...") value = "";
	return value;
}

function TechnologyTree(settings, tree_history) {
	var tree_obj = new dhtmlXTreeObject("technologyTypesTree","100%","100%",0);
	this.tree_obj = tree_obj;

	tree_obj.enableTreeImages(true);
	tree_obj.enableCheckBoxes(true);
	tree_obj.enableThreeStateCheckboxes(true);
	tree_obj.setImagePath(settings.icon_base_url + "var/icons/small/");
	tree_obj.loadXMLString(settings.technologyTypesXML);
	tree_obj.attachEvent("onClick", GEvent.callback(this, function(itemId) {
		tree_obj.setCheck(itemId, tree_obj.isItemChecked(itemId) ? 0 : 1);
		GEvent.trigger(this, "onChange");
	}));
	tree_obj.attachEvent("onOpenEnd", function(itemId) {
		var rootNodes = tree_obj.getSubItems(0).split(",");

		// see if itemId is a top-level node
		var index = -1;
		for(var i = 0; i < rootNodes.length; i ++)
			if(rootNodes[i] == itemId)
				index = i;
		if(index < 0)
			return;

		// it's a top-level node; collapse the other ones.
		for(var i = rootNodes.length - 1; i >= 0; i--) {
			var node = rootNodes[i];
			if(i != index)
				tree_obj.closeItem(node);
			else
				tree_obj.selectItem(node, false, false);
		}
	});
	tree_obj.attachEvent("onCheck", GEvent.callback(this, function(itemId) {
		GEvent.trigger(this, "onChange");
	}));
	//Restore tree status
	if(tt_history) {
		if(tt_history.length != "undefined" && tt_history.length > 0) {
			for(i = tt_history.length - 1; i >= 0 ; i--) {
				tree_obj.setCheck(tt_history[i], 1);
			}
		} else {
			tree_obj.setCheck(tt_history, 1);
		}
	}
}

TechnologyTree.prototype.getChecked = function() {
	return this.tree_obj.getAllChecked();
}


/**
 * Initialize new MapControl object.
 * @return Nothing
 */
function MapControl(settings) {
	this.settings = settings;
	this.filterControl = null;
	this.map = null;
	this.timerId = 0; //Timer ID object for throttling refreshes of map points
	this.iconManager = new AIconManager();
	this.mapTypeWidget = null;
	//Markers displayed at any moment of time on the map
	this.markers = new Array();
	//Marker for which related locations will be displayed
	this.markerRelated = null;
	//Displayed vertices on the map
	this.displayedRelations = new Array();
};


MapControl.prototype.initialize = function() {
	if(! GBrowserIsCompatible())
		return;
	map_div = document.getElementById(this.settings.map_div);
	log.debug("[MapControl.initialize]Starting map initialization");
	try {
		this.map = new GMap2(map_div);
		mapTypes = this.settings.map_types;
		var pos = new GControlPosition(G_ANCHOR_TOP_RIGHT, new GSize(10,10));
		this.mapTypeWidget = new MapTypeWidget();
		this.map.addControl(this.mapTypeWidget, pos);
		this.mapTypeWidget.setTypeMap();

		pos = new GControlPosition(G_ANCHOR_TOP_LEFT, new GSize(10,10));
		if(this.settings.map_control == "large") {
			this.map.addControl(new GLargeMapControl(), pos);
		}
		if(this.settings.map_control == "small") {
			this.map.addControl(new GSmallMapControl(), pos);
		}
		if(this.settings.map_scale) this.map.addControl(new GScaleControl());
		if(this.settings.mouse_wheel_zoom) { this.map.enableScrollWheelZoom(); } else { this.map.disableScrollWheelZoom(); }
		if(this.settings.double_click_zoom) { this.map.enableDoubleClickZoom(); } else { this.map.disableDoubleClickZoom(); }
		if(this.settings.continuous_zoom) { this.map.enableContinuousZoom(); } else { this.map.disableContinuousZoom(); }
		if(this.settings.map_overview) {
			ovc = new GOverviewMapControl();
			this.map.addControl(ovc);
		}
		if(mapTypes.length > 1 ) {
			var mapTypePosition = new GControlPosition(G_ANCHOR_TOP_LEFT, new GSize(10,10));
			//TODO: remove this, replaced by MapTypeWidget this.map.addControl(new GMapTypeControl(), mapTypePosition);
			for (i = 0; i < mapTypes.length; i++) {
				if( G_SATELLITE_MAP == this.friendlyMapType(mapTypes[i])) this.map.addMapType(G_SATELLITE_MAP);
				if( G_HYBRID_MAP == this.friendlyMapType(mapTypes[i])) this.map.addMapType(G_HYBRID_MAP);
				if( G_PHYSICAL_MAP == this.friendlyMapType(mapTypes[i])) this.map.addMapType(G_PHYSICAL_MAP);
				if( G_NORMAL_MAP == this.friendlyMapType(mapTypes[i])) this.map.addMapType(G_NORMAL_MAP);
			}
			//TODO: HACK for hybrid type
			useHybrid = false;
			for (i = 0; i < mapTypes.length; i++) {
				if (mapTypes[i] == "hybrid") {
					useHybrid = true;
					break;
				}
			}
			if(!useHybrid) this.map.removeMapType(G_HYBRID_MAP);
		}


		//Register handlers for map browsing (zoom, pan)
		GEvent.addListener(this.map, "moveend", GEvent.callback(this, function() {
			GEvent.trigger(this, EVT_ON_REQUEST_NEW_SEARCH);
		}));
		var pos = new GControlPosition(G_ANCHOR_TOP_LEFT, new GSize(0, 520));
		this.map.setMapType(this.friendlyMapType(this.settings.map_type_default));

		///Check if we have coordinates on request. If yes, then use them
		var initial_zoom = this.settings.initial_zoom;
		var ctrl = document.getElementById("zoom");
		if(ctrl) {
			val = parseInt(ctrl.value);
			if(!isNaN(val)) {
				initial_zoom = val;
			}
		}
		var initial_lat = this.settings.initial_lat;
		ctrl = document.getElementById("center_lat");
		if(ctrl) {
			initial_lat = parseFloat(ctrl.value);
			if(isNaN(initial_lat)) initial_lat = this.settings.initial_lat;
		}

		var initial_lng = this.settings.initial_lng;
		ctrl = document.getElementById("center_lng");
		if(ctrl) {
			initial_lng = parseFloat(ctrl.value);
			if(isNaN(initial_lng)) initial_lng = this.settings.initial_lng;
		}
		this.map.setCenter( new GLatLng( initial_lat, initial_lng ), initial_zoom );

		GEvent.trigger(this, EVT_ON_REQUEST_NEW_SEARCH);
	} catch(e) {
		map = null;
		log.fatal("[MapControl.initialize]Map initialization failed! (Reason:" + e + ")");
	}
}

MapControl.prototype.friendlyMapType = function(friendlyName) {
	if("normal" == friendlyName) return G_NORMAL_MAP;
	if("satellite" == friendlyName) return G_SATELLITE_MAP;
	if("hybrid" == friendlyName) return G_HYBRID_MAP;
	if("physical" == friendlyName) return G_PHYSICAL_MAP;
	return null;
};


/**
 * Change map type
 * @param friendlyMapTypeName one of "normal", "satellite", "hybrid", "physical"
 */
MapControl.prototype.changeMapType = function(friendlyMapTypeName) {
	this.map.setMapType(this.friendlyMapType(friendlyMapTypeName));
}


/**
 * Setup event handlers. In order to call one of the registered handlers here,
 * you generate the event via trigger:
 * 		GEvent.trigger(GLOBAL_EVENT, EVT_ON_event_name_string, param1, param2, ...);
 * GLOBAL_EVENT is default, but can be any object registered (first parameter when calling addListener)
 * This would execute the actual event handler for onResultsCount handler registered
 * by this method.
 * To register a new event handler call:
 * GEvent.addListener(context_object, event_name_string, callback_handler)
 * callback_handler is a function which handles the actual event. Can receive param1, param2 etc.
 * @return Nothing
 */
MapControl.prototype.bindPointLoader = function(pointLoader) {
	GEvent.addListener(pointLoader, EVT_ON_RECEIVE_POINTS, GEvent.callback(this, function(req, selected_lt) {
		count = req.responseXML.getElementsByTagName("xml")[0].getAttribute("num_rows");
		var ctResultsCount = document.getElementById("resultsCount");
		ctResultsCount.innerHTML = count;
		this.updateMapLocations(req, selected_lt);
	}));
}

MapControl.prototype.moveMap = function(lat, lng, zoom) {
	this.map.setCenter(new GLatLng(lat, lng), zoom);
}

/**
 * Callback method when point request finished.
 * @param req HTTP request object to extract data from
 */
MapControl.prototype.updateMapLocations = function(req, selected_lt) {
	var items = req.responseXML.getElementsByTagName("entry");
	log.debug("[MapControl.updateMapLocations]Got " + items.length + " items from server. Adding to map...");
	this.map.clearOverlays();
	this.markers = new Array();
	for(var i = 0; i < items.length; i++) {
		try {
			var tag = items[i];
			var id = parseInt(tag.getAttribute("id"));
			var lt_id = parseInt(tag.getAttribute("lt"));
			var tt_ids = tag.getAttribute("tt");
			var g_p = tag.getAttribute("g_p");
			g_p = g_p != "None" ? parseInt(g_p) : -1;
			var lat = tag.getAttribute("lat");
			var lng = tag.getAttribute("lng");
			marker = this.addMarker(id, lat, lng, selected_lt, lt_id, tt_ids, g_p);
			this.markers.push(marker);
		} catch(e) {
			log.fatal("[updateMapLocations]Failed to add point to map: id=" + id + "," + e);
		}
	}
	this.showRelatedLocations();
}

MapControl.prototype.addMarker = function(id, lat, lng, selected_lt, lt_id, tt_ids, g_p) {
	var icon = this.iconManager.getGoogleIcon(id, lt_id, tt_ids, selected_lt, g_p);
	if(!icon) {
		log.warn("No icon for location type with id=" + lt_id);
		return;
	}
	var objThis = this;
	var point = new GLatLng(lat, lng);
	var marker = null;
	if(id == -1) {
		marker = new GMarker(point, {icon : icon, title: g_p + " grouped locations. Click to zoom in on them"});
		marker.id = id;
		GEvent.addListener(marker, "click", GEvent.callback(this, function(latlng) {
			var zoom_value = this.getCurrentZoomLevel();
			zoom_value += 2;
			this.map.setCenter(latlng, zoom_value);
		}));
	} else {
		marker = new GMarker(point, {icon : icon});
		marker.id = id;
		marker.lt_id = lt_id;
		GEvent.addListener(marker, "click", GEvent.callback(this, function(latlng) {
			objThis.clearRelatedLocations();
			objThis.markerRelated = marker;
			GEvent.trigger(this, "marker_clicked", marker.getLatLng(), id);
		}));
	}
	this.map.addOverlay(marker);
	return marker;
}

MapControl.prototype.getCurrentZoomLevel = function() {
	return this.map.getZoom();
}

/**
 * @return An array of four elements: [NE_LAT, NE_LNG, SW_LAT, SW_LNG]
 */
MapControl.prototype.getBounds = function() {
	var bounds = this.map.getBounds();
	ne = bounds.getNorthEast();
	sw = bounds.getSouthWest();
	return [ne.lat(), ne.lng(), sw.lat(), sw.lng()];
}


MapControl.prototype.getMapWidth = function() {
	return this.map.getSize().width;
}

/**
 * @param lat Latitude of current marker (where lines will start from)
 * @param lng Longitude of current marker (where lines will start from)
 */
MapControl.prototype.showRelatedLocations = function() {
	if(this.markerRelated != null) {
		var start_point = this.markerRelated.getLatLng();
		url = gSettings.xmlrpc_related_locations_endpoint + "?id=" + this.markerRelated.id;
		var objThis = this;
		AjaxRequest.get({
			"url" : url,
			"onSuccess" : function(req) {
				var items = req.responseXML.getElementsByTagName("entry");
				if(items.length > 0) {
					var arr_related = new Array();
					for(j = 0; j < items.length; j++) {
						var item = items[j];
						arr_related.push(parseInt(item.getAttribute("id")));
					}

					gMapBalloon.hide();
					for (i = objThis.markers.length - 1; i >= 0; i--) {
						var rm = objThis.markers[ i ];
						if(rm.id != objThis.markerRelated.id && arr_related.indexOf(rm.id) >= 0) {
							var polyLine = new GPolyline([start_point, rm.getLatLng()], "#FF0000", 3, 1);
							objThis.map.addOverlay(polyLine);
							objThis.displayedRelations.push(polyLine);
						}
					}
				}
			},
			"onError" : log_http_error
		});
	}
}

MapControl.prototype.clearRelatedLocations = function() {
	this.markerRelated = null;
	if(this.displayedRelations && this.displayedRelations.length > 0) {
		for(i = 0; i < this.displayedRelations.length; i++) {
			try {
				overlay = this.displayedRelations[ i ];
				this.map.removeOverlay(overlay);
			} catch(e) {
				//Invalid overlay fail silently
			}
		}
	}
}

/////////////////////////// End of MapControl class ////////////////////////////

MapTypeWidget.Inherits(GControl);
/**
 * This is the map type control (changes from 'hybrid' to 'map', 'sattelite' etc.)
 */
function MapTypeWidget() { this.map = null; }
MapTypeWidget.prototype.Inherits = function(parent)
{
	if(arguments.length > 1)
		parent.apply(this, Array.prototype.slice.call(arguments, 1));
	else
		parent.call(this);
}


MapTypeWidget.prototype.initialize = function (map) {
	this.container = document.getElementById("map_type_ctrl");
	this.map = map;
	map.getContainer().appendChild(this.container);
	return this.container;
}


MapTypeWidget.prototype.setTypeMap = function() {
	this.resetTypes();
	var ctrl = document.getElementById("mtc_map");
	ctrl.setAttribute("class", "selected");
	ctrl.setAttribute("className", "selected");
	this.map.setMapType(G_NORMAL_MAP);
}


MapTypeWidget.prototype.setTypeSattelite = function() {
	this.resetTypes();
	var ctrl = document.getElementById("mtc_sat");
	ctrl.setAttribute("class", "selected");
	ctrl.setAttribute("className", "selected"); /* For IE browsers */
	this.map.setMapType(G_SATELLITE_MAP);
}


MapTypeWidget.prototype.setTypeHybrid = function() {
	this.resetTypes();
	var ctrl = document.getElementById("mtc_hyb");
	ctrl.setAttribute("class", "selected");
	ctrl.setAttribute("className", "selected"); /* For IE browsers */
	this.map.setMapType(G_HYBRID_MAP);
}


MapTypeWidget.prototype.setTypeTerrain = function() {
	this.resetTypes();
	var ctrl = document.getElementById("mtc_ter");
	ctrl.setAttribute("class", "selected");
	ctrl.setAttribute("className", "selected"); /* For IE browsers */
	this.map.setMapType(G_PHYSICAL_MAP);
}


MapTypeWidget.prototype.resetTypes = function() {
	var ctrl = document.getElementById("mtc_map");
	ctrl.setAttribute("class", "unselected");
	ctrl.setAttribute("className", "unselected"); /* For IE browsers */
	ctrl = document.getElementById("mtc_sat");
	ctrl.setAttribute("class", "unselected");
	ctrl.setAttribute("className", "unselected"); /* For IE browsers */
	ctrl = document.getElementById("mtc_hyb");
	ctrl.setAttribute("class", "unselected");
	ctrl.setAttribute("className", "unselected"); /* For IE browsers */
	ctrl = document.getElementById("mtc_ter");
	ctrl.setAttribute("class", "unselected");
	ctrl.setAttribute("className", "unselected"); /* For IE browsers */
}

/**
 * Globals
 * Main map control displayed into the page
 */

function log_http_error(req) {
	log.fatal("Error loading points from server. Server failure (" + req.statusText + ").\n" + req.responseText);
}

function set_busy(busy) {
	var bCtrl = document.getElementById("busy");
	bCtrl.style.display = busy ? "block" : "none";
	document.getElementById("wrap").style.cursor = busy ? "wait" : "default";
	document.getElementById("widget_conainer").style.cursor = busy ? "wait" : "default";
}

gMapBalloon = null;

/**
 * Main entry point into framework. Called on "onload" event of document.
 * @return Nothing
 */
function js_main() {
	var mapControl = new MapControl(gSettings);
	var searchInput = new SearchInput(gSettings, tt_history);
	var pointLoader = new PointLoader(gSettings, mapControl, searchInput);

	mapControl.initialize();

	var mapBalloon = new MapBalloon(mapControl);
	mapControl.map.addControl(mapBalloon);
	mapBalloon.hide();
	GEvent.addListener(pointLoader, EVT_ON_RECEIVE_POINTS, function() {
		mapBalloon.hide();
	});
	GEvent.addListener(pointLoader, EVT_BUSY, function(busy) {
		if(busy)
			mapBalloon.hide();
	});
	GEvent.addListener(mapControl, "marker_clicked", function(latlng, id) {
		mapBalloon.show(latlng, id);
	});

	GEvent.addListener(pointLoader, EVT_BUSY, set_busy);

	// set up some globals
	window.gPointLoader = pointLoader;
	window.gMapControl = mapControl;
	window.gMapBalloon = mapBalloon;
	gMapBalloon = mapBalloon;

	window.button_doSearch = function() {
		searchInput.doSearch();
	}

	window.event_onClickLocationType = function(id_lt) {
		searchInput.onClickLocationType(id_lt);
	}

	//If we have data into search_string / search_location, black the text
	if(searchInput.getSearchString() != "") {
		document.getElementById("search_string").style.color = "black";
	}
	if(searchInput.getSearchStringLocation() != "") {
		document.getElementById("search_location").style.color = "black";
	}
};
