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,
	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')
		}
		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."
		}
		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;
	},
	
	open: function(){
		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){
		if(type == 'hlp' && !gmaps.setpos) this.alert(msg);
		if(type == 'loc'){ this.alert(gmaps.setpos? '' : msg); gmaps.setpos = !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();
		return false;
	},
	
	addmarker: function(location, count, data){
		var icon;
		if(typeof data != 'object'){
			icon = this.mapopt.icon_gr;
			data = {};
		}else 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,
			count: count,
			dec: '',
			msg: '',
			isshow: false,
			marker: new google.maps.Marker({
				position: location, 
				draggable: data.self? true : false,
				icon: icon.ic,
				shadow: icon.sh
			}),
			
			decoder: function(){ gmaps.decoder(this); 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;
			},
			info: function(plain, msg){
				if(msg) this.msg = msg;
				if(!plain){
					gmaps.infowin.open(gmaps.map, this.marker);
					gmaps.infowin.mid = this.mid;
				}
				if(gmaps.infowin.mid == this.mid){
					if(this.msg == ''){
						this.msg = (this.count > 1)? gmaps.maptmpl.tmpl_group : gmaps.maptmpl.tmpl_info;
						this.msg = this.msg.replace(/%PHOTO%/g, this.data.photo).replace(/%LOGIN%/g, this.data.login).replace(/%NAME%/g, this.data.name);
						this.msg = this.msg.replace(/%COUNTRY%/g, this.data.country).replace(/%CITY%/g, this.data.city).replace(/%AGE%/g, this.data.age);
						this.msg = this.msg.replace(/%ORIENT%/g, this.data.orient).replace(/%PHCOUNT%/g, this.data.phcount);
						this.msg = this.msg.replace(/%COUNT%/g, this.count).replace(/%SEX%/g, (this.data.sex == 1)? "findm.gif" : "findw.gif");
					}
					gmaps.infowin.setContent(this.msg.replace(/%DECODER%/g, this.dec));
				}
				if(this.dec == '' && this.count <= 1) this.decoder();
				return this;
			}
		};
		
		google.maps.event.addListener(res.marker, 'click', function(){
				res.info();
				if(!data.self) gmaps.mytarget = res;
			}
		);
		
		google.maps.event.addListener(res.marker, 'dblclick', function(){
				if(res.data.crc) res.zoom(15); else res.zoom(0,2);
			}
		);
		
		if(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.ceil(x2 / this.tile_size),
		tby = Math.floor(y1 / this.tile_size), tey = Math.ceil(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 = eval(responseText) }catch(e){ gmaps.alert('loaderror'); return; }
					
					for(var i = 0; i < dt.length; i++){
						gmaps.tiles[dt[i].zoom+':'+dt[i].tlx+':'+dt[i].tly] = 1;
						for(var j = 0; j < dt[i].coord.length; j++){
							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;
							var m = 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);
							m.show(true);
							if(!gmaps.markers[dt[i].zoom]) gmaps.markers[dt[i].zoom] = {};
							if(!gmaps.markers[dt[i].zoom][dt[i].tlx+':'+dt[i].tly]) gmaps.markers[dt[i].zoom][dt[i].tlx+':'+dt[i].tly] = [];
							gmaps.markers[dt[i].zoom][dt[i].tlx+':'+dt[i].tly].push(m);
						}
					}
					gmaps.alert('');
				}
			}
			this.xhrt.send(null);
		}/*
		for(var z in this.markers) for(var t in this.markers[z]) for(var i = 0; i < this.markers[z][t].length; i++){
			this.markers[z][t][i].show((z == zoom && loadtl[t] !== undefined));
		}*/
		for(var t in this.last_tl) if((this.last_zm != zoom || loadtl[t] === undefined) && this.markers[this.last_zm] && 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] && 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');
		}
	}
};