/**************************************************************************************/
/* GoogleMap Class                                                                    */
/* 																					  */
/* - usage: call googleMap.initialize() on load and GUnload() on unload				  */
/*																					  */
/* - marker data structure:															  */
/*																					  */
/*		<markers zoom="...">														  */
/*			<marker lat="..." lng="..." viewInfo="...">								  */
/*				<icon>...</icon>													  */
/*				<tab label="...">													  */
/*					<title>...</title>												  */
/*					<address>...</address>											  */
/*					<phone>...</phone>												  */
/*					<homepage>...</homepage>										  */
/*					<routing>...</routing>											  */
/*				</tab>																  */
/*				...																	  */
/*			</marker>																  */
/*			...																		  */
/*		</markers>																	  */
/*																					  */
/*		("viewInfo" and "routing" can be 0 or 1)									  */
/*																					  */
/* - marker in query string:														  */
/*																					  */
/*		marker=title[...];address[...];icon[...];viewInfo[...];...					  */
/*																					  */
/* - everything except for "address" is optional									  */
/*																					  */
/* - custom map settings in query string:											  */
/*																					  */
/*		map=name[...];minZoom[...];maxZoom[...]	 									  */
/*																					  */
/* - info bubble settings in query string: 											  */
/*																					  */
/*		bubble=maxWidth[...];css[...];cssTitle[...]									  */
/*																					  */
/* - default marker icon path in query string:                                        */
/*																					  */
/*		icon=/path/to/marker.png                                                      */
/**************************************************************************************/

/* add bind method to functions so we can bind an object to a function */
if(typeof(Function.prototype.bind) == 'undefined') Function.prototype.bind = function() {
	var _this = this, args = [], object = arguments[0];
	for(var i = 1; i < arguments.length; i++) args.push(arguments[i]);
	return function() {
		return _this.apply(object, args);
	}
}

var googleMap = {

/* Configuration **********************************************************************/

	zoom: [14, 0],						// default zoom factor, automatic zoom (0 = no, otherwise max. zoom factor)
	latLng: [50.386413, 7.622354],		// default coordinates if no marker is set
	addLocalSearch: false,				// add local search to map (true = yes, false = no)
	bubbleWidth: 250,					// default info bubble width
	icon: [],							// default marker icon (path, width, height)
	language: 'de',						// default language
	i18n: {								// translations
		de: [
			'Route berechnen',
			'Hierher',
			'Von hier',
            'Adresse nicht gefunden'
		],
		it: [
			'Ottieni indicazioni stradali',
			'Da qui',
			'A qui',
            'Address not found'
		],
		en: [
		    'Get directions',
		    'hither',
		    'from here',
            'Address not found'
		],
		nl: [
		    'Routebeschrijving',
		    'hier',
		    'van hier',
            'Adres niet gevonden'
		],
		es: [
		    'C&oacute;mo llegar',
		    'Mi ubicaci&oacute;n',
		    'Destino',
            'Address not found'
		],
		fr: [
		    'Calculer itin&eacute;raire',
		    'Vers',
		    "&Agrave; partir d'ici",
            'Address not found'
		]
	},
	options: null,

/* Functions **************************************************************************/

	/**
	 * "options" is an object with the following properties:
	 *
	 * 	data:               [marker string or path to XML file, alternative path to XML file (if the first path doesn't contain markers)]
	 *	zoom:               [default zoom factor, auto zoom max. factor; if not set or 0, auto zoom is disabled]
	 *	latLng:             [default latitude, default longitude]
	 *	search:             view search (0 or 1)
	 *	mapWidth:           width in pixels or percent
	 *	dataDir:            path to data directory
	 *	customMap:          path to custom map
	 *	bubbleOpts:         string with bubble options
	 *	language:           string
	 *	icon:               [path, width, height]
     *	hideMapControl:     0 or 1
     *	hideTypeControl:    0 or 1
     *	hideErrorBox:       0 or 1
	 *
	 * All properties except for "data" are optional.
	 */
	initialize: function(options) {
		if(GBrowserIsCompatible()) {
			this.options = options;
			this.dataDir = options.dataDir;
			this.markers = [];
			this.xmlDoc = null;
			this.cnt = 0;
			this.bubbleOpts = this.readOpts(options.bubbleOpts);

			if(options.language) this.language = options.language.toLowerCase();
			if(options.customMap != '') this.getCustomMap(options.customMap);
			if(options.zoom) this.zoom = (typeof options.zoom == 'object') ? options.zoom : [options.zoom];
			if(options.icon) this.icon = (typeof options.icon == 'object') ? options.icon : [options.icon];

			if(typeof options.latLng == 'object') {
				if(options.latLng[0]) this.latLng[0] = options.latLng[0];
				if(options.latLng[1]) this.latLng[1] = options.latLng[1];
			}

			if(options.mapWidth && !isNaN(options.mapWidth)) {
				if(this.bubbleWidth > options.mapWidth - 100) {
					this.bubbleWidth = options.mapWidth - 100;
					if(this.bubbleWidth < 100) this.bubbleWidth = 100;
				}
			}

			if(options.search != '') {
				this.addLocalSearch = parseInt(options.search) ? true : false;
			}

			if(options.data[0] != '') {
				if(options.data[0].indexOf('[') != -1) this.getMarker(options.data[0]);
				else this.loadXml(this.dataDir + options.data[0]);
			}
			else this.createMap();
		}
	},

	trim: function(v) {
		if(v) {
			v = v.replace(/^\s+/, '');
			v = v.replace(/\s+$/, '');
		}
		return v;
	},

	getCookie: function(name) {
		if(document.cookie) {
			var idx = document.cookie.lastIndexOf(name + '=');
			if(idx == -1) return null;
			var value = document.cookie.substring(idx + name.length + 1);
			var end = value.indexOf(';');
			if(end == -1) end = value.length;
			value = value.substring(0, end);
			value = unescape(value);
			return value;
		}
		return undefined;
	},

	setCookie: function(name, value, days, path, domain, secure) {
		if(this.cookieSupport()) {
			var expires = -1;

			if(typeof(days) == 'number' && days >= 0){
				var d = new Date();
				d.setTime(d.getTime() + days * 24 * 60 * 60 * 1000);
				expires = d.toGMTString();
			}
			value = escape(value);
			document.cookie = name + '=' + value + ';' +
				              ((expires != -1) ? ' expires=' + expires + ';' : '') +
				              (path ? 'path=' + path : '') +
				              (domain ? '; domain=' + domain : '') +
				              (secure ? '; secure' : '');
		}
	},

	deleteCookie: function(name) {
		this.setCookie(name, '-', 0, '/');
	},

	cookieSupport: function() {
		if(typeof(navigator.cookieEnabled) != 'boolean'){
			this.setCookie('__TestingYourBrowserForCookieSupport__', 'CookiesAllowed');
			var cookieVal = this.getCookie('__TestingYourBrowserForCookieSupport__');
			navigator.cookieEnabled = (cookieVal == 'CookiesAllowed');

			if(navigator.cookieEnabled){
				this.deleteCookie('__TestingYourBrowserForCookieSupport__');
			}
		}
		return navigator.cookieEnabled;
	},

	setIcon: function(name) {
		var icon = new GIcon(G_DEFAULT_ICON);
		if(name) {
			icon.image = this.dataDir + name;
		}
		else if(this.icon) {
			if(this.icon[0]) {
				icon.image = this.dataDir + this.icon[0];
			}

			if(this.icon[1] && this.icon[2]) {
				var width = this.icon[1];
				var height = this.icon[2];
				icon.iconSize = new GSize(width, height);
				icon.imageMap = [0, 0, width, 0, width, height, 0, height];
				icon.shadowSize = new GSize(Math.round(width * 1.5), height);
				icon.iconAnchor = new GPoint(Math.round(width / 2), height);
			}
		}
		return {icon: icon};
	},

	ajaxReq: function(file, fn) {
		var request = GXmlHttp.create();
		var async = (typeof(fn) == 'function') ? true : false;
		request.open('GET', file, async);

		if(async) {
			request.onreadystatechange = function() {
				if(request.readyState == 4) {
					if(request.status != 200) {
						alert('Server returned ' + request.status);
					}
					else {
                        var xml = request.responseXML;

                        if(!xml || !xml.childNodes || xml.childNodes.length == 0) {
                            if(request.responseText) {
                                xml = document.createElement('div');
                                xml.innerHTML = request.responseText.replace(/^<\?xml.+?\?>/i, '');
                            }
                            else xml = null;
                        }
                        if(xml) fn(xml);
                    }
				}
			}
		}
		request.send(null);
		return async ? true : request;
	},

	loadXml: function(xmlFile) {
		this.ajaxReq(xmlFile, function(responseXML) {
			googleMap.xmlDoc = responseXML;
			googleMap.getMarkers();
		});
	},

	checkLatLng: function() {
		var geocoder = new GClientGeocoder();
		var coords, lat, lng;

		/* check for each marker if coords are set */
		for(var i = this.cnt; i < this.markers.length; i++) {
			if(!this.markers[i].lat || !this.markers[i].lng) {
				coords = this.getCookie('coords[' + escape(this.markers[i].address) + ']');

				if(coords != undefined && coords != '' && coords.indexOf('x') != -1) {
					lat = coords.substring(0, coords.indexOf('x'));
					lng = coords.substring(coords.indexOf('x') + 1);
				}
				else lat = lng = '';

				if(lat == '' || lng == '') {
					/* get coords and stop check because we must wait for server response */
					this.cnt = i;
					geocoder.getLatLng(this.markers[i].address, function(point) {
						/* response received, set coords... */
						if(point) {
							googleMap.markers[googleMap.cnt].lat = point.lat();
							googleMap.markers[googleMap.cnt].lng = point.lng();

							if(!document.cookie || document.cookie.length < 6000) {
								/* write coords to cookie */
								googleMap.setCookie('coords[' + escape(googleMap.markers[googleMap.cnt].address) + ']', point.lat() + 'x' + point.lng(), 30);
							}
						}
						/* ...and continue check with next marker */
						googleMap.cnt++;
						googleMap.checkLatLng();
					});
					break;
				}
				else {
					this.markers[i].lat = lat;
					this.markers[i].lng = lng;
				}
			}
		}
		/* create map after all coords have been retrieved */
		if(i == this.markers.length) this.createMap();
	},

	readOpts: function(data) {
		var i, v, key, val, args;
		var obj = {};

		if(data) {
			data = data.replace(/\];?$/, '');
			args = data.split('];');

			for(i = 0; i < args.length; i++) {
				v = args[i].split('[');
				key = this.trim(v[0]);
				val = this.trim(v[1]);
				obj[key] = val;
			}
		}
		return obj;
	},

	getMarker: function(data) {
		var obj = this.readOpts(data);

		this.markers[0] = {};
		this.markers[0].tabs = [];
		this.markers[0].tabs[0] = {};

		/* create single marker from query data */
		for(var key in obj) switch(key) {

			case 'lat':
			case 'lng':
			case 'viewInfo':
			case 'icon':
				this.markers[0][key] = obj[key];
				break;

			default:
				this.markers[0].tabs[0][key] = obj[key];
				if(key == 'address') this.markers[0].address = obj[key];
		}
		this.checkLatLng();
	},

	getMarkers: function() {
		var m, t, i, j, k, nodes, label, key, val, zoom;

		/* look for zoom factor */
		nodes = this.xmlDoc.childNodes;
		for(i = 0; i < nodes.length; i++) {
			if(nodes[i].nodeType == 1) {
				zoom = nodes[i].getAttribute('zoom');
				if(zoom) this.zoom[0] = parseInt(zoom);
				zoom = nodes[i].getAttribute('autoZoom');
				if(zoom) this.zoom[1] = parseInt(zoom);
				break;
			}
		}

		/* look for marker elements in XML document */
		m = this.xmlDoc.getElementsByTagName('marker');

		/* if XML document contains no markers, try again with alternative document */
		if(m.length == 0 && this.options.data[1] != '') {
			this.loadXml(this.options.data[1]);
			this.options.data[1] = '';
			return false;
		}

		/* get marker content from XML */
		for(i = 0; i < m.length; i++) {
			this.markers[i] = {};
			this.markers[i].lat = m[i].getAttribute('lat');
			this.markers[i].lng = m[i].getAttribute('lng');
			this.markers[i].viewInfo = m[i].getAttribute('viewInfo');
			this.markers[i].icon = GXml.value(m[i].getElementsByTagName('icon')[0]);
			this.markers[i].tabs = {};

			t = m[i].getElementsByTagName('tab');

			for(j = 0; j < t.length; j++) {
				nodes = t[j].childNodes;
				label = t[j].getAttribute('label');
				if(!label) label = j;
				this.markers[i].tabs[label] = {};

				for(k = 0; k < nodes.length; k++) {
					if(nodes[k].nodeType == 1) {
						if(nodes[k].firstChild) {
							key = this.trim(nodes[k].tagName.toLowerCase());
							val = this.trim(nodes[k].firstChild.nodeValue);

							if(val) {
								if(val.match(/\{\{[^\}]+\}\}/)) {
									val = val.replace(/\{\{/g, '<');
									val = val.replace(/\}\}/g, '>');
									val = val.replace(/(src=[\"\'])([^\"\']+[\"\'])/i, '$1' + this.dataDir + '$2');
								}
								this.markers[i].tabs[label][key] = val;
								if(key == 'address') this.markers[i].address = val;
							}
						}
					}
				}
			}
		}
		this.checkLatLng();
		return true;
	},

	showBubble: function(i) {
		var maxWidth = this.bubbleOpts.maxWidth ? this.bubbleOpts.maxWidth : this.bubbleWidth;
		this.markers[i].marker.openInfoWindowTabsHtml(
			this.markers[i].marker.tabs,
			{maxWidth: maxWidth}
		);
	},

	getTileUrl: function(a, b) {
		var dataDir = googleMap.dataDir.replace(/\.\.\//, '');
		var url = location.protocol + '//' + location.host + '/' + dataDir + googleMap.customMap.name;
		var file = 'tile_' + a.x + '_' + a.y + '_' + b + '.jpg';
		var response = googleMap.ajaxReq(url + '/' + file);
		return (response.status == 200) ? url + '/' + file : url + '/blank.jpg';
	},

	getCustomMap: function(data) {
		this.customMap = this.readOpts(data);

		var copyCollection = new GCopyrightCollection('Map data');
		var copyright = new GCopyright(1, new GLatLngBounds(new GLatLng(-90, -180), new GLatLng(90, 180)), 0, '&copy;2008 modix.de');
		copyCollection.addCopyright(copyright);

		var tileLayers = [new GTileLayer(copyCollection, this.customMap.minZoom, this.customMap.maxZoom)];
		tileLayers[0].getTileUrl = this.getTileUrl;
		this.customMap.gMapType = new GMapType(tileLayers, new GMercatorProjection(this.customMap.maxZoom + 1), 'MyMap', {errorMessage: 'No map data available'});
	},

    viewErrorBox: function(errors) {
        var div, btn;
		var i18n = this.i18n[this.language] ? this.i18n[this.language] : this.i18n.de;

        div = document.createElement('div');
        div.innerHTML = '<b>' + i18n[3] + ':</b><br>- ' + errors.join('<br>');
        div.style.border = '1px solid #d00000';
        div.style.padding = '4px 6px';
        div.style.backgroundColor = '#fff8f8';
        div.style.position = 'absolute';
        div.style.overflow = 'hidden';
        div.style.zIndex = 69;
        div.style.whiteSpace = 'nowrap';
        div.style.top = '50%';
        div.style.left = '50%';
        div.style.width = '300px';
        div.style.height = ((errors.length + 1) * 20 + 10) + 'px';
        div.style.lineHeight = '20px';
        div.style.marginLeft = '-150px';
        div.style.marginTop = -((errors.length + 1) * 10 + 5) + 'px';
        div.style.boxShadow = '4px 4px 4px rgba(0, 0, 0, 0.3)';

        btn = document.createElement('div');
        btn.innerHTML = '<b>&times;</b>';
        btn.style.color = '#d00000';
        btn.style.position = 'absolute';
        btn.style.top = '0px';
        btn.style.right = '4px';
        btn.style.paddingLeft = '10px';
        btn.style.cursor = 'pointer';
        btn.onclick = function() { document.body.removeChild(this.parentNode); }

        div.appendChild(btn);
        document.body.appendChild(div);
    },

	createMap: function() {
		var zoom, point, info, i, j, content, key, tab, routing, link, url, addr, cssTitle;
		var cmap = null;
		var i18n = this.i18n[this.language] ? this.i18n[this.language] : this.i18n.de;
		var latLngBounds = new GLatLngBounds();
        var errors = [];

		/* create map and add controls */
		var map = new GMap2(document.getElementById('map_canvas'));
		if(!this.options.hideMapControl) map.addControl(new GSmallMapControl());
		if(!this.options.hideTypeControl && !this.customMap) map.addControl(new GMapTypeControl());
		// if(this.addLocalSearch) map.addControl(new google.maps.LocalSearch());

		/* add custom map */
		if(this.customMap && this.customMap.gMapType) {
			cmap = this.customMap.gMapType;
			map.addMapType(cmap);
			if(this.zoom[0] < this.customMap.minZoom) this.zoom[0] = this.customMap.minZoom;
			else if(this.zoom[0] > this.customMap.maxZoom) this.zoom[0] = this.customMap.maxZoom;
		}

		/* set map center */
		for(i = info = 0; i < this.markers.length; i++) {
			if(this.markers[i].lat != undefined && this.markers[i].lng != undefined) {
				latLngBounds.extend(new GLatLng(this.markers[i].lat, this.markers[i].lng));
				if(this.markers[i].viewInfo > 0) info++;
			}
            else errors.push(this.markers[i].address);
		}

        /* view error box */
        if(!this.options.hideErrorBox && errors.length > 0) {
            this.viewErrorBox(errors);
        }

		if(!latLngBounds.isEmpty()) {
			if(this.zoom[1]) {
				zoom = map.getBoundsZoomLevel(latLngBounds);
				if(zoom > this.zoom[1]) zoom = this.zoom[1];
            }
            else zoom = this.zoom[0];
			map.setCenter(latLngBounds.getCenter(), zoom, cmap);
		}
		else map.setCenter(new GLatLng(this.latLng[0], this.latLng[1]), this.zoom[0], cmap);

		/* add all markers to map */
		for(i = 0; i < this.markers.length; i++) {
			if(this.markers[i].lat && this.markers[i].lng) {
				point = new GLatLng(this.markers[i].lat, this.markers[i].lng);
				this.markers[i].marker = new GMarker(point, this.setIcon(this.markers[i].icon));
				this.markers[i].marker.tabs = [];

				/* build info bubble content */
				if(typeof this.markers[i].viewInfo == 'undefined' || this.markers[i].viewInfo >= 0) {
                    for(label in this.markers[i].tabs) {
                        tab = this.markers[i].tabs[label];
                        content = routing = '';

                        for(key in tab) {
                            if(tab[key]) switch(key.toLowerCase()) {

                                case 'title':
                                    if(typeof tab[key] == 'string') {
                                        url = 'http://www.google.com/local?source=uds' +
                                              '&q=' + escape(tab[key]) +
                                              '&sll=' + this.markers[i].lat + ',' + this.markers[i].lng +
                                              '&latlng=' + (this.markers[i].lat * 1000000) + ',' + (this.markers[i].lng * 1000000) +
                                              '&near=' + this.markers[i].lat + ',' + this.markers[i].lng;

                                        cssTitle = this.bubbleOpts.cssTitle ? this.bubbleOpts.cssTitle : '';
                                        link = '<a href="' + url + '" target="_blank" style="' + cssTitle + '">' + tab[key] + '</a>';
                                        content += '<h1>' + link + '</h1>';
                                    }
                                    break;

                                case 'address':
                                    if(typeof tab[key] == 'string') {
                                        addr = tab[key].replace(/,\s*/g, '<br/>');
                                        content += addr + '<br/>';
                                    }
                                    break;

                                case 'homepage':
                                    if(typeof tab[key] == 'string') {
                                        if(tab[key].match(/^http/)) url = tab[key];
                                        else url = 'http://' + tab[key];
                                        link = '<a href="' + url + '" target="_blank">' + tab[key] + '</a>';
                                        content += link + '<br/>';
                                    }
                                    break;

                                case 'routing':
                                    if(tab[key] > 0) {
                                        url = 'http://www.google.com/maps?li=d&hl=' + this.language + '&f=d&iwstate1=dir:to' +
                                              '&daddr=' + escape(this.markers[i].address) + '&iwloc=1';

                                        link = '<a href="' + url + '" target="_blank">' + i18n[1] + '</a>';
                                        routing = '<p>' + i18n[0] + ': ' + link + ' - ';

                                        url = 'http://www.google.com/maps?li=d&hl=' + this.language + '&f=d&iwstate1=dir:from' +
                                              '&saddr=' + escape(this.markers[i].address) + '&iwloc=1';

                                        link = '<a href="' + url + '" target="_blank">' + i18n[2] + '</a>';
                                        routing += link + '</p>';
                                    }
                                    break;

                                default:content += tab[key] + '<br/>';
                            }
                        }
                        if(routing) content += routing;

                        if(this.bubbleOpts.css) {
                            content = '<div style="' + this.bubbleOpts.css + '">' + content + '</div>';
                        }
                        this.markers[i].marker.tabs.push(new GInfoWindowTab(label, content));
                    }

                    /* add open info bubble event to marker (Google bug: does not work with IE 6!) */
                    GEvent.addListener(this.markers[i].marker, 'click', this.showBubble.bind(this, i));
				}

				/* add marker to map */
				map.addOverlay(this.markers[i].marker);

				/* if desired, open info bubble now */
				if(this.markers[i].viewInfo > 0) {
					setTimeout(this.showBubble.bind(this, i), 500);
				}
			}
		}
	}
}

