var MapModel = Backbone.Model.extend({

	// model attributes
	event: undefined, // on of CONSTANTS.events; to store the last used event. This is because we don't want to refresh the address list when clicking the address links or clicking on the layer markers.
	mapBounds: undefined, // LatLng object: http://code.google.com/apis/maps/documentation/javascript/reference.html#LatLngBounds
	dataTable: undefined, // DataTable object: http://code.google.com/apis/chart/interactive/docs/reference.html#QueryResponse_getDataTable
	selectedObject: {
		dataTable: undefined,
		id: undefined
	},
	addressLocation: undefined,
	addressSelectionState: undefined, // combination of LatLng and filter, used to notify listeners about changes of address

	// other properties
	flavour: undefined, // to create different filters; could be neater
	filterModel: undefined,
	mapView: undefined,
	updateTimerId: undefined,

	// constants
	settings: {
		data: undefined, // set at initialize
		urls: {
			"VISUALISATION": "http://www.google.com/fusiontables/gvizdata"
		},
		iface: {
			"UPDATE_DELAY_MS": 400
		}
	},

	LOCATIONS_WITHIN_BOUNDS_QUERY_TEMPLATE: _.template("SELECT <%= QUERY_COLUMNS %> FROM <%= TABLE_ID %> WHERE ST_INTERSECTS( <%= GEOFIELD %>, RECTANGLE(LATLNG<%= SOUTHWEST %>, LATLNG<%= NORTHEAST %>)) <%= FILTER %>"),
	LAYER_QUERY_TEMPLATE: _.template("SELECT <%= QUERY_COLUMNS %> FROM <%= TABLE_ID %>"),
	FILTER_TEMPLATE: _.template(" WHERE <%= FILTER %>"),
	IDS_FILTER_TEMPLATE: _.template("<%= ID_FIELD %> IN (<%= IDS %>)"),
	FIND_OBJECT_QUERY_TEMPLATE: _.template("SELECT <%= QUERY_COLUMNS %> FROM <%= TABLE_ID %> WHERE <%= ID_FIELD %> = <%= OBJ_ID %> LIMIT 1"),

	QUERY_COLUMNS_FIELDS: undefined, // set in initialize

	QUERY_COLUMNS_NAMES: [], // set below

	initialize: function (options) {

		this.settings.data = options.dataTable;
		this.QUERY_COLUMNS_FIELDS = options.dataTableFields;

		this.filterModel = options.filterModel;
		this.filterModel.bind('change:filter', this.onFiltersChanged, this);
		this.filterModel.bind('change:ids', this.onFiltersChanged, this);
		this.filterModel.bind('change:filter', this.onMapChanged, this);
		this.bind('change:mapBounds', this.onMapChanged, this);

		this.flavour = options.flavour;

		var self = this;
		this.QUERY_COLUMNS_NAMES = _.map(this.QUERY_COLUMNS_FIELDS, function (fieldName) {
			return self.settings.data[fieldName];
		});
	},

	goToAddress: function (address) {

		this.unset('selectedObject', {silent: true});
		var self = this;
		this.geocodeAddress(address, function (latLng) {
			self.set({addressLocation: latLng});
		});
	},

	findLocationsWithinBounds: function () {

		var bounds = this.get('mapBounds');
		if (!bounds) {
			return;
		}

		this.unset('dataTable', {silent: true});
		var filter = '',
			filterQuery = this._makeFilterQuery(this.filterModel.get('filter'), this.filterModel.get('ids'));
		if (filterQuery) {
			filter = ' AND ' + filterQuery;
		}
		// writeDebug("filter=" + filter);
		var query = this.LOCATIONS_WITHIN_BOUNDS_QUERY_TEMPLATE({
			ID_FIELD: this.settings.data.ID_FIELD,
			QUERY_COLUMNS: this.QUERY_COLUMNS_NAMES.join(','),
			TABLE_ID: this.settings.data.FUSION_TABLE_ID,
			FILTER: filter,
			GEOFIELD: this.settings.data.GEO_FIELD,
			SOUTHWEST: bounds.getSouthWest(),
			NORTHEAST: bounds.getNorthEast()
		});

		// writeDebug("query=" + query);
		
		var self = this;
		this._postQuery(query, function (response) {
			if (response.isError()) {
				writeDebug("\t error in response");	
			}
			self.set({dataTable: response.getDataTable()});
		});
	},

	geocodeAddress: function (address, callback) {
		var options = {
			address: address
		};
		var geocoder = new google.maps.Geocoder();
		geocoder.geocode(options, function (results, status) {
			if (status === google.maps.GeocoderStatus.OK) {
				var latLng = results[0].geometry.location;
				callback(latLng);
			}
		});
	},

	goToObjectWithID: function (id) {
		this.unset('selectedObject', {silent: true});

		var query = this.FIND_OBJECT_QUERY_TEMPLATE({
			QUERY_COLUMNS: this.QUERY_COLUMNS_NAMES.join(','),
			TABLE_ID: this.settings.data.FUSION_TABLE_ID,
			ID_FIELD: this.settings.data.ID_FIELD,
			OBJ_ID: id
		});
		var self = this;
		this._postQuery(query, function (response) {
			self.set({selectedObject: {
				dataTable: response.getDataTable(),
				id: id
			}});
		});
	},

	/**
	Called on change:mapBounds
	*/
	onMapChanged: function () {
		// wait a bit before notifying everyone
		if (this.updateTimerId !== undefined) {
			clearTimeout(this.updateTimerId);
		}
		var self = this;

		this.updateTimerId = setTimeout(function () {
			self.set({addressSelectionState: self._calculateAddressSelectionState()});
		}, this.settings.iface.UPDATE_DELAY_MS);
	},

	_calculateAddressSelectionState: function () {
		var bounds = this.get('mapBounds');
		var filters = this._makeFilterQuery(this.filterModel.get('filter'), this.filterModel.get('ids'));
		return bounds + filters;
	},

	/**
	Called on change:filters
	*/
	onFiltersChanged: function () {
		this.unset('event', {silent: true});
		this.unset('selectedObject', {silent: true});
		var filter = '';
		var filterQuery = this._makeFilterQuery(this.filterModel.get('filter'), this.filterModel.get('ids'));
		if (filterQuery) {
			filter = this.FILTER_TEMPLATE({
				FILTER: filterQuery
			});
		}
		var query = this.LAYER_QUERY_TEMPLATE({
			QUERY_COLUMNS: this.QUERY_COLUMNS_NAMES.join(','),
			TABLE_ID: this.settings.data.FUSION_TABLE_ID
		});
		query += filter;
		this.mapView.applyFilter(query);
	},

	/**
	Utility function for TableData rows.
	*/
	getTableRowValue: function (table, row, field) {
		return table.getValue(row, _.indexOf(this.QUERY_COLUMNS_FIELDS, field));					
	},

	/**
	Utility function
	*/
	latLngFromGeoString: function (geoString) {
		if (!geoString) {
			return;
		}
		var latLng;
		var re = new RegExp(/^\(([\d\.]+)[\s\,]*([\d\.]+)\)$/);
		var match = re.exec(geoString);
		if (match) {
			if (!isNaN(match[1]) && !isNaN(match[2])) {
				latLng = new google.maps.LatLng(match[1], match[2]);
			}
		}
		return latLng;
	},

	/**
	Reads a filter object {type, category}. 
	Creates a SQL query string to be used in WHERE clause.
	*/
	_makeFilterQuery: function (filter, ids) {
		if (!filter && !ids) {
			return '';
		}

		var parts = [];
/*
		if (filter.type) {
			if (filter.type === 'rijks') {
				parts.push('GEM_MONUMENT contains \'\'');
			} else if (filter.type === 'gemeentelijk') {
				parts.push('GEM_MONUMENT = 1');
			}
		}
*/

		if (filter && filter.category) {
			var category = parseInt(filter.category, 10);
			if (this.flavour === 'bedrijven') {
				if (category !== 0) {
					parts.push(this.settings.data.MAIN_CATEGORY_FIELD + ' = \'' + category + '\'');
				}
			} else {							
				if (category !== 0) {
					var mainCategoryNumber = parseInt(category / 100, 10);
					// convert to A-Z
					var categoryCode = String.fromCharCode(mainCategoryNumber + 64);				
					var subCategoryNumber = category - mainCategoryNumber*100;
	
					parts.push(this.settings.data.MAIN_CATEGORY_FIELD + ' = \'' + categoryCode + '\'');
					
					if (subCategoryNumber > 0) {
						parts.push(this.settings.data.SUB_CATEGORY_FIELD + ' = ' + subCategoryNumber);
					}
				}
			}
		}
		if (ids && ids.length > 0) {
			var idsQuery = this.IDS_FILTER_TEMPLATE({
				ID_FIELD: this.settings.data.ID_FIELD,
				IDS: ids.join(',')
			});
			parts.push(idsQuery);
		}
		return parts.join(' AND ');
	},

	_postQuery: function (query, callback) {
		var safeQuery = encodeURIComponent(query);
		var visualizationQuery = new google.visualization.Query(this.settings.urls.VISUALISATION + '?tq=' + safeQuery);
		//set the callback function
		var self = this;
		visualizationQuery.send(function (response) {
			if (response.isError()) {
				writeDebug("Response has error:" + response.getMessage());
				writeDebug("Query was:" + query);
			}
			callback(response);
		});
	}
});

var MapView = PanelView.extend({

	gMap: undefined, // Google Map
	gLayers: undefined, // list of google.maps.FusionTablesLayer objects 
	gInfoWindow: undefined, // google.maps.InfoWindow object (balloon)
	gImageSearch: undefined, // google.search.ImageSearch object
	gCurrentLatLngLocation: undefined, // google.maps.LatLng object
	gVisualizationQuery: undefined,
	streetView: undefined,
	streetViewService: undefined,
	templates: undefined, 
	style: undefined, 

	settings: {
		map: {
			"DEFAULT_ZOOM": 9,
			"SEARCHED_ZOOM": 14,
			"ADDRESS_ZOOM": 18,
			"MAX_ZOOM": 20,
			"DEFAULT_MAP_TYPE": google.maps.MapTypeId.TERRAIN,
			"ZOOMED_MAP_TYPE": google.maps.MapTypeId.SATELLITE,
			"DEFAULT_CENTER": new google.maps.LatLng(52.27, 5.57),
			"STREETVIEW_MAX_DISTANCE": 25,
			"STRICT_BOUNDS": new google.maps.LatLngBounds(
				new google.maps.LatLng(50.63, 3.00), 
				new google.maps.LatLng(53.30, 6.75)
			)
		},
		urls: {
			"WIKIPEDIA_IMAGES": "http://commons.wikimedia.org",
			"ADLIB_IMAGE_DATABASE": "http://cultureelerfgoed.adlibsoft.com/harvest/wwwopac.ashx",
			"ADLIB_IMAGES": "http://images.memorix.nl/rce/thumb/"
		},
		detailPageUrlTemplate: undefined
	},

	initialize: function (options) {
		// call super.initialize(options)
		PanelView.prototype.initialize.call(this, this.attributes, options);

		this.templates = options.templates;
		this.style = options.style;
		this.detailPageUrlTemplate = options.detailPageUrlTemplate;
		this.useGoogleImagesAsFallback = options.useGoogleImagesAsFallback;

		this.model.bind('change:selectedObject', this.onSelectedObjectChanged, this);

		this.model.bind('change:addressLocation', this.onLocationChanged, this);

		// if we want to have custom markers:
		// the maximum is 500
		// so this only works if we can aggregate markers
//		this.model.bind('change:dataTable', this.onDataTableChanged, this);

		this._initGoogleMap();
		if (this.useGoogleImagesAsFallback) {
			this._initImageSearch();
		}
		this.render();
	},

	show: function () {
		// call super.show()
		PanelView.prototype.show.call(this);
		this.render();
	},

	hide: function () {
		// call super.hide()
		PanelView.prototype.hide.call(this);
	},

	update: function (screenMode, screenModes) {
		this.render(screenMode, screenModes);
	},

	render: function (screenMode, screenModes) {
		if (screenMode) {
			var styledMapType = new google.maps.StyledMapType(this.style, {
				map: this.gMap,
				name: 'Styled Map'
			});
			this.gMap.mapTypes.set('map-style', styledMapType);
			this.gMap.setOptions({
				panControl: (screenMode === CONSTANTS.screenModes.DESKTOP),
				mapTypeControl: (screenMode > CONSTANTS.screenModes.HANDHELD),
				streetViewControl: (screenMode > CONSTANTS.screenModes.HANDHELD),
				styles: this.style
			});
		}		
		this._resizeMap(this.el.width(), this.el.height());

		return this;
	},

	applyFilter: function (filterQuery) {
		this._closeBalloon();
		this._hideStreetView();
		if (this.gLayers) {
			this.gLayers[0].setQuery(filterQuery);
		}
	},

	/**
	Called when 1 object is selected
	*/
	onSelectedObjectChanged: function () {
		this._hideStreetView();
		var selectedObject = this.model.get('selectedObject');
		if (!selectedObject) {
			return;
		}

		var table = selectedObject.dataTable;
		if (!table || table.getNumberOfRows() === 0) {
			return;
		}

		// create custom event data object
		var row = {};
		row[this.model.settings.data.NAME_FIELD] = {
			value: this.model.getTableRowValue(table, 0, 'NAME_FIELD')
		};
		row[this.model.settings.data.ID_FIELD] = {
			value: this.model.getTableRowValue(table, 0, 'ID_FIELD')
		};
		row[this.model.settings.data.ADDRESS_FIELD] = {
			value: this.model.getTableRowValue(table, 0, 'ADDRESS_FIELD')
		};
		row[this.model.settings.data.GEO_FIELD] = {
			value: this.model.getTableRowValue(table, 0, 'GEO_FIELD')
		};

		var geoString = this.model.getTableRowValue(table, 0, 'GEO_FIELD');

		var latLng = this.model.latLngFromGeoString(geoString);

		// update later if latLng is not known
		var self = this;
		var updateWithInfoData = function (latLng) {
			var infoData = {
				row: row,
				latLng: latLng
			};

			var event = self.model.get('event');
			if (!event || (event !== CONSTANTS.events.LINK&CONSTANTS.events.MARKER)) {
				if (self.gMap.getZoom() === self.settings.map.DEFAULT_ZOOM) {
					self.gMap.setZoom(self.settings.map.ADDRESS_ZOOM);
				}
			}
			self.gMap.setMapTypeId(self.settings.map.ZOOMED_MAP_TYPE);
			self.gMap.panTo(latLng);
			self._showBalloon(infoData);

		}

		if (latLng) {
			updateWithInfoData(latLng);
		} else {	
			this.model.geocodeAddress(geoString, function (latLng) {
				updateWithInfoData(latLng)
			});
		}
	},

	onLocationChanged: function () {
		this._hideStreetView();
		var location = this.model.get('addressLocation');
		if (!location) {
			this.reset();
			return;
		}

		this.gMap.panTo(location);
		this.gMap.setMapTypeId(this.settings.map.ZOOMED_MAP_TYPE);
		var zoom = this.settings.map.SEARCHED_ZOOM;
		if (zoom !== this.gMap.getZoom()) {
			this.gMap.setZoom(zoom);
		}
	},

	onDataTableChanged: function () {
		var table = this.model.get('dataTable');
		if (!table) {
			return;
		}
		var numRows = table.getNumberOfRows();

		for (i = 0; i < numRows; i = i + 1) {
			var string_coordinates = table.getValue(i, 0);
			var split_coordinates = string_coordinates.split(" ");
			var lat = split_coordinates[0];
			var lng = split_coordinates[1];
			var coordinate = new google.maps.LatLng(lat, lng);

			// Get row data for the info window
//			var store = response.getDataTable().getValue(i, 0);
//			var delivery = response.getDataTable().getValue(i, 2);

			this._createMarker(coordinate);
		}
	},

	_createMarker: function (coordinate) {
		// Create the marker
		var marker = new google.maps.Marker({
			map: this.gMap, 
			position: coordinate,
			icon: new google.maps.MarkerImage("images/marker.png")
		});
/*
	  google.maps.event.addListener(marker, 'click', function (event) {
		if (lastWindow) {
		  lastWindow.close();
		} else {
		  lastWindow = new google.maps.InfoWindow();
		}
	
		// Set the position and contents of the info window, and open the window
		lastWindow.setPosition(coordinate);
		lastWindow.setContent('blo');
		lastWindow.open(map);
	  });
*/
	},

	reset: function () {
		this._closeBalloon();
		this._hideStreetView();
		this.gMap.setZoom(this.settings.map.DEFAULT_ZOOM);
		this.gMap.setMapTypeId(this.settings.map.DEFAULT_MAP_TYPE);
		this.gMap.panTo(this._getMapCenter());
	},

	_resizeMap: function (mapWidth, mapHeight) {

		if (!this.gMap) {
			return;
		}
		// get a reference to the element that contains the div
		var mapDiv = $(this.gMap.getDiv());

		// set the new height and width
		mapDiv.height(mapHeight);
		mapDiv.width(mapWidth);

		var center = this.gMap.getCenter(); 
		google.maps.event.trigger(this.gMap, 'resize');

		// fire a resize event which will make the map
		// redraw taking into account the new size
		// and keep map centered at the current center location

		this.gMap.setCenter(center);
	},
	
	_initGoogleMap: function () {

		this.gMap = new google.maps.Map($(this.el)[0], {
			center: this._getMapCenter(),
			zoom: this.settings.map.DEFAULT_ZOOM,
			minZoom: 7,
			backgroundColor: this.el.css('background-color'),
			// set screen size dependent control options at screen resize, default to false:
			panControl: false,
			mapTypeControl: false,
			streetViewControl: false
		});

		var self = this;
//		google.maps.event.addListener(this.gMap, 'tilesloaded', function () {

			// now the map has loaded, add the layer
			if (!self.gLayers) {
				// create first layer
				var ftLayer = new google.maps.FusionTablesLayer({
					suppressInfoWindows: true
				});
				ftLayer.setTableId(self.model.settings.data.FUSION_TABLE_ID);
				if (self.gMap) {
					ftLayer.setMap(self.gMap);
				}

				// events
				google.maps.event.addListener(ftLayer, 'click', function (infoData) {
					self.model.set({event: CONSTANTS.events.MARKER});
					var id = infoData.row[self.model.settings.data.ID_FIELD].value;
					self.model.goToObjectWithID(id);
				});
				self.gLayers = [];
				self.gLayers.push(ftLayer);
			}
//		});

		google.maps.event.addListener(this.gMap, 'bounds_changed', function () {
			self._limitToMaxBounds();
			self.model.set({mapBounds: self.gMap.getBounds()});
		});
		google.maps.event.addListener(this.gMap, 'dragend', function () {
			self._limitToMaxBounds();
			self.model.set({event: CONSTANTS.events.DRAG});
			self.model.set({mapBounds: self.gMap.getBounds()});
		});
		google.maps.event.addListener(this.gMap, 'zoom_changed', function () {
			self._limitToMaxBounds();
			self.model.set({event: CONSTANTS.events.ZOOM});
			self.model.set({mapBounds: self.gMap.getBounds()});
		});
		google.maps.event.addListener(this.gMap, 'click', function () {
			self.model.unset('selectedObject');
			self._closeBalloon();
		});

		this.gInfoWindow = new google.maps.InfoWindow({
			content: '',
			maxWidth: 400,
			position: this.settings.map.DEFAULT_CENTER
		});
		google.maps.event.addListener(this.gInfoWindow, 'content_changed', function () {
			self.onBalloonContentChanged();
		});

		this.streetView = this.gMap.getStreetView();
		this.streetViewService = new google.maps.StreetViewService();
	},

	/*
	Prevent map from moving outside of NL
	*/
	_limitToMaxBounds: function () {
		var c = this.gMap.getCenter(),
				x = c.lng(),
				y = c.lat(),
				maxX = this.settings.map.STRICT_BOUNDS.getNorthEast().lng(),
				maxY = this.settings.map.STRICT_BOUNDS.getNorthEast().lat(),
				minX = this.settings.map.STRICT_BOUNDS.getSouthWest().lng(),
				minY = this.settings.map.STRICT_BOUNDS.getSouthWest().lat(),
				outOfBounds = false;

			if (x < minX) {
				x = minX;
				outOfBounds = true;
			}
			if (x > maxX) {
				x = maxX;
				outOfBounds = true;
			}
			if (y < minY) {
				y = minY;
				outOfBounds = true;
			}
			if (y > maxY) {
				y = maxY;
				outOfBounds = true;
			}
			if (outOfBounds) {
				this.gMap.setCenter(new google.maps.LatLng(y, x));
			}
	},

	/*
	Tries to read the user location.
	Note: should use https for security
	*/
	_getMapCenter: function () {
		var center; // LatLng object
		if (google.loader.ClientLocation) {
			center = new google.maps.LatLng(google.loader.ClientLocation.latitude, google.loader.ClientLocation.longitude);
		} else {
			center = this.settings.map.DEFAULT_CENTER;
		}
		return center;
	},

	_closeBalloon: function () {
		if (this.gInfoWindow) {
			this.gInfoWindow.close(this.gMap);
		}
		
	},

	onBalloonContentChanged: function () {
		//
	},

	/**
	Executes an image search.
	Implemented by subclasses.
	*/
	_loadThumbnails: function (id, latLng, addressQuery, url, callback) {
		//
	},

	/**
	Loads images from Street View.
	Do not use: results in error images after a couple of views due to access limit.
	*/
/*
	_loadGoogleStreetViewThumbnails: function (latLng, addressQuery, url, callback) {
		
		var API_URL = 'http://maps.googleapis.com/maps/api/streetview';
		var SIZE = 80;
		var pSize = 'size=' + SIZE + 'x' + SIZE;
		var pLocation = 'location=' + latLng;
		var pFov = 'fov=90';
		var pPitch = 'pitch=0';
		var pSensor = 'sensor=false';
		
		var $imageRow = $('<div></div>').addClass('images streetview');
		
		var IMG_COUNT = 6, i;
		for (i = 0; i < 360; i = i + (360/IMG_COUNT)) {
			var pHeading = 'heading=' + i;
			var imgUrl = API_URL + '?' + [pSize, pLocation, pFov, pPitch, pHeading, pSensor].join('&');
			$imageRow.append($('<div></div>').addClass('image streetview').append($('<a></a>').attr({
				href: url ? url : '#',
				target: url ? '_blank' : ''
			}).append($('<img>').attr({
				src: imgUrl
			}))));
		}
		$imageRow.append($('<div></div>').addClass('clear'));
		var $contentDiv = jQuery('.infoWindow .imageRow');
		$contentDiv.addClass('hasImages');
		$contentDiv.html($imageRow);
		
		callback();
	},
*/

	/** 
	Checks if street view is available at given coordinate.
	If not, disables the streetview link.
	*/
	_checkStreetView: function (latLng) {
		var self = this;
		this.streetViewService.getPanoramaByLocation(latLng, this.settings.map.STREETVIEW_MAX_DISTANCE, function (streetViewPanoramaData, status) {
			if (status === google.maps.StreetViewStatus.OK) {
				//writeDebug("has streetview");
				$('.infoWindow .streetview', self.el).removeClass('disabled');
			} else {
				//writeDebug("no street view within " + self.settings.map.STREETVIEW_MAX_DISTANCE + " meters");
				$('.infoWindow .streetview', self.el).addClass('disabled');
			}
		});
	},

	_initBalloonInterfaceBehaviour: function () {

		var self = this;
		$('.infoWindow ul.links a.zoomMap').livequery('click', function () {
			var geoString = $(this).attr('data-latlng');
			self._zoomToCoordinate(geoString);
			return false;
		});
		$('.infoWindow ul.links a.streetview').livequery('click', function () {
			var geoString = $(this).attr('data-latlng');
			self._showStreetView(geoString);
			return false;
		});
		$('.infoWindow ul.links a.streetview.disabled').livequery('click', function () {
			return false;
		});
	},

	_zoomToCoordinate: function (geoString) {
		var latLng = this.model.latLngFromGeoString(geoString);
		this.gMap.setMapTypeId(this.settings.map.ZOOMED_MAP_TYPE);
		this.gMap.panTo(latLng);
		this.gMap.setZoom(this.settings.map.MAX_ZOOM);
	},

	_showStreetView: function (geoString) {
		var latLng = this.model.latLngFromGeoString(geoString);
		this.streetView.setPosition(latLng);
		this.streetView.setVisible(true);
	},

	_hideStreetView: function (geoString) {
		if (this.streetView) {
			this.streetView.setVisible(false);
		}
	},

	_showBalloon: function (infoData) {
		// overridden by subclasses
	}
});	

var MapViewMonumenten = MapView.extend({

	initialize: function (options) {
		// call super.initialize(options)
		MapView.prototype.initialize.call(this, options);
	},

	/**
	Executes an image search.
	*/
	_loadThumbnails: function (id, latLng, addressQuery, url, callback) {
		return this._loadAdlibThumbnails(id, latLng, addressQuery, url, callback);
		//return this._loadGoogleImageSearchThumbnails(id, latLng, addressQuery, url, callback);
		//return this._loadGoogleStreetViewThumbnails(latLng, addressQuery, url, callback);
	},

	/**
	Test with id: 21748
	*/
	_loadAdlibThumbnails: function (id, latLng, addressQuery, url, callback) {
		var self = this;
		$().adlibdata(this.settings.urls.ADLIB_IMAGE_DATABASE, {
			database: 'images',
			search: 'pointer 1009 and monument.record_number->mD="' + id + '"',
			xmltype: 'grouped',
			limit: 5
		}, function (adlibJSON) {
			if (!adlibJSON.recordList) {
				return self._loadGoogleImageSearchThumbnails(id, latLng, addressQuery, url, callback);
			} else {
				var records = adlibJSON.recordList.record,
					SIZE = 66,
					SITEURL = self.settings.urls.ADLIB_IMAGES,
					images = [],
					i;
				if (!records || records.length === 0) {
					return self._loadGoogleImageSearchThumbnails(id, latLng, addressQuery, url, callback);
				}
				for (i=0; i<records.length; i=i+1) {
					var reps = records[i].Reproduction;
					if (reps) {
						var image = reps['0']['reproduction.reference'].toString();
						if (!(/\\/.test(image))) {
							image = image.replace(/\.jpg/gi, '');
							image += '.jpg';
							images.push(SITEURL + SIZE + 'x' + SIZE + '/' + image);
						}
					}
				}
				if (images.length > 0) {
					// TODO: make template
					var $imageRow = $('<div></div>').attr('class', 'images');
					var i;
					for (i = 0; i < images.length; i = i + 1) {
						$imageRow.append($('<div></div>').addClass('image').append($('<a></a>').attr({
							href: url ? url : '#',
							target: url ? '_blank' : ''
						}).append($('<img>').attr({
							src: images[i]
						}))));
					}
					$imageRow.append($('<div></div>').addClass('clear'));
					var $contentDiv = jQuery('.infoWindow .imageRow');
					$contentDiv.addClass('hasImages');
					$contentDiv.hide();
					$contentDiv.html($imageRow);
					$contentDiv.fadeIn();
					callback();
				} else {
					return self._loadGoogleImageSearchThumbnails(id, latLng, addressQuery, url, callback);
				}
			}
		});
	},


	_initImageSearch: function () {

		this.gImageSearch = new google.search.ImageSearch();

		this.gImageSearch.setSiteRestriction(this.settings.urls.WIKIPEDIA_IMAGES);

		// Restrict to photos only
		this.gImageSearch.setRestriction(google.search.ImageSearch.RESTRICT_IMAGETYPE, google.search.ImageSearch.IMAGETYPE_PHOTO);

		// Restrict to extra large images only
		this.gImageSearch.setRestriction(google.search.ImageSearch.RESTRICT_IMAGESIZE, google.search.ImageSearch.IMAGESIZE_MEDIUM);

		this.gImageSearch.setRestriction(google.search.ImageSearch.RESTRICT_COLORIZATION, google.search.ImageSearch.COLORIZATION_COLOR);

		this.gImageSearch.setRestriction(google.search.Search.RESTRICT_SAFESEARCH, google.search.Search.SAFESEARCH_STRICT);

		this.gImageSearch.setQueryAddition('Rijksmonument');

		this.gImageSearch.setResultSetSize(5);
	},

	_loadGoogleImageSearchThumbnails: function (id, latLng, addressQuery, url, callback) {
		if (!this.useGoogleImagesAsFallback) {
			callback();
			return;
		}
		var writeImages = function (searcher) {

			// Check that we got results
			var results = searcher.results;
			if (!results || results.length === 0) {
				callback();
				return;
			}
			
			// TODO: make template
			var $imageRow = $('<div></div>').attr('class', 'images');
			var i;
			for (i = 0; i < results.length; i = i + 1) {
				$imageRow.append($('<div></div>').addClass('image').append($('<a></a>').attr({
					href: url ? url : '#',
					target: url ? '_blank' : ''
				}).append($('<img>').attr({
					src: results[i].tbUrl
				}))));
			}
			$imageRow.append($('<div></div>').addClass('clear'));
			var $contentDiv = jQuery('.infoWindow .imageRow');
			$contentDiv.addClass('hasImages');
			$contentDiv.hide();
			$contentDiv.html($imageRow);
			$contentDiv.fadeIn();

			callback();
		};
		this.gImageSearch.setSearchCompleteCallback(this, writeImages, [this.gImageSearch]);
		this.gImageSearch.execute(addressQuery);
	},

	/**
	_showBalloon (infoData)

	Creates a map balloon.
	*/
	_showBalloon: function (infoData) {

		var BALLOON_TEMPLATE = _.template(this.templates.map.balloon), 
			BALLOON_NAME_ADDRESS_TEMPLATE = _.template(this.templates.map.balloonNameAddress), 
			BALLOON_DESCRIPTION_RIJKS_TEMPLATE = _.template(this.templates.map.balloonDescriptionRijks), 
			BALLOON_LINKS = _.template(this.templates.map.balloonLinks), 
			BALLOON_LINK_ZOOM = _.template(this.templates.map.balloonLinkZoom), 
			BALLOON_LINK_STREETVIEW = _.template(this.templates.map.balloonLinkStreetView), 
			BALLOON_LINK_DETAIL = _.template(this.templates.map.balloonLinkDetail), 
			BALLOON_LINK_DETAIL_URL = _.template(this.detailPageUrlTemplate);

		var address = infoData.row[this.model.settings.data.ADDRESS_FIELD].value;

		if (address) {
			// Adds 1 space before numbers.
			address = address.replace(/(\d+)/g, ' $1');
			address = address.replace(/ {2}/g, ' ');
		}
		// some monuments have a specific name
		var name = infoData.row[this.model.settings.data.NAME_FIELD].value;
		if (name) {
			// remove any quotes that are in the database
			name = name.replace(/\"/g, '');
		}
		if (name) {
			address = BALLOON_NAME_ADDRESS_TEMPLATE({
				NAME: name,
				ADDRESS: address
			});
		}

		var description = '';
		var links = '', url;
		var id = infoData.row[this.model.settings.data.ID_FIELD].value;
		if (id !== undefined) {
			if (id === 0) {
				description = this.templates.map.balloonDescriptionGemeentelijk;
			} else {
				description = BALLOON_DESCRIPTION_RIJKS_TEMPLATE({
					ID: id
				});
			}

			// links
			var CLASSNAME_LAST_LINK = 'last';

			// check requirements

			// defaults
			var hasZoom = 1, hasStreetView = 1, hasDetailPage = 0;

			// has street view? => because we are querying a service this has to be checked later
       
			// has detail page?
			if (id !== 0) {
				// rijksmonumenten: yes, gemeentelijke monumenten: no
				hasDetailPage = 1;
			}

			var linkCount = (hasZoom + hasStreetView + hasDetailPage);
			var balloonLinkZoom = '', balloonLinkStreetView = '', balloonLinkDetail = '';

			var linksCounted = 0;
			if (hasZoom) {
				linksCounted += 1;
				balloonLinkZoom = BALLOON_LINK_ZOOM({
					LATLNG: infoData.latLng,
					IS_LAST: (linkCount === linksCounted) ? CLASSNAME_LAST_LINK : ''
				});
			}
			if (hasStreetView) {
				linksCounted += 1;
				balloonLinkStreetView = BALLOON_LINK_STREETVIEW({
					LATLNG: infoData.latLng,
					IS_LAST: (linkCount === linksCounted) ? CLASSNAME_LAST_LINK : ''
				});
			}
			if (hasDetailPage) {
				url = BALLOON_LINK_DETAIL_URL({
					ID: id
				});
				linksCounted += 1;
				balloonLinkDetail = BALLOON_LINK_DETAIL({
					URL: url,
					IS_LAST: (linkCount === linksCounted) ? CLASSNAME_LAST_LINK : ''
				});
			}
			// finally assemble:
			links = BALLOON_LINKS({
				ZOOM_LINK: balloonLinkZoom,
				STREETVIEW_LINK: balloonLinkStreetView,
				DETAIL_LINK: balloonLinkDetail
			});

		}

		var classIsHigh = '';
		if (address.length > 31) {
			classIsHigh = 'high';
		}
		var balloon = BALLOON_TEMPLATE({
			ADDRESS: address,
			DESCRIPTION: description,
			LINKS: links,
			CLASS_IS_HIGH: classIsHigh
		});
		
		this.gInfoWindow.setContent(balloon);
		this.gInfoWindow.setPosition(infoData.latLng);
		this.gInfoWindow.open(this.gMap);
		
		// somehow we need to call this for every balloon
		this._initBalloonInterfaceBehaviour();
		
		// we cannot retrieve the content div at this point
		// because it still needs to get attached to the balloon div
		// that we cannot access directly
		// we try again after loading the images
		
		var self = this;
		this._loadThumbnails(id, infoData.latLng, infoData.row[this.model.settings.data.ADDRESS_FIELD].value, url, function () {
			self._checkStreetView(infoData.latLng);
		});
	}
});


var MapViewBedrijven = MapView.extend({

	initialize: function (options) {
		
		// call super.initialize(options)
		MapView.prototype.initialize.call(this, options);
	},

	_loadThumbnails: function (id, latLng, addressQuery, url, callback) {
		//
	},
	
	/**
	_showBalloon (infoData)
	
	Creates a map balloon.
	*/
	_showBalloon: function (infoData) {
		var BALLOON_TEMPLATE = _.template(this.templates.map.balloon), 
			BALLOON_NAME_ADDRESS_TEMPLATE = _.template(this.templates.map.balloonNameAddress), 
			BALLOON_DESCRIPTION_RIJKS_TEMPLATE = _.template(this.templates.map.balloonDescriptionRijks),
			BALLOON_LINKS = _.template(this.templates.map.balloonLinks), 
			BALLOON_LINK_ZOOM = _.template(this.templates.map.balloonLinkZoom), 
			BALLOON_LINK_STREETVIEW = _.template(this.templates.map.balloonLinkStreetView);

		var address = infoData.row[this.model.settings.data.ADDRESS_FIELD].value;
		if (address) {
			// Adds 1 space before numbers.
			address = address.replace(/(\d+)/g, ' $1');
			address = address.replace(/ {2}/g, ' ');
			address = address.replace(/\\n/g, '<br \/>');
		}
		// some monuments have a specific name
		var name = infoData.row[this.model.settings.data.NAME_FIELD].value;
		if (name) {
			// remove any quotes that are in the database
			name = name.replace(/\"/g, '');
		}

		var description = '';
		var links = '', url;
		var id = infoData.row[this.model.settings.data.ID_FIELD].value;
		if (id !== undefined) {
			description = BALLOON_DESCRIPTION_RIJKS_TEMPLATE({
				ADDRESS: address
			});

			// links
			var CLASSNAME_LAST_LINK = 'last';

			// check requirements

			// defaults
			var hasZoom = 1, hasStreetView = 1, hasDetailPage = 0;

			// has street view? => because we are querying a service this has to be checked later
       
			// has detail page?
			if (id !== 0) {
				// rijksmonumenten: yes, gemeentelijke monumenten: no
				hasDetailPage = 1;
			}

			var linkCount = (hasZoom + hasStreetView);
			var balloonLinkZoom = '', balloonLinkStreetView = '';

			var linksCounted = 0;
			if (hasZoom) {
				linksCounted += 1;
				balloonLinkZoom = BALLOON_LINK_ZOOM({
					LATLNG: infoData.latLng,
					IS_LAST: (linkCount === linksCounted) ? CLASSNAME_LAST_LINK : ''
				});
			}
			if (hasStreetView) {
				linksCounted += 1;
				balloonLinkStreetView = BALLOON_LINK_STREETVIEW({
					LATLNG: infoData.latLng,
					IS_LAST: (linkCount === linksCounted) ? CLASSNAME_LAST_LINK : ''
				});
			}
			// finally assemble:
			links = BALLOON_LINKS({
				ZOOM_LINK: balloonLinkZoom,
				STREETVIEW_LINK: balloonLinkStreetView
			});

		}

		var classIsHigh = '';
		if (address.length > 31) {
			classIsHigh = 'high';
		}
		var balloon = BALLOON_TEMPLATE({
			NAME: name,
			DESCRIPTION: address,
			LINKS: links,
			CLASS_IS_HIGH: classIsHigh
		});

		this.gInfoWindow.setContent(balloon);
		this.gInfoWindow.setPosition(infoData.latLng);
		this.gInfoWindow.open(this.gMap);

		// somehow we need to call this for every balloon
		this._initBalloonInterfaceBehaviour();

		// we cannot retrieve the content div at this point
		// because it still needs to get attached to the balloon div
		// that we cannot access directly
		// we try again after loading the images

		this._checkStreetView(infoData.latLng);
	}
});

