google.maps.Marker.prototype.distanceFrom = function(marker) {
	var slf = this.getPosition(), trg = marker.getPosition(), R = 6371000; // meters
	var lat1 = slf.lat(), lon1 = slf.lng(), lat2 = trg.lat(), lon2 = trg.lng();
	var dLat = (lat2-lat1) * Math.PI / 180, dLon = (lon2-lon1) * Math.PI / 180;
	var a = Math.sin(dLat / 2) * Math.sin(dLat / 2) + Math.cos(lat1 * Math.PI / 180 ) * Math.cos(lat2 * Math.PI / 180 ) * Math.sin(dLon / 2) * Math.sin(dLon / 2);
	var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
	return Math.round(c * R);
}

MercatorProjection = function(){
	var _RANGE = 256,
	pixelOrigin_ = new google.maps.Point(_RANGE / 2, _RANGE / 2),
	pixelsPerLonDegree_ = _RANGE / 360,
	pixelsPerLonRadian_ = _RANGE / (2 * Math.PI),

	bound = function(value, opt_min, opt_max){
		if (opt_min != null) value = Math.max(value, opt_min);
		if (opt_max != null) value = Math.min(value, opt_max);
		return value;
	},
	degreesToRadians = function(deg){ return deg * (Math.PI / 180); },
	radiansToDegrees = function(rad){ return rad / (Math.PI / 180); };
	return {
		fromLatLngToPoint: function(latLng){
			var point = new google.maps.Point(0, 0), lng = latLng.lng();
			while(lng > 360) lng -= 360; while(lng < 0) lng += 360;
			point.x = pixelOrigin_.x + lng * pixelsPerLonDegree_;
			var siny = bound(Math.sin(degreesToRadians(latLng.lat())), -0.9999, 0.9999);
			point.y = pixelOrigin_.y + 0.5 * Math.log((1 + siny) / (1 - siny)) * -pixelsPerLonRadian_;
			return point;
		},
		fromPointToLatLng: function(point){
			var lng = (point.x - pixelOrigin_.x) / pixelsPerLonDegree_;
			var latRadians = (point.y - pixelOrigin_.y) / -pixelsPerLonRadian_;
			var lat = radiansToDegrees(2 * Math.atan(Math.exp(latRadians)) - Math.PI / 2);
			return new google.maps.LatLng(lat, lng);
		},
		fromPixelToLatLng: function(x, y, zoom){ return this.fromPointToLatLng({x: x/Math.pow(2, zoom), y: y/Math.pow(2, zoom)}); },
		fromLatLngToPixel: function(latLng, zoom){ var p = this.fromLatLngToPoint(latLng); p.x = Math.round(p.x*Math.pow(2, zoom)); p.y = Math.round(p.y*Math.pow(2, zoom)); return p; }
	};
}();

var gmaps = {
	setpos: false, brsupport: false, tile_size: 256, zoom_def: 20, zoom_max: 15, zoom_cur: 15, mid: 0,
	last_tl: {}, last_zm: 0,
	xhr: null,
	xhrt: null,
	geocoder: null,
	dirservice: null,
	dirdisplay: null,
	infowin: null,
	map: null,
	mytarget: null,
	mymarker: null,
	myselect: null,
	tiles:{},
	markers:{},

	option: function(option){
		this.mapopt = {
			load:		option.load? option.load : true,
			data_self:	option.data_self? option.data_self : {},
			data_targ:	option.data_targ? option.data_targ : {},
			sex_targ:	option.sex_targ? option.sex_targ : 0,
			self_x:		option.self_x? option.self_x : 0,
			self_y:		option.self_y? option.self_y : 0,
			targ_x:		option.targ_x? option.targ_x : 0,
			targ_y:		option.targ_y? option.targ_y : 0,
			icon_my:	option.icon_my? option.icon_my : {ic:'', sh:''},
			icon_m:		option.icon_m? option.icon_m : {ic:'', sh:''},
			icon_w:		option.icon_w? option.icon_w : {ic:'', sh:''},
			icon_gr:	option.icon_gr? option.icon_gr : {ic:'', sh:''},
			map_main:	option.map_main? document.getElementById(option.map_main) : document.getElementById('map_main'),
			map_canvas:	option.map_canvas? document.getElementById(option.map_canvas) : document.getElementById('map_canvas'),
			map_direct:	option.map_direct? document.getElementById(option.map_direct) : document.getElementById('map_direct'),
			map_error:	option.map_error? document.getElementById(option.map_error) : document.getElementById('map_error'),
			map_addr:	option.map_addr? document.getElementById(option.map_addr) : document.getElementById('map_addr'),
			btn_class:		option.btn_class? option.btn_class : 'gbtn',
			btn_class_h:	option.btn_class? option.btn_class : 'gbtn gbtn_sel',
		}
		this.mapmsg = {
			geocode:	option.geocode? option.geocode : "Geocode was not successful.",
			failed:		option.failed? option.failed : "Geolocation service failed.",
			browser:	option.browser? option.browser : "Your browser doesn't support geolocation.",
			direction:	option.direction? option.direction : "No way found",
			save:		option.save? option.save : "Save Position.",
			saveerror:	option.saveerror? option.saveerror : "Save Position - Error.",
			load:		option.load? option.load : "Load Position.",
			loaderror:	option.loaderror? option.loaderror : "Load Position - Error.",
			btn_geo:	option.btn_geo? option.btn_geo : "Determine my location.",
			btn_loc:	option.btn_loc? option.btn_loc : "Set up my location.",
			btn_show:	option.btn_show? option.btn_show : "Show on map.",
			btn_setloc:	option.btn_setloc? option.btn_setloc : "Click on the map to change location.",
			btn_confrm:	option.btn_confrm? option.btn_confrm : "Change position?",
			btn_addr:	option.btn_addr? option.btn_addr : "Enter address",
			btn_setdel:	option.btn_setdel? option.btn_setdel : "Are you sure you want to delete the marker?",
			btn_del:	option.btn_del? option.btn_del : "Remove yourself from the map.",
			m:			option.m? option.m : " m",
			km:			option.km? option.km : " km",
		}
		this.maptmpl = {
			tmpl_info:	option.tmpl_info? option.tmpl_info : "",
			tmpl_group:	option.tmpl_group? option.tmpl_group : ""
		}
		this.mapopt.map_addr.value = this.mapmsg.btn_addr;
		this.mapopt.icon_my.tp = 1;
		this.mapopt.icon_gr.tp = 2;
		this.mapopt.icon_m.tp = 3;
		this.mapopt.icon_w.tp = 4;
	},

	open: function(){
		if(window.ifGmapSeparatePage !== true){
			this.mapopt.map_main.style.display = 'block';
			this.mapopt.map_main.style.top = 70 + ((document.documentElement && document.documentElement.scrollTop) || (document.body && document.body.scrollTop)) + 'px';
			this.mapopt.map_main.style.left = 70 + ((document.documentElement && document.documentElement.scrollLeft) || (document.body && document.body.scrollLeft)) + 'px';
			this.mapopt.map_main.style.width = -140 + ((document.documentElement && document.documentElement.clientWidth) || (document.body && document.body.clientWidth)) + 'px';
			this.mapopt.map_main.style.height = -140 + ((document.documentElement && document.documentElement.clientHeight) || (document.body && document.body.clientHeight)) + 'px';
			this.mapopt.map_canvas.style.width = parseInt(this.mapopt.map_main.style.width) - 20 + 'px';
			this.mapopt.map_canvas.style.height = parseInt(this.mapopt.map_main.style.height) - 45 + 'px';
		}

		if(!this.map){
			this.map = new google.maps.Map(document.getElementById(this.mapopt.map_canvas.id), {
				zoom: gmaps.zoom_cur,
				navigationControl: true,
				mapTypeControl: false,
				scaleControl: false,
				mapTypeId: google.maps.MapTypeId.ROADMAP
			});

			this.xhr = window.ActiveXObject? new ActiveXObject("Microsoft.XMLHTTP") : new XMLHttpRequest(),
			this.xhrt = window.ActiveXObject? new ActiveXObject("Microsoft.XMLHTTP") : new XMLHttpRequest(),
			this.geocoder = new google.maps.Geocoder(),
			//this.dirservice = new google.maps.DirectionsService(),
			//this.dirdisplay = new google.maps.DirectionsRenderer(),
			this.infowin = new google.maps.InfoWindow(),
			/*
			this.dirdisplay.setMap(this.map);
			if(this.mapopt.map_direct) this.dirdisplay.setPanel(this.mapopt.map_direct.id);
			*/
			google.maps.event.addListener(this.map, 'click',
				function(event){
					if(gmaps.setpos){
						if(confirm(gmaps.mapmsg.btn_confrm)) gmaps.setmypos(event.latLng).decoder();
						gmaps.setpos = false;
					}else gmaps.infowin.close();
					gmaps.alert('');
				}
			);

			if(this.mapopt.load){
				google.maps.event.addListener(this.map, 'dragend', function(){ gmaps.gettile(); });
				google.maps.event.addListener(this.map, 'zoom_changed', function(){ gmaps.gettile(); });
			}

			if(this.mapopt.self_x && this.mapopt.self_y){
				this.mymarker = this.addmarker(MercatorProjection.fromPixelToLatLng(this.mapopt.self_x, this.mapopt.self_y, this.zoom_def), 1, this.mapopt.data_self).show(true);
			}
			if(this.mapopt.targ_x && this.mapopt.targ_y){
				this.mytarget = this.addmarker(MercatorProjection.fromPixelToLatLng(this.mapopt.targ_x, this.mapopt.targ_y, this.zoom_def), 1, this.mapopt.data_targ).show(true);
			}
			var m = this.mytarget || this.mymarker;
			if(m) m.center().info();
			else{
				this.map.setCenter(new google.maps.LatLng(55.755786, 37.617633));
				this.address(this.mapopt.data_self.country + " " + this.mapopt.data_self.city);
				this.geo();
			}
		}
		return false;
	},

	close: function(){
		this.mapopt.map_main.style.display = 'none';
		return false;
	},

	btn: function(type, msg, el){
		if(!gmaps.setpos){
			if(type == 'geo' && confirm(this.alert(msg))) gmaps.geo();
			if(type == 'me') if(this.mymarker) this.mymarker.center(true).info(); else this.geo();
			if(type == 'targ') if(this.mytarget) this.mytarget.center(true).info();
			if(type == 'adr') if(this.mapopt.map_addr.value == this.mapmsg.btn_addr) this.mapopt.map_addr.value = '';
			if(type == 'del' && confirm(this.alert(msg))) gmaps.sendpos();
			if(type == 'hlp'){
				if(el) el.style.className = msg? this.mapopt.btn_class : this.mapopt.btn_class_h;
				this.alert(msg);
			}
		}
		if(type == 'loc'){
			this.alert(gmaps.setpos? '' : msg);
			gmaps.setpos = !gmaps.setpos;
			if(el) el.style.className = gmaps.setpos? this.mapopt.btn_class : this.mapopt.btn_class_h;
		}
		return false;
	},

	addmarker: function(location, count, data){
		var icon = this.mapopt.icon_gr;
		if(count > 1) icon = this.mapopt.icon_gr
		else if(data.self) icon = this.mapopt.icon_my
		else if(data.sex == 1) icon = this.mapopt.icon_m
		else icon = this.mapopt.icon_w;

		var res = {
			mid: this.mid++,
			data: data? [data] : null,
			count: count,
			dec: '',
			msg: '',
			isshow: false,
			type: icon.tp,
			first: 0,

			marker: new google.maps.Marker({
				position: location,
				draggable: (data && data.self)? true : false,
				icon: icon.ic,
				shadow: icon.sh
			}),

			distance: function(v){ return this.marker.distanceFrom(v); },
			decoder: function(){ gmaps.decoder(this); return this; },
			append: function(v){
				this.data.push(v);
				this.count = this.data.length;
				if(this.count > 1) {
					this.marker.setIcon(gmaps.mapopt.icon_gr.ic);
					this.marker.setShadow(gmaps.mapopt.icon_gr.sh);
				}
				return this;
			},
			center: function(pan){
				if(pan) gmaps.map.panTo(this.marker.getPosition());
				else gmaps.map.setCenter(this.marker.getPosition());
				gmaps.gettile();
				return this;
			},
			zoom: function(num, inc){
				this.center();
				if(num) gmaps.map.setZoom(num); else gmaps.map.setZoom(gmaps.map.getZoom() + inc);
				return this;
			},
			show: function(show){
				if(this.isshow != show){
					this.marker.setMap(show? gmaps.map : null);
					if(!show && gmaps.infowin.mid == this.mid) gmaps.infowin.close();
					this.isshow = show;
				}
				return this;
			},
			shift: function(v){
				if(v) this.first = ((this.first + 3) > this.data.length)? this.data.length - 3 : this.first + 3;
				else this.first = ((this.first - 3) < 0)? 0 : this.first - 3;
				this.info(true, true);
			},
			info: function(ifopen, refr){
				if(!ifopen){
					gmaps.infowin.open(gmaps.map, this.marker);
					gmaps.infowin.mid = this.mid;
				}
				if(gmaps.infowin.mid == this.mid){
					if(!this.msg || refr){
						var msg = '';
						if(this.data) for(var i = this.first; i < this.data.length && i < this.first + 3; i++){
							var m = gmaps.maptmpl.tmpl_info;
							m = m.replace(/%PHOTO%/g, this.data[i].photo).replace(/%LOGIN%/g, this.data[i].login).replace(/%NAME%/g, this.data[i].name);
							m = m.replace(/%COUNTRY%/g, this.data[i].country).replace(/%CITY%/g, this.data[i].city).replace(/%AGE%/g, this.data[i].age);
							m = m.replace(/%ORIENT%/g, this.data[i].orient).replace(/%PHCOUNT%/g, this.data[i].phcount);
							m = m.replace(/%SEX%/g, (this.data[i].sex == 1)? "findm.gif" : "findw.gif");
							msg += m;
						}

						this.msg = (this.count > 1)? gmaps.maptmpl.tmpl_group.replace(/%COUNT%/g, this.count).replace(/%PAGE%/g, (this.first / 3 + 1)).replace(/%GROUPCONT%/g, msg) : msg;
						var d = gmaps.mymarker? gmaps.mymarker.distance(this.marker) : 0;
						this.msg = this.msg.replace(/%DISTANCE%/g, (d > 999)? ((d / 1000).toFixed(1) + gmaps.mapmsg.km) : (d + gmaps.mapmsg.m));
						this.msg = this.msg.replace(/%GRHIDE%/g, (this.data && this.count > 1)? 'none' : 'block');
						this.msg = this.msg.replace(/%GRSHOW%/g, (this.data && this.count > 1)? 'block' : 'none');
					}
					gmaps.infowin.setContent(this.msg.replace(/%DECODER%/g, this.dec));
				}
				if(!this.dec) this.decoder();
				return this;
			}
		};

		google.maps.event.addListener(res.marker, 'click', function(){
				res.info();
				if(!(data && data.self)) gmaps.myselect = res;
			}
		);

		google.maps.event.addListener(res.marker, 'dblclick', function(){
				if(res.count > 1) res.zoom(0,2); else res.zoom(15);
			}
		);

		if(data && data.self) google.maps.event.addListener(res.marker, 'dragend', function(event){
				res.center(true).decoder();
				gmaps.sendpos(event.latLng);
			}
		);
		return res;
	},

	setmypos: function(location){
		if(!this.mymarker){
			this.mymarker = this.addmarker(location, 1, this.mapopt.data_self);
			this.mymarker.show(true);
		}else{
			this.mymarker.marker.setPosition(location);
		}
		this.sendpos(location);
		this.mymarker.decoder();
		return this.mymarker;
	},

	sendpos: function(location){
		this.alert('save');
		var pos = location? MercatorProjection.fromLatLngToPixel(location, this.zoom_def) : {x:0, y:0};
		this.xhr.open("GET", '/?a=mappos&x='+pos.x+"&y="+pos.y, true);
		this.xhr.onreadystatechange = function(){
			if(gmaps.xhr.readyState == 4 && gmaps.xhr.status == 200){
				var responseText = gmaps.xhr.responseText;
				gmaps.alert((responseText == '1')? '' : 'saveerror');
				if(!location && gmaps.mymarker){ gmaps.mymarker.show(false); gmaps.mymarker = null; }
			}
		}
		this.xhr.send(null);
	},

	viewtiles: function(pos){
		var out = {},
		w = parseInt(this.mapopt.map_canvas.style.width),  h = parseInt(this.mapopt.map_canvas.style.height),
		x1 = pos.x - w / 2, y1 = pos.y - h / 2, x2 = x1 + w, y2 = y1 + h;
		var tbx = Math.floor(x1 / this.tile_size), tex = Math.floor(x2 / this.tile_size),
		tby = Math.floor(y1 / this.tile_size), tey = Math.floor(y2 / this.tile_size);
		for(var j = tby; j <= tey; j++) for(var i = tbx; i <= tex; i++) if(i >= 0 && j >= 0) out[i+':'+j] = {x:i, y:j};
		return out;
	},

	checktiles: function(tile, zoom){
		var out = '';
		for(var i in tile) if(!this.tiles[zoom+':'+i]){
			if(out != '') out += ':';
			out += tile[i].x + ',' + tile[i].y;
		}
		return out;
	},

	gettile: function(){
		var zoom = (this.map.getZoom() > this.zoom_max)? this.zoom_max : this.map.getZoom();
		var pos = MercatorProjection.fromLatLngToPixel(this.map.getCenter(), zoom);
		var loadtl = this.viewtiles(pos);
		var data = this.checktiles(loadtl, zoom);
		if(data != ''){
			this.alert('load');
			this.xhrt.open("GET", '/?a=maptile&zoom='+zoom+"&tile="+data, true);
			this.xhrt.onreadystatechange = function(){
				if(gmaps.xhrt.readyState == 4 && gmaps.xhrt.status == 200){
					var dt = 0, responseText = gmaps.xhrt.responseText;
					try{ dt = (typeof JSON != 'undefined')? JSON.parse(responseText) : eval(responseText); gmaps.alert(''); }catch(e){ gmaps.alert('loaderror'); return; }
					for(var i = 0; i < dt.length; i++){
						var ind = dt[i].zoom+':'+dt[i].tlx+':'+dt[i].tly;
						if(!gmaps.markers[ind]) gmaps.markers[ind] = [];
						gmaps.tiles[ind] = {};
						for(var j = 0; j < dt[i].coord.length; j++){
							var crd = dt[i].coord[j].x+':'+dt[i].coord[j].y;
							if(dt[i].coord[j].data && gmaps.mapopt.data_self.crc === dt[i].coord[j].data.crc) continue;
							if(dt[i].coord[j].data && gmaps.mapopt.data_targ.crc === dt[i].coord[j].data.crc) continue;
							if(!gmaps.tiles[ind][crd]){
								var mrk = gmaps.addmarker(MercatorProjection.fromPixelToLatLng(dt[i].coord[j].x, dt[i].coord[j].y, gmaps.zoom_def), dt[i].coord[j].count, dt[i].coord[j].data);
								mrk.show(true);
								gmaps.markers[ind].push(mrk);
								gmaps.tiles[ind][crd] = mrk;
							}else{
								gmaps.tiles[ind][crd].append(dt[i].coord[j].data);
							}
						}
					}
				}
			}
			this.xhrt.send(null);
		}

		for(var t in this.last_tl) if((this.last_zm != zoom || loadtl[t] === undefined) && this.markers[this.last_zm+':'+t]) for(var i = 0; i < this.markers[this.last_zm+':'+t].length; i++){
			this.markers[this.last_zm+':'+t][i].show(false);
		}


		for(var t in loadtl) if((this.last_zm != zoom || this.last_tl[t] === undefined) && this.markers[zoom+':'+t]) for(var i = 0; i < this.markers[zoom+':'+t].length; i++){
			this.markers[zoom+':'+t][i].show(true);
		}

		this.last_tl = loadtl;
		this.last_zm = zoom;
	},

	input: function(el){
		var e = el || window.event;
		var code = e.charCode || e.keyCode;
		if (code == 13 || code == 10) {
			this.address();
		}
	},

	address: function(addr){
		this.geocoder.geocode({address: addr? addr : this.mapopt.map_addr.value},
			function(results, status){
				if(status == google.maps.GeocoderStatus.OK){
					gmaps.map.fitBounds(results[0].geometry.viewport);
				}else gmaps.alert('geocode');
			}
		);
		return false;
	},

	geo: function(){
		if(navigator.geolocation){
			this.brsupport = true;
			navigator.geolocation.getCurrentPosition(
				function(position){
					gmaps.setmypos(new google.maps.LatLng(position.coords.latitude,position.coords.longitude)).center().info();
					gmaps.map.setZoom(15);
				},
				gmaps.error
			);
		}else if(google.gears){
			this.brsupport = true;
			var geo = google.gears.factory.create('beta.geolocation');
			geo.getCurrentPosition(
				function(position){
					gmaps.setmypos(new google.maps.LatLng(position.latitude,position.longitude)).center().info();
					gmaps.map.setZoom(15);
				},
				gmaps.error
			);
		}else{
			this.brsupport = false;
			this.error();
		}
		return false;
	},

	decoder: function(target){
		this.geocoder.geocode({latLng: target.marker.getPosition()},
			function(results, status){
				if(status == google.maps.GeocoderStatus.OK){
					//results[0].address_components[i].types[0] + results[0].address_components[i].long_name
					target.dec = results[0].formatted_address;
					target.info(true);
				}
			}
		);
	},

	alert: function(msg){
		msg = this.mapmsg[msg] ? this.mapmsg[msg] : msg;
		if(this.mapopt.map_error) this.mapopt.map_error.innerHTML = msg;
		return msg;
	},

	error: function(){
		if(gmaps.brsupport){
			gmaps.alert('failed');
		}else{
			gmaps.alert('browser');
		}
	}
};
