/* 
 * Advanced Mapping Options.
 */
AllhomesAdvMapOptions={
   displaySaleIcons:true,
   displayRentalIcons:true
}

/*
 * Marker managers.
 */
var saleMarkerMgr = null;
var rentalMarkerMgr = null;

/*
 * Listing icons.
 */
var saleIcon = null;
var rentalIcon = null;
var saleLargeIcon = null;
var rentalLargeIcon = null;

/*
 * Advanced Map marker state.
 * The state is stored on each AJAX call back to retrieve listing points.
 * It is used to prevent multiple callbacks for the same bounding box and zoom levels.
 */
AdvMapMarkerState={
   zoomLevel:null,
   swlat:null,
   swlng:null,
   nelat:null,
   nelng:null
}

/*
 * Initialise the Advanced Map.
 * Behaviour is to show Listing Markers.
 */
function initialiseAdvancedMap(map, lat, lng, zoom) {
	initialiseAdvancedMapWithoutMarkers(map, lat, lng, zoom);
	initialiseMapMarkers(map);
}

/*
 * Initialise the Advanced Map only but don't show Markers.
 * This is required when viewing Google Maps in ACT.
 */
function initialiseAdvancedMapWithoutMarkers(map, lat, lng, zoom) {
	map.setCenter(new GLatLng(lat, lng), zoom);
	map.addControl(new GLargeMapControl());
	map.addControl(new GMapTypeControl());
	map.addControl(new GOverviewMapControl());
}

/*
 * Initialise the Advanced Map without the Map Type controls.
 */
function initialiseAdvancedMapWithoutControls(map, lat, lng, zoom) {
	map.setCenter(new GLatLng(lat, lng), zoom);
	map.addControl(new GLargeMapControl());
	map.addControl(new GOverviewMapControl());
initialiseMapMarkers(map);
}

/*
 * Add a click event listener to change the map type to the given map type.
 */
function addMapTypeClickListener(map, element, selected, mapType) {
	// Check if DOM event listener is allowed (Newer browsers).
	var triggerAnyOtherListeners = true;
	element.onclick = function() {map.setMapType(mapType);};

	// If the map type is selected as default
	if (selected) {
		map.setMapType(mapType);
		element.checked = true;
	}
}

/*
 * Add a click event listener to change the map type to 'normal'
 * view of the street map.
 */
function addStreetMapTypeClickListener(map, element, selected) {
	addMapTypeClickListener(map, element, selected, G_NORMAL_MAP);
}

/*
 * Add a click event listener to change the map type to aerial/satellite
 * photo view.
 */
function addPhotoMapTypeClickListener(map, element, selected) {
	addMapTypeClickListener(map, element, selected, G_SATELLITE_MAP);
}

/*
 * Add a click event listener to change the map type to combined
 * street map and aerial/satellite view.
 */
function addHybridMapTypeClickListener(map, element, selected) {
	addMapTypeClickListener(map, element, selected, G_HYBRID_MAP);
}

 /*
 * Initialise markers and marker functionality.
 */
function initialiseMapMarkers(map) {
	// Initialise marker managers.
	saleMarkerMgr = createSaleMarkerManager(map);
	rentalMarkerMgr = createRentalMarkerManager(map);
	
	// Initialise marker icons.
	saleIcon = createSaleIcon();
	rentalIcon = createRentalIcon();
	saleLargeIcon = createLargeSaleIcon();
	rentalLargeIcon = createLargeRentalIcon();
	
	// Add an event handler to update the markers when map is moved.
	GEvent.addListener(map, "moveend", function() {
	   createAllListingMarkers(map);
	});
	
	// Add listing markers to map
	createAllListingMarkers(map);
	
	// Add checkbox listeners to switch house markers on and off.
	// Initialise checkboxes from adv map options defaults.
	var saleIconsCB = document.getElementById("saleIconsCB");
	saleIconsCB.checked = AllhomesAdvMapOptions.displaySaleIcons;
	saleIconsCB.onclick=function() {toggleSaleIcons(saleIconsCB, map);};
		
	var rentalIconsCB = document.getElementById("rentalIconsCB");
	rentalIconsCB.checked = AllhomesAdvMapOptions.displayRentalIcons;
	rentalIconsCB.onclick=function() {toggleRentalIcons(rentalIconsCB, map);};
}

/*
 * Initialize the sale marker manager.
 */
function createSaleMarkerManager(map) {
	return new MarkerManager(map);
}

/*
 * Initialize the rental marker manager.
 */
function createRentalMarkerManager(map) {
	return new MarkerManager(map);
}

/*
 * Changes the state of the Allhomes.AdvMapOptions.dispalySaleIcons property to the
 * state of the specified checkbox.
 * dispalySaleIcons is used to determine whether sale icons are displayed on the map.
 * 
 * Parameters:
 *    saleIconsCB - the checkbox element that is used to change display state.
 *    map - the map this checkbox is associated with.
 */
function toggleSaleIcons(saleIconsCB, map) {
   AllhomesAdvMapOptions.displaySaleIcons = saleIconsCB.checked;
   saleMarkerMgr.clearMarkers();
   AdvMapMarkerState.zoomLevel=null;
   AdvMapMarkerState.swlat=null;
   AdvMapMarkerState.swlng=null;
   AdvMapMarkerState.nelat=null;
   AdvMapMarkerState.nelng=null;
   createAllListingMarkers(map);
};

/*
 * Changes the state of the Allhomes.AdvMapOptions.dispalyRentalIcons property to the
 * state of the specified checkbox.
 * dispalyRentalIcons is used to determine whether rental icons are displayed on the map.
 * 
 * Parameters:
 *    rentalIconsCB - the checkbox element that is used to change display state.
 *    map - the map this checkbox is associated with.
 */
function toggleRentalIcons(rentalIconsCB, map) {
   AllhomesAdvMapOptions.displayRentalIcons = rentalIconsCB.checked;
   rentalMarkerMgr.clearMarkers();
   AdvMapMarkerState.zoomLevel=null;
   AdvMapMarkerState.swlat=null;
   AdvMapMarkerState.swlng=null;
   AdvMapMarkerState.nelat=null;
   AdvMapMarkerState.nelng=null;
   createAllListingMarkers(map);
};
	  
/*
 * Create a sale listing marker. 
 * 
 * Parameters:
 *    listingId - the listing id
 *    latlng    - GLatLng
 */
function createSaleMarker(latlng) {
	if (AdvMapMarkerState.zoomLevel > 17) {
		return new GMarker(latlng, saleLargeIcon);
	}
	else {
		return new GMarker(latlng, saleIcon);
	}
}

/*
 * Create a rental listing marker. 
 * 
 * Parameters:
 *    listingId - the listing id
 *    latlng    - GLatLng
 */
function createRentalMarker(latlng) {
	if (AdvMapMarkerState.zoomLevel > 17) {
		return new GMarker(latlng, rentalLargeIcon);
	}
	else {
		return new GMarker(latlng, rentalIcon);
	}
}

/**
 * Create sale icon.
 * 
 * Returns:
 *    GIcon
 */
function createSaleIcon() {
	var icon = new GIcon();
   icon.iconAnchor = new GPoint(8, 8);
   icon.infoWindowAnchor = new GPoint(15, 0);
   icon.iconSize = new GSize(15, 15);
   icon.image = "/ah/image/red_house.gif";
   return icon;
}

/**
 * Create sale icon.
 * 
 * Returns:
 *    GIcon
 */
function createRentalIcon() {
	var icon = new GIcon();
   icon.iconAnchor = new GPoint(8, 8);
   icon.infoWindowAnchor = new GPoint(15, 0);
   icon.iconSize = new GSize(15, 15);
   icon.image = "/ah/image/grey_house.gif";
   return icon;
}

/**
 * Create large sale icon.
 * 
 * Returns:
 *    GIcon
 */
function createLargeSaleIcon() {
	var icon = new GIcon();
   icon.iconAnchor = new GPoint(8, 30);
   icon.infoWindowAnchor = new GPoint(30, 0);
   icon.iconSize = new GSize(30, 30);
   icon.image = "/ah/image/buy_house_30px.gif";
   return icon;
}

/**
 * Create sale icon.
 * 
 * Returns:
 *    GIcon
 */
function createLargeRentalIcon() {
	var icon = new GIcon();
   icon.iconAnchor = new GPoint(8, 30);
   icon.infoWindowAnchor = new GPoint(30, 0);
   icon.iconSize = new GSize(30, 30);
   icon.image = "/ah/image/rent_house_30px.gif";
   return icon;
}

/*
 * Send an asynchronous request for sale listing markers in map bounds.
 */
function createSaleMarkers(map) {
   if (AllhomesAdvMapOptions.displaySaleIcons == true) {
      createListingMarkers(map, 'sales');
	}
}

/*
 * Send an asynchronous request for rental listing markers in map bounds.
 */
function createRentalMarkers(map) {
   if (AllhomesAdvMapOptions.displayRentalIcons == true) {
      createListingMarkers(map, 'rental');
	}
}

/*
 * Send an asynchronous request for sale and rental listing markers in map bounds.
 */
function createAllListingMarkers(map) {
	if (AllhomesAdvMapOptions.displaySaleIcons == true && AllhomesAdvMapOptions.displayRentalIcons == true) {
      createListingMarkers(map, 'all');
	}
	else if (AllhomesAdvMapOptions.displayRentalIcons == true) {
		createRentalMarkers(map);
	}
	else if (AllhomesAdvMapOptions.displaySaleIcons == true) {
		createSaleMarkers(map);
	}
	else {
		// Clear all markers.
		saleMarkerMgr.clearMarkers();
		rentalMarkerMgr.clearMarkers();
	}
}

/*
 * Send an asynchronous request for listing markers in map bounds.
 * The response will be a JSON object containing a list of listing ids, geographic coordinates and summary urls.
 */
function createListingMarkers(map, listingType) {

   var bounds = map.getBounds();
   var southWest = bounds.getSouthWest();
   var northEast = bounds.getNorthEast();
   
   // these are our current map boundaries in latitude(Y) and longitude(X)
   var swlat = southWest.lat();
   var swlng = southWest.lng();
   var nelat = northEast.lat();
   var nelng = northEast.lng();
   
   // current map zoom level
   zoomLevel = map.getZoom();
   
   // reset requestRequired before checking conditions
   var requestRequired = false;
   
   // Check if bounding box or zoom level has changed.  If so re-request markers.
   // If our current view port is inside the current marker state request port
	if (AdvMapMarkerState.swlat > swlat || 
	    AdvMapMarkerState.swlng > swlng ||
	    AdvMapMarkerState.nelat < nelat ||
	    AdvMapMarkerState.nelng < nelng) {
		
		requestRequired = true;
	}
	else if (AdvMapMarkerState.zoomLevel != zoomLevel) {
		// Lets get new ones at all zooms.
		requestRequired = true;
	}
	
	// let's request some markers again
	if (requestRequired == true) {
		
		// Keep 3 decimal places.  We never seem to need any more than this.
		var factor = 1000.0
		
		// Buffer and round boundaries to whole numbers so that request results can be cached.
		swlat = Math.floor(swlat * factor) / factor;
		swlng = Math.floor(swlng * factor) / factor;
		nelat = Math.ceil(nelat * factor) / factor;
		nelng = Math.ceil(nelng * factor) / factor;   

		var latdiff = Math.abs(nelat - swlat);
		var lngdiff = Math.abs(nelng - swlng);

		swlat -= latdiff;
		nelat += latdiff;
		swlng -= lngdiff;
		nelng += lngdiff;

		var latFactor = findFactor(Math.abs(nelat - swlat));
		var lngFactor = findFactor(Math.abs(nelng - swlng));

		// Find the largest rounding factor required.
		var factor = latFactor;
		if (lngFactor > latFactor) {
			factor = lngFactor;
		}
		
		// Snap to a reasonable interval for zoom level so request is cacheable.
		var averageGeoDist = Math.floor((latdiff + lngdiff)/2);
		var snapInterval = getSnapInterval(averageGeoDist);

		swlat = snapDown(swlat, snapInterval, factor);
		nelat = snapUp(nelat, snapInterval, factor);
		swlng = snapDown(swlng, snapInterval, factor);
		nelng = snapUp(nelng, snapInterval, factor);

		// Update marker state.
		AdvMapMarkerState.zoomLevel = zoomLevel;
		AdvMapMarkerState.swlat = swlat;
		AdvMapMarkerState.swlng = swlng;
		AdvMapMarkerState.nelat = nelat;
		AdvMapMarkerState.nelng = nelng;
	   
	   new Ajax.Request('/svc/mapping/listing-points', {
	      method:'get',
	      parameters: {type: listingType, swlat: swlat, swlng: swlng, nelat: nelat, nelng: nelng},
	      onSuccess: function(transport) {
	         updateMapListingMarkers(transport.responseJSON, map);
	      }
	   });
	}
}

/**
 * Find the rounding factor for a given number.
 * Any number that is greater or equal to 1 returns a factor of 1.
 */
function findFactor(number) {
   var mystr = number.toString();

   var factor = 1;
   
   if (number < 1) {
      var zeroCount = 0;
      for (var i = 0, len = mystr.length; i < len; i++) {
         if (mystr.charAt(i) == "0") {
            zeroCount++;
         }
         else if (mystr.charAt(i) == ".") {
            // Skip but don't count the decimal point.
            continue;
         }
         else {
            // Stop counting zeros as soon as we hit a number other than zero.
            break;
         }
      }
      factor = Math.pow(10,zeroCount);
   }
   
   return factor;
}

/**
 * Round up to the next whole 5 interval.
 * e.g. 34.13 will round to 35 or -53.1 will round to -50
 */
function snapUp(number, interval, factor) {
	if (number != 0.0 && interval != 0) {
		number = Math.ceil(number * factor) / factor;
		number = Math.floor((number + interval) / interval) * interval;
	}
	// Round again to get rid of extra decimal places due to javascript rounding problems, maybe to do with floats...
	return Math.ceil(number * factor) / factor;
}

/**
 * Round down to the next whole 5 interval.
 * e.g. 34.13 will round to 30 or -53.1 will round to -55
 */
function snapDown(number, interval, factor) {
	if (number != 0.0 && interval != 0) {
		number = Math.floor(number * factor) / factor;
		number = Math.ceil((number - interval) / interval) * interval;
	}
	// Round again to get rid of extra decimal places due to javascript rounding problems, maybe to do with floats...
	return Math.floor(number * factor) / factor;
}

/**
 * Round to closest specified interval.
 * e.g. 34.13 snapped to 0.1 is 34.1 or -53.1 snapped to 5 is -55
 */
function snapToNearest(number, interval) {
	if (number != 0.0 && interval != 0) {
		number = Math.round(number / interval) * interval;
	}
	return number;
}

/**
 * Return a snap interval that makes sense for the specified zoom level.
 */
function getSnapInterval(averageGeoDist) {

	// 1% percent of the size of the image
	snapInterval = Math.ceil(0.01*averageGeoDist);
	
	return snapInterval;
}

/**
 * Handles json listingPoints and updates the markers on the map.
 * 
 * @param listingPoints ListingPoints JSON object. 
 * @param map Google map.
 */
function updateMapListingMarkers(listingPoints, map) {   
   // Parse sale listing points from the DOM         
   var saleMarkers = createMarkers(listingPoints.saleListingPoint, map, createSaleMarker);
   
   saleMarkerMgr.clearMarkers();
   saleMarkerMgr.addMarkers(saleMarkers, 3);
   // saleMarkerMgr.addMarkers(saleMarkers);
   saleMarkerMgr.refresh();
   
   // Parse rental listing points from the DOM
   var rentalMarkers = createMarkers(listingPoints.rentalListingPoint, map, createRentalMarker);

   rentalMarkerMgr.clearMarkers();
   rentalMarkerMgr.addMarkers(rentalMarkers, 3);
   // rentalMarkerMgr.addMarkers(rentalMarkers);
   rentalMarkerMgr.refresh();
}

/**
 * Create markers from array of listing points.
 * 
 * @param markers Array of listing points
 * @param map Google map
 * @param createMarker function reference that creates the specific type of marker required (sale/rental)
 * @returns {Array} of GMarkers
 */
function createMarkers(markers, map, createMarker) {
   var markersArray = new Array();
   for ( var i = 0; i < markers.length; i++) {
      // Longitude and latitude nodes
      var latlng = new GLatLng(markers[i].latitude, markers[i].longitude);

      // Create sale listing marker
      var marker = createMarker(latlng);

      addMarkerInfo(marker, map, markers[i].url, latlng);
      
      markersArray.push(marker);
   }
   return markersArray;
}

/*
 * Display info window containing listing details on markers click event. 
 */  
function addMarkerInfo(marker, map, listingSummaryUrl, latlng) {
   GEvent.addListener(marker, "click", function() {
      var listingXHtml = queryListingDetailXHTML(listingSummaryUrl);
      map.openInfoWindowHtml(latlng, listingXHtml);
      
      // Check if we have street view available.
      checkStreetView(marker.getLatLng());
   });  	
}

/*
 * Send a synchronous request for listing detail XHTML.
 * 
 * Parameters:
 *    listingId - the listing id
 * 
 * Returns: 
 *    the response XML document
 */
function queryListingDetailXHTML(listingSummaryUrl) {
   var httpRequest = createXmlRequest();

   if (!httpRequest) {
      // Failed to create an XMLHttpRequest instance.
      return false;
   }
   
   httpRequest.open('GET', listingSummaryUrl, false);
   httpRequest.send(null);
   
   return httpRequest.responseText;
}

/**
 * Open a Panorama Street View Bubble. 
 */
function openPanoramaBubble() {
	// Find the current open info window.
	var infoWindow = map.getInfoWindow();

	// Find the lat and long.
	var latLng = infoWindow.getPoint();

	// Create the full screen element.
	var contentNode = document.createElement('div');
	contentNode.id = 'panoFullGMap2';
	contentNode.className = 'googlePanoramaFullSize';
	contentNode.innerHTML = 'Loading panorama';
	map.openInfoWindow(latLng,
			"<h1 class='googlePanoramaH1'>Street View</h1><div id='panoGMap2' class='googlePanoramaInfoSize'></div>",
			{maxContent: contentNode, maxTitle: "Full Screen Street View"}
	  );
	
	// Which way to look
	var bearingAngle = calculateBearing(window.googleReturnedLatLong, latLng);
	
	// Create the Flash Street View object.
	panorama = new GStreetviewPanorama(document.getElementById("panoGMap2"));
	// Set the location of the Street View.
	panorama.setLocationAndPOV(latLng, {yaw: bearingAngle});

	// Add a listener for the maximise button.
	GEvent.addListener(infoWindow, "maximizeend", function() {
		panorama.setContainer(contentNode);
		window.setTimeout("resizeFullScreenPanorama();", 5);
	});
}

 /**
  * Calculate the bearing from startlatlong to destlatlong
  * Used information from: http://www.movable-type.co.uk/scripts/latlong.html
  */
function calculateBearing(startPos, destPos) {
	var DEGREES_PER_RADIAN = 180.0 / Math.PI;
	
	var deltaLng = destPos.lngRadians() - startPos.lngRadians();
	var lat1 = startPos.latRadians();
	var lat2 = destPos.latRadians();
	var y = Math.sin(deltaLng) * Math.cos(lat2);
	var x = Math.cos(lat1)*Math.sin(lat2) -
    	    Math.sin(lat1)*Math.cos(lat2)*Math.cos(deltaLng);
	
	// Convert to normalised degrees
	return  (Math.atan2(y, x) * DEGREES_PER_RADIAN + 360) % 360;
}

/**
 * Resizes the panorama to fit the info full screen box.
 * 
 * The height and width are calculated from the parent node.
 */
function resizeFullScreenPanorama() {
	var panoContainer = document.getElementById('panoFullGMap2');
	var PADDING = 15;

	if (panoContainer) {
		var calcWidth = panoContainer.parentNode.offsetWidth - PADDING;
		panoContainer.style.width = calcWidth + 'px';

		var calcHeight = panoContainer.parentNode.offsetHeight - PADDING;
		panoContainer.style.height = calcHeight + 'px';
		
		// Ask the panorama to resize
		panorama.checkResize();
	}
}

/**
 * Check if a given GLatLng has a nearby street view panorama.
 *  
 * @param latlng The GLatLng to check.
 */
function checkStreetView(latlng) {
	var streetViewClient = new GStreetviewClient();
	streetViewClient.getNearestPanoramaLatLng(latlng, findStreetView);
}

/**
 * This is called asynchronously from checkStreetView and is used to enable the
 * Street View link if a nearby street view panorama was found.
 *   
 * @param latlng The GLatLng of nearby streetview, null if not found.
 */
function findStreetView(latlng) {
	if (latlng != null) {
		window.googleReturnedLatLong = latlng;
		// We delay showing the link initially because we need to make sure the popup has opened first.
		window.setTimeout("showStreetViewLink();", 5);
	}
}

/**
 * Enable the street view link.
 */
function showStreetViewLink() {
	// Show the street view link.
	pStreetView = document.getElementById('streetViewLink');
	if (pStreetView) {
		pStreetView.style.display = 'block';
	}
}
