$(document).ready(function() {

	var mapContainerId			= 'gSearchMap';
	var $mapContent             = $('#'+mapContainerId);
	
	// Only execute this script when #gMap exists
	if (!$mapContent.length) 	return;
	
	//path configurations
	var placesJsonUrl           = '/javascripts/places_20110819_1630.js';
	var markerJsonUrl           = '/javascripts/stores_20110718_1545.js';
	//var markerImagePath         = '/images/storelocator';
	
	//initialize some shared variables
	var defaultZoom             = 9;
	//var defaultLatLng           = new google.maps.LatLng(52.132633, 5.291266); //center of the Netherlands
	//var defaultLatLng           = new google.maps.LatLng(52.08788999999999, 4.8851869999999735); //woerden
	var defaultLatLng           = new google.maps.LatLng(52.013859, 4.709789999999998); //gouda
	
	var messages                = {
		'searchNotice'          : 'Klik op een icoontje in de kaart voor het adres en de routebeschrijving.',
		'postalCodeInvalid'     : 'De ingevoerde postcode is ongeldig.',
		'noSearchTerm'          : 'Voer een zoekterm in.',
		'noPostalCode'          : 'Voer een postcode in.',
		'routeNotice'           : 'De routebeschrijving opent in een nieuw venster.',
		'locationNotFound'      : 'Er is geen locatie gevonden.'
	};
	var zoomLevels              = [14, 13, 12, 11, 10, 9, 8, 7]; //the zoom levels for the km ranges (1, 2, 5, 10, 25, 50, 100, 1000)
	var distances               = [1000, 2000, 5000, 10000, 25000, 50000, 100000, 1000000];
	var searchType              = false; //false = postal code, true = city
	var hasErrorWindow          = false;
	var hasSearch               = false;
	var searchZoomType          = true; //false = point, true = bounds / undefined
	var searchCenter            = null;
	var searchZoom              = 0;
	var isForeignCountry        = false;
	var placesData              = null;
	var closestCity             = null;

	//load some jQuery objects into memory (to cache the DOM calls)
	var $mapNotice              = $('#mapNotice');
	var $mapFormPostalCode      = $('#mapFormPostalCode');
	var $mapFormPostalCodeInput = $('#mapFormPostalCodeInput').val('');
	var $mapFormRange           = $('#mapFormRange');
	var $mapFormRangeSelect     = $('#mapFormRangeSelect').val(2);
	var $mapFormCity            = $('#mapFormCity');
	var $mapFormCityInput       = $('#mapFormCityInput').val('');
	var $shop_600_or_less       = $('input#shop_600_or_less').attr('checked', 'checked').removeAttr('disabled');
	var $shop_10100_or_less     = $('input#shop_10100_or_less').attr('checked', 'checked').removeAttr('disabled');
	var $shop_10100_or_more     = $('input#shop_10100_or_more').removeAttr('checked').removeAttr('disabled');

	//initialize the map
    var mapOptions              = {
      'zoom'                    : defaultZoom,
      'center'                  : defaultLatLng,
      'mapTypeId'               : google.maps.MapTypeId.ROADMAP,
	  'scaleControl'            : true
    };
    var map                     = new google.maps.Map(document.getElementById(mapContainerId), mapOptions);
	var geocoder                = new google.maps.Geocoder();
	
	//initialize the markers
	var markers                 = [];
	var markerData              = [];
	var markerIcon              = null;
	var infoBox                 = null;
	var infoBoxIdleRestore      = false;
	var infoBoxTitle            = null;
	
	var ignoreOffset = 0;
	var j;
	
	//get the places data in a JSON request
	$.getJSON(placesJsonUrl, function(data){
		placesData = data;
	});
	
	//get the marker data in a JSON request
	$.getJSON(markerJsonUrl, function(data) {
		
		//iterate the marker data
		for (var i = 0; i < data.store.length; i++) {
		
			//ignore markers with a paylimit of 0
			j = i - ignoreOffset;
						
			//create the marker object
			markers[j] = new google.maps.Marker({
				position: new google.maps.LatLng(data.store[i].geocoord.latitude, data.store[i].geocoord.longitude)
			});
			
			//store some additional marker info
			markerData[j] = {
				'description': data.store[i].description,
				'postalCode': data.store[i].zipCode.substr(0, 4) + ' ' + data.store[i].zipCode.substr(4, 2),
				'address': data.store[i].address,
				'city': data.store[i].city,
				'title': infoBoxTitle,
				'inSearchRadius': false
			};
			
			//check for custom content
			if (typeof data.store[i].content != 'undefined') {
				markerData[j].content = data.store[i].content;
			}

			//create closure (to copy the value of `i`)
			(function(i) {
				//bind the marker click handler
				google.maps.event.addListener(markers[i], 'click', function() {

					var contentString = '';
					contentString += (markerData[i].title ? '<h1>' + markerData[i].title + '</h1>' : '');
					contentString += (markerData[i].description ? '<p>' + markerData[i].description + '</p>' : '');
					contentString += '<p>' + markerData[i].address + '<br>' + markerData[i].postalCode + '<br>' +  markerData[i].city + '</p>';

					var infowindow = new google.maps.InfoWindow({
					    content: contentString
					});

					infowindow.open(map,markers[i]);
					return;	
				});
			})(j);
		}
		
		//make sure the main office marker is visible by default
		// SHOW NOTHING markers[markers.length - 1].setMap(map);
	});
	
	//bind the event handler for the search form
	$('form#mapForm').bind('submit', function(event) {
	
		event.preventDefault();
		var searchTerm = $mapFormPostalCodeInput.val();
		
		if (searchType) {
		
			//city search
			searchTerm = $mapFormCityInput.val();
			
			//check for no city
			if (/^\s*$/.test(searchTerm)) {
				$mapNotice.addClass('errorMessage').text(messages.noSearchTerm);
				return;
			}
			else {
				//hack to force result
				searchTerm = searchTerm + " NL";
			}
			
		} else {
		
			//format the postal code input
			searchTerm = formatPostalCode($mapFormPostalCodeInput.val());
			
			//check for errors
			if (searchTerm == 'empty') {
				$mapNotice.addClass('errorMessage').text(messages.noSearchTerm);
				return;
			}
			if (searchTerm == 'invalid') {
				$mapNotice.addClass('errorMessage').text(messages.postalCodeInvalid);
				return;
			}
		}
		
		//do a geocode search request
		searchMap(searchTerm);
			
	});
	
	//set the search type to postal code on click
	$mapFormPostalCodeInput.bind('focus', function(event) {
		searchType = false;
		$mapFormRange.removeClass('disabled');
		$mapFormPostalCode.removeClass('disabled');
		$mapFormCity.addClass('disabled');
		$mapFormCityInput.val('');
	});
	
	//set the search type to city on click
	$mapFormCityInput.bind('focus', function(event) {
		searchType = true;
		$mapFormCity.removeClass('disabled');
		$mapFormRange.addClass('disabled');
		$mapFormPostalCode.addClass('disabled');
		$mapFormPostalCodeInput.val('');
	});
	
	//bind the event handler for the map's idle event
	google.maps.event.addListener(map, 'idle', function() {
	
		//if the idle action comes after a zoom action
		if (infoBoxIdleRestore) {
			infoBoxIdleRestore = false;
			
			//if an infobox is to be visible reset the z-index
			$mapContent.find('div.infoBox').parent().css('display', 'block');
		}
	});
	
	//bind the event handler for the map's zooming
	google.maps.event.addListener(map, 'zoom_changed', function() {
	
		//hide the infobox if it is visible
		var $infoBoxDiv = $mapContent.find('div.infoBox').parent();
		if ($infoBoxDiv.length > 0) {
			infoBoxIdleRestore = true;
			
			//hide the infobox
			$infoBoxDiv.css('display', 'none');
		}
	});
	
	/**
	 * Formats a postal code so that it always has the same specifications.
	 *
	 * @param  string postalCode the unformatted postal code
	 * @return string postalCode the formatted postal code or the error notice 'empty' or 'invalid'
	 */
	function formatPostalCode(postalCode) {
	
		//remove any whitespace
		postalCode = postalCode.replace(/\s+/g, '');
	
		//check for no postalCode
		if (postalCode.length < 1) {
			return 'empty';
		}
		
		//check for an invalid postal code
		if (!/^\d{4}[a-zA-Z]{0,2}$/.test(postalCode)) {
			return 'invalid';
		}
		
		//format the postal code
		searchTerm = postalCode.substr(0, 4) + ' ' + postalCode.substr(4);
		
		//add letters to the postal code (if missing)
		while (searchTerm.length < 7) {
			searchTerm += 'a';
		}
		return searchTerm;
	}
	
	/**
	 * Checks the visible part of the map for visible markers.
     * Shows an error dialog if there are no markers visible.
	 *
	 * @return boolean hasVisibleMarkers will be true if markers are visible in the map
	 */
	function checkVisibleMarkers() {
		
		//check if the current view has visible markers
		var hasVisibleMarkers = false;
		var bounds = map.getBounds();
		var markerLatLng;
		var centerPoint = map.getCenter();
		var distance = -1;
		var closestDistance = -1;

		//iterate all the markers
		for (var i = 0; i < markers.length; i++) {
			markerLatLng = markers[i].getPosition();
			
			//check if the marker is contained within the current view
			if (bounds.contains(markerLatLng) && markerData[i].inSearchRadius) {
				hasVisibleMarkers = true;
				break;
			}
			
			//check if the marker is closest to the current view
			distance = google.maps.geometry.spherical.computeDistanceBetween(centerPoint, markerLatLng);
			if (distance < closestDistance || closestDistance < 0) {
				closestDistance = distance;
				closestCity = markerData[i].city;
			}
		}
		
		//if no markers are visible and the error window is not already shown
		return hasVisibleMarkers;
	}

	
	/**
	 * Open a small modal popup containing an error message.
	 * If a previous error message is still visible, this method returns instantly.
	 *
	 * @param object error the content for the error window, requires a 'title' and 'message' property
	 */
	function showErrorWindow(error) {

		//make sure no more then 1 error window is created
		if (hasErrorWindow) {
			return;
		}
		hasErrorWindow = true;
		
		//show the error window
		$.openPopup({
			'showAnimation': 'none',
			'transitionAnimation': 'none',
			'hideAnimation': 'none',
			'name': 'Info',
			'startWidth': 303,
			'minWidth': 303,
			'startHeight': 100,
			'minHeight': 100,
			'content': '<h1>'+ error.title + '</h1>' +
					   '<div class="boxText">' +
					   '    <p>' + error.message + '</p>' +
					   '</div>',
			'showHandler': function() {
				//unfocus the search form
				$mapFormPostalCodeInput.blur();
				$mapFormCityInput.blur();
			},
			'hideHandler': function(){
				//make sure a new error window can be created
				hasErrorWindow = false;
			}
		});
	}
		
	/**
	 * Does a geocode request based on a search term and updates the map.
	 * Either the map's center position is moved, markers are show/hidden
	 * or an error message/dialog is shown.
	 *
	 * @param string  searchTerm    the text to search for in the geocode request
	 * @param boolean hasMainOffice autofits to show all visible markers if set to true, centers on the search result if set to false
	 */
	function searchMap(searchTerm) {

		//do the geocode request
		geocoder.geocode({
			'address': searchTerm
		}, function(results, status) {
			
			//if the location was not found
			if (status != google.maps.GeocoderStatus.OK) {
				//show an inline error message
				$mapNotice.addClass('errorMessage').text(messages.locationNotFound);
				return;
			}
			
			//make sure the first successful search action gets registered
			hasSearch = true;
			
			//default zoom: 5km
			var zoomLevel = 2;
			
			//check the country of the result
			isForeignCountry = false;
			var resultIndex      = 0;
			
			//extract country info
			var country          = getCountryFromResult(results[resultIndex]);
						
			if (country == 'NL') {
				if (searchType) {
					//set the specified zoom for the city if it exists in the places data
					var placeName = searchTerm.replace(/\s\s/g, ' ').replace(/\s/g, '-').toLowerCase();
					if (placeName.indexOf('gemeente-') != 0) {
						placeName = 'gemeente-' + placeName;
					}
					if (typeof placesData[placeName] != 'undefined') {
						zoomLevel = placesData[placeName];
					}
					
				} else {
					//set the specified zoom for the postal code search
					zoomLevel = $mapFormRangeSelect.val();
				}
				
			} else {
				if (!searchType) {
					//set the specified zoom for the postal code search
					zoomLevel = $mapFormRangeSelect.val();
				}
				
				while (country != 'NL' && resultIndex < results.length - 1) {
					resultIndex++;
					country = getCountryFromResult(results[resultIndex]);
				}
				if (country != 'NL') {
					resultIndex = 0;
					isForeignCountry = true;
				}
			}
			
			//store the search data for later use with the "10100 or more" filter
			searchZoom = zoomLevel;
			searchCenter = results[resultIndex].geometry.location;
			
			//reset the error message
			$mapNotice.removeClass('errorMessage').text(messages.searchNotice);
				
			//position/zoom the map based on the search result center point and the zoom level
			processMarkersWithPoint(results[resultIndex].geometry.location, zoomLevel, true);
			
			//if the country is invalid
			if (isForeignCountry) {
				//show an error window
				showErrorWindow({
					'title'  : 'Geen locatie gevonden',
					'message':  'Er zijn geen locaties in het buitenland.'
				});
				
			//if the country is valid
			} else {
				//check the visible markers
				findVisibleMarkers(zoomLevel);
			}
		});
	}
	
	/**
	 * Returns a country from a search result returned by the Google webservice
	 *
	 * @param  object searchResult the search result returned by the Google webservice
	 * @return string country      the country or null if it was not found
	 */
	function getCountryFromResult(searchResult) {
		
		for (var i in searchResult.address_components) {
			//BUG IE7: can't iterate address_components:
			if (typeof searchResult.address_components[i].types != "object") {
				if ( $.browser.msie && $.browser.version == '7.0' ) return "NL"; //return NL to avoid county error 
				return null; //trigger error
			}
			if ($.inArray('country', searchResult.address_components[i].types) > -1) {
				return searchResult.address_components[i].short_name;
			}
		}
		return null;
	}
	
	/**
	 * show/hide markers based on a search result and position the map based on a centerpoint
	 *
	 * @param object  centerPoint      a Google LatLng object for positioning the map and calculating the distance to the markers
	 * @param integer zoomLevel        the zoom level for zooming the map and calculating the distance to the markers
	 * @param boolean forcePositioning if set to true the map will always be positioned and zoomed
	 */
	function processMarkersWithPoint(centerPoint, zoomLevel, forcePositioning) {
		var distance = 0;
		var markerPosition = null;
		for (var i = 0; i < markerData.length; i++) {
			markerPosition = markers[i].getPosition();
			distance = google.maps.geometry.spherical.computeDistanceBetween(centerPoint, markerPosition);
			if (distance <= distances[zoomLevel]) {
				markerData[i].inSearchRadius = true;
				if (markers[i].getMap() == null) {
					markers[i].setMap(map);
				}
			} else if (markerData[i].inSearchRadius) {
				markerData[i].inSearchRadius = false;
				markers[i].setMap(null);
			}
		}
		
		if (searchZoomType || forcePositioning) {
		
			//position the map
			map.setCenter(centerPoint);
			
			//zoom the map
			map.setZoom(zoomLevels[zoomLevel]);

			searchZoomType = false;
		}
	}
	
	/**
	 * show/hide markers based on a search result and position the map based on the marker bounds
	 *
	 * @param object  centerPoint a Google LatLng object for calculating the distance to the markers
	 * @param integer zoomLevel   the zoom level for calculating the distance to the markers
	 */
	function processMarkersWithBounds(centerPoint, zoomLevel) {
		searchZoomType = true;
		var distance = 0;
		var bounds = new google.maps.LatLngBounds();
		var markerPosition = null;
		var hasVisibleMarkersInSearchRadius = false;
		for (var i = 0; i < markerData.length; i++) {
			markerPosition = markers[i].getPosition();
			distance = google.maps.geometry.spherical.computeDistanceBetween(centerPoint, markerPosition);
			if (distance <= distances[zoomLevel]) {
				markerData[i].inSearchRadius = true;
				hasVisibleMarkersInSearchRadius = true;
				if (markers[i].getMap() == null) {
					markers[i].setMap(map);
				}
				bounds.extend(markerPosition);
			} else if (markerData[i].inSearchRadius) {
				markerData[i].inSearchRadius = false;
				markers[i].setMap(null);
			}
		}
		
		//position/zoom the map
		map.fitBounds(bounds);
	}
	
	/**
	 * show/hide markers based on a search result and position the map dynamically
	 */
	function processMarkers() {
		if (!hasSearch) {
			return;
		}
		
		processMarkersWithPoint(searchCenter, searchZoom, false);
	}
	
	/*
	 * Checks for visible markers. If there are no markers the map will be
	 * zoomed out untill markers are found or the maximum zoomlevel is reached
	 *
	 * @param integer zoomLevel the zoomLevel to start searching from
	 */
	function findVisibleMarkers(zoomLevel) {
		var hasVisibleMarkers = checkVisibleMarkers();
		if (zoomLevel < 8) {
			searchZoom = zoomLevel;
			while(!hasVisibleMarkers && searchZoom < 8) {
				searchZoom++;
				processMarkersWithPoint(searchCenter, searchZoom, true);
				hasVisibleMarkers = checkVisibleMarkers();
			}
		}
		if (!hasVisibleMarkers) {
			if (zoomLevel < 8) {
				searchZoom = zoomLevel;
			}
			var errorMessage = 'In de door u opgegeven postcode of plaats is geen locatie gevonden.';
			if (closestCity != null) {
				errorMessage += '<br>De dichtsbijzijnde locatie is in ' + closestCity + '.';
			}
			showErrorWindow({
				'title'  : 'Geen locatie gevonden',
				'message':  errorMessage
			});
		}
	}
});


