﻿/**
 * @author Sergey Chikuyonok (gonarch@design.ru)
 * @copyright Art.Lebedev Studio (http://www.artlebedev.ru)
 */

/**
 * @constructor
 * @param {Element, String} elem Element pointer or element's ID of minimap element
 * @param {Map} [map] Map service provider
 */
function MiniMap(elem, map){
	this.setPointer(elem);
	this.setMap(map);
};

/**
 * Set map service provider
 * @param {Map} map
 */
MiniMap.prototype.setMap=function(map){
	this._map=map;
};

/**
 * Return map service provider
 * @return {Map}
 */
MiniMap.prototype.getMap=function(){
	return this._map;
};

/**
 * Set minimap zoom
 * @param {Number} val
 */
MiniMap.prototype.setZoom=function(val){
	this.zoom=parseFloat(val);
};

/**
 * Return minimap zoom
 * @return {Number}
 */
MiniMap.prototype.getZoom=function(){
	return this.zoom;
};

/**
 * Set geographic coordinates of minimap's center
 * @param {Number} x
 * @param {Number} y
 */
MiniMap.prototype.setCenter=function(x, y){
	this.center={};
	this.center.x=x;
	this.center.y=y;
};

/**
 * Return geographic coordinates of minimap's center (object with 'x' and 'y' properties)
 * @return {Object}
 */
MiniMap.prototype.getCenter=function(){
	return this.center;
};

/**
 * Set minimap document element pointer
 * @param {Element} ptr
 */
MiniMap.prototype.setPointer=function(ptr){
	this.ptr=getElement(ptr);
	//var me=this;
	//addEvent(this.ptr, 'click', function(evt){me.moveToPoint(evt);});

	//find selection rectangle
	var items=this.ptr.getElementsByTagName('div');
	for(var i=0; i<items.length; i++){
		if(matchClass(items[i], 'rectangle')){
			this.rect=items[i];
			break;
		}
	}
	if(!this.rect){
		this._createRectangle();
	}
};

/**
 * Return minimap document elemnent pointer
 * @return {Element}
 */
MiniMap.prototype.getPointer=function(){
	return this.ptr;
};

/**
 * Return minimap width
 * @return {Number}
 */
MiniMap.prototype.getWidth=function(){
	return this.getPointer().offsetWidth;
};

/**
 * Return minimap width
 * @return {Number}
 */
MiniMap.prototype.getHeight=function(){
	return this.getPointer().offsetHeight;
};

/**
 * Return minimap scale
 */
MiniMap.prototype.getScale=function(){
	return this.getZoom()/this.getWidth();
};

MiniMap.prototype.getRectangle=function(){
	return this.rect;
};

/**
 * Draw rectangle for current map state
 */
MiniMap.prototype.draw=function(){
	var map=this.getMap();
	if(map){
		if(!this.rect){
			this._createRectangle();
		}

		this.showRectangle();

		var scale=map.getScale();
		var rx=map.Earth.equator/scale;
		var ry=map.Earth.meridian/scale;
		var rs=map.getZoomValue()/this.getZoom();

		var mini_width=this.getWidth(), mini_height=this.getHeight();

		var center=this.getCenter();
		var map_center=map.getCoordinates();
		var w=Math.round(Math.max(map.getWidth()*rs, 5));
		var h=Math.round(Math.max(map.getHeight()*rs, 5));

		var dx=center.x - map_center.x;
		var dy=center.y - map_center.y;
		var cx=dx*rx*rs;
		var cy=dy*ry*rs;

		var x=Math.round(-cx+mini_width/2 - w/2);
		var y=Math.round(cy+mini_height/2 - h/2);

		//проверяем, чтобы размеры прямоугольника в его текущем положении не превышали размер миникарты
		if(x < 0){
			w+=x; x=0;
		}
		if(x+w > mini_width){
			w=mini_width-x;
		}

		if(y < 0){
			h+=y; y=0;
		}
		if(y+h > mini_height){
			h=mini_height-y;
		}

		//console.log('dimensions: x %d, y %d, w %d, h %d', x, y, w, h);

		if(w >0 && h>0 && w<500 && h<500){
			this.rect.style.left=x+'px';
			this.rect.style.top=y+'px';
			this.rect.style.width=w+'px';
			this.rect.style.height=h+'px';
		}
	}
};

/**
 * Creates rectangle that reflects map position
 */
MiniMap.prototype._createRectangle=function(){
	this.rect=document.createElement('div');
	this.rect.className='rectangle';
	var ptr=this.getPointer();
	if(ptr){
		ptr.appendChild(this.rect);
	}
	this.hideRectangle();
};

/**
 * Returns offset in real map pixels
 * @param {Number} x
 * @param {Number} y
 * @return {Object}
 */
MiniMap.prototype.getOffset=function(x, y){
	var map=this.getMap();
	var dx=x - this.rect.offsetLeft - this.rect.offsetWidth/2;
	var dy=y - this.rect.offsetTop - this.rect.offsetHeight/2;
	var r=this.getZoom()/map.getZoomValue();
	return {x: -dx*r, y: -dy*r};
};

MiniMap.prototype.hideRectangle=function(){
	if(this.rect){
		addClass(this.rect, 'hidden');
	}
};

MiniMap.prototype.showRectangle=function(){
	if(this.rect){
		removeClass(this.rect, 'hidden');
	}
};

/**
 * Map extensions base class
 * @constructor
 */
function MapExtension(){
	this._iface=null;
}

MapExtension.prototype={
	/**
	 * Set map interface provider
	 * @param {MapIFace} iface
	 */
	setIface: function(iface){
		this._iface=iface;
	},

	/**
	 * Get map interface provider
	 * @return {MapIFace}
	 */
	getIface: function(){
		return this._iface;
	}
};


/**
 * Map toolbar extension
 * @constructor
 * @extends {MapExtension}
 * @param {String, Element} elem_toolbar Toolbar pointer
 * @param {String, Element} elem_point_info Point info container
 */
function MapControl(elem_point_info){
	MapControl.baseConstructor.call(this);
	this.tools={};
	this._defaultSelected='pan';
	this._drag=false;
	this.blocked=false;
	this.setPointInfoContainer(elem_point_info);
};

MapControl.inheritFrom(MapExtension);

MapControl.prototype.setPointInfoContainer=function(elem){
	this._point_container=getElement(elem);
};

/**
 * @return {Element}
 */
MapControl.prototype.getPointInfoContainer=function(){
	return this._point_container;
};

/**
 * Set map interface provider
 * @param {MapIFace} iface
 */
MapControl.prototype.setIface=function(iface){
	MapControl.baseConstructor.prototype.setIface.call(this, iface);
	if(this._defaultSelected){
		this.setTool('pan');
		this._defaultSelected=null;
	}
};


/**
 * Set current tool
 * @param {String} tool_name
 */
MapControl.prototype.setTool=function(tool_name){
	var iface=this.getIface();
	var map_container=iface.getContainer();
	removeClass(map_container, 'tool-\\w+');
	addClass(map_container, 'tool-'+tool_name);
	this._selectedTool=tool_name;
};

/**
 * Returns name of the current tool
 * @return {String}
 */
MapControl.prototype.getTool=function(){
	return this._selectedTool;
};


/**
 * Show full point info
 * @param {Object} point
 */
MapControl.prototype.showPointInfo=function(point){
	this.clearPointInfo();
	var p_inf=this.getPointInfoContainer();
	header=document.createElement('h2');
	header.appendChild(document.createTextNode('Position'));
	info=document.createElement('p');
	info.appendChild(document.createTextNode(point.description));
	p_inf.appendChild(header);
	p_inf.appendChild(info);

	header=document.createElement('h2');
	header.appendChild(document.createTextNode('Fuel'));
	info=document.createElement('div');
	info.className='fuel-icons';
	var el;
	for(var i=0; i<point.fuel.length; i++){
		el=document.createElement('img');
		el.src='./i/e.gif';
		el.className=point.fuel[i];
		info.appendChild(el);
	}
	p_inf.appendChild(header);
	p_inf.appendChild(info);

	header=document.createElement('h2');
	header.appendChild(document.createTextNode('Service'));
	info=document.createElement('div');
	info.className='service-icons';
	for(var i=0; i<point.service.length; i++){
		el=document.createElement('img');
		el.src='./i/e.gif';
		el.className=point.service[i];
		info.appendChild(el);
	}
	p_inf.appendChild(header);
	p_inf.appendChild(info);
};

MapControl.prototype.clearPointInfo=function(){
	var p_inf=this.getPointInfoContainer();
	if(p_inf){
		var items=p_inf.childNodes;
		for(var i=items.length-1; i>=0; i--){
			items[i].parentNode.removeChild(items[i]);
		}
	}
};

/**
 * Minimaps extension
 * @constructor
 * @extends {MapExtension}
 * @param {String, Element} map_country Minimap container pointer
 */
function MapMiniExtension(minimap){
	MapMiniExtension.baseConstructor.call(this);
	this.setMinimap(minimap);
};

MapMiniExtension.inheritFrom(MapExtension);

MapMiniExtension.prototype.setMinimap=function(obj){
	this.minimap=obj;
	if(obj){
		obj.draw();
		var me=this;
		addEvent(obj.getPointer(), 'mouseup', function(evt){me.dispatchEvent(evt);});
		addEvent(obj.getPointer(), 'mousedown', function(evt){me.dispatchEvent(evt);});
		addEvent(obj.getPointer(), 'mousemove', function(evt){me.dispatchEvent(evt);});
	}
};

/** @return {MiniMap} */
MapMiniExtension.prototype.getMinimap=function(){
	return this.minimap;
};

/**
 * Set map interface provider
 * @param {MapIFace} iface
 */
MapMiniExtension.prototype.setIface=function(iface){
	MapMiniExtension.baseConstructor.prototype.setIface.call(this, iface);
	this.draw();
};

MapMiniExtension.prototype.onMapStartLoading=function(){
	this.draw();
};

MapMiniExtension.prototype.draw=function(){
	var m=this.getMinimap();
	if(m){ m.draw(); }
};

MapMiniExtension.prototype._onMouseDown=function(evt){
	var minimap=this.getMinimap();
	if(minimap){
		var rect=minimap.getRectangle();
		if(evt.target == rect){
			this._drag=true;
			this._dragStart={x: evt.clientX, y: evt.clientY};
			this._rectState={
				x: rect.offsetLeft,
				y: rect.offsetTop,
				max_x: minimap.getWidth() - rect.offsetWidth,
				max_y: minimap.getHeight() - rect.offsetHeight
			};
		}
	}
};

MapMiniExtension.prototype._onDrag=function(evt){
	if(this._drag){
		var dx=evt.clientX - this._dragStart.x;
		var dy=evt.clientY - this._dragStart.y;

		var x=Math.max(0, Math.min(this._rectState.x + dx, this._rectState.max_x));
		var y=Math.max(0, Math.min(this._rectState.y + dy, this._rectState.max_y));
		var minimap=this.getMinimap();
		var rect=minimap.getRectangle();

		rect.style.left=x+'px';
		rect.style.top=y+'px';
	}
};

MapMiniExtension.prototype._onMouseUp=function(evt){
	this.moveToPoint(evt);
	this._drag=false;
};

MapMiniExtension.prototype.dispatchEvent=function(evt){
	if((evt=checkEvent(evt))){
		switch(evt.type){
			case 'mousedown':
				this._onMouseDown(evt);
				break;
			case 'mouseup':
				this._onMouseUp(evt);
				break;
			case 'mousemove':
				this._onDrag(evt);
				break;
		}
	}
};

/**
 * Move map to clicked point
 * @param {Event} evt
 */
MapMiniExtension.prototype.moveToPoint=function(evt){
	if((evt=checkEvent(evt))){
		var x, y, minimap=this.getMinimap();
		if(!minimap){
			return;
		}
		if(this._drag){
			var rect=minimap.getRectangle();
			x=2*rect.offsetLeft-this._rectState.x+rect.offsetWidth/2;
			y=2*rect.offsetTop-this._rectState.y+rect.offsetHeight/2;
		}
		else{
			x=evt.layerX;
			y=evt.layerY;
//			alert(x+', '+y+', '+evt.target.id);
			var elem=evt.target, ptr=minimap.getPointer();

			if(ptr.runtimeStyle && !window.opera){
				//stupid IE
				x-=ptr.offsetLeft;
				y-=ptr.offsetTop;
			}

			do{
				if(elem == ptr){
					break;
				}
				else{
					x+=elem.offsetLeft;
					y+=elem.offsetTop;
				}
			}while((elem=elem.parentNode));

//			alert(x+', '+y+', '+minimap.getWidth() +', ' +  minimap.getHeight());


			//если кликнули за пределы миникарты - ничего не делаем
			if(x<0 || y<0 || x>minimap.getWidth() || y>minimap.getHeight()){
				return;
			}
		}


		var iface=this.getIface();
		var d=minimap.getOffset(x, y);
		iface.offsetMap(d.x, d.y);
		iface.loadMap();
	}
};

/**
 * City list extension
 * @constructor
 * @extends {MapMiniExtension}
 * @param {String, Element} elem City list container pointer
 */
function MapCityList(elem, label){
	MapCityList.baseConstructor.call(this);
	this._city=null;
	this.setDefaultLabel(label);
	this.setContainer(elem);
};

MapCityList.inheritFrom(MapMiniExtension);

MapCityList.prototype.setContainer=function(elem){
	this._container=getElement(elem);
};

/** @return {Element} */
MapCityList.prototype.getContainer=function(){
	return this._container;
};

/** @param {Element} city_list */
MapCityList.prototype.setCityList=function(city_list){
	var me=this;
	this._cityList=city_list;
	addEvent(city_list, 'change', function(evt){me.onCitySelect(evt);});
};

MapCityList.prototype.getCityList=function(){
	return this._cityList;
};

MapCityList.prototype.setDefaultLabel=function(label){
	this._defLabel=label;
};

MapCityList.prototype.getDefaultLabel=function(){
	return this._defLabel;
};

/**
 * Set map interface provider
 * @param {MapIFace} iface
 */
MapCityList.prototype.setIface=function(iface){
	MapCityList.baseConstructor.prototype.setIface.call(this, iface);
	this.createList();
};

MapCityList.prototype.setCurrentCity=function(city){
	this._city=city;
	this.setMinimap((city) ? city.getMinimap() : null);
};

MapCityList.prototype.getCurrentCity=function(){
	return this._city;
};

MapCityList.prototype.createList=function(){
	var cont=this.getContainer();
	if(cont){
		var iface=this.getIface();
		var map=iface.getMap();
		var city_list=map.getCities();
		var city_cont=document.createElement('select');

		city_cont.options[0]=new Option(this.getDefaultLabel(), 0);
		for(var i=0; i<city_list.length; i++){
			this._addCityInList(city_cont, city_list[i]);
		}
		cont.appendChild(city_cont);

		this.setCityList(city_cont);
	}
};

/**
 * Add city to list
 * @param {Element} container
 * @param {ISGEOCityMap} city
 */
MapCityList.prototype._addCityInList=function(container, city){
	container.options[container.options.length]=new Option(city.getCityName(), city.getMapName());
};

MapCityList.prototype.onCitySelect=function(evt){
	if((evt=checkEvent(evt))){
		var map_name=evt.target.value;
		if(map_name == '0'){
			var iface=this.getIface();
			var map=iface.getMap();

			map.setZoomLevel(map.switchMapThreshold);
			iface.loadMap();
		}
		else{
			var city=ISGEOCityMap.find(map_name);
			if(city){
				this.selectCity(city);
			}
		}
	}
};

/**
 * Moves and zooms map for passed city
 * @param {ISGEOCityMap} city
 */
MapCityList.prototype.goToCity=function(city){
	var iface=this.getIface();
	var map=iface.getMap();
	this.setCurrentCity(city);
	if(map){
		var minimap=city.getMinimap();
		map.setCoordinates(minimap.getCenter());
		map.setZoomLevel(map.switchMapThreshold+1);
		iface.loadMap();
	}
};

MapCityList.prototype.selectCity=function(city){
	var city_list=this.getCityList();
	if(city_list){
		var opts=city_list.options;
		var il=opts.length;
		var city_name=city.getMapName();
		this.getContainer().className='minimap '+city_name;
		for(var i=0; i<il; i++){
			if(opts[i].value == city_name){
				city_list.selectedIndex=i;
				break;
			}
		}

		this.goToCity(city);
	}
};

MapCityList.prototype.removeCitySelection=function(){
	var city_list=this.getCityList();
	if(city_list){
		city_list.selectedIndex=0;
	}
	var city=this.getCurrentCity();
	city.getMinimap().hideRectangle();
	this.getContainer().className='minimap';
	this.setCurrentCity(null);
};

MapCityList.prototype.onMapStartLoading=function(){
	this.draw();
};

MapCityList.prototype.onMapFinishLoading=function(){
	this.draw();
};


/**
 * Map point search extension
 * @constructor
 * @extends {MapExtension}
 * @param {String, Element} elem Search container pointer
 */
function MapPointSearch(elem){
	MapPointSearch.baseConstructor.call(this);
	this.tools={};
	this._lastSearch='';
	this._preventHiding=false;
	this.foundPoints=null;
	this.currentPoint=null;
	this.setContainer(elem);
};

MapPointSearch.inheritFrom(MapExtension);

MapPointSearch.prototype.setContainer=function(elem){
	this._container=getElement(elem);
	this.getTools();
};

/** @return {Element} */
MapPointSearch.prototype.getContainer=function(){
	return this._container;
};

MapPointSearch.prototype.setCurrentPoint=function(point){
	this.currentPoint=point;
};

MapPointSearch.prototype.getCurrentPoint=function(){
	return this.currentPoint;
};

MapPointSearch.prototype.getTools=function(){
	var cont=this.getContainer();
	if(cont){
		this.tools={
			field: cont.getElementsByTagName('input')[0],
			result: cont.getElementsByTagName('div')[0]
		};

		var me=this;
		var f=function(evt){me.dispatchEvent(evt);};
		addEvent(this.tools.field, 'keyup', f);
		addEvent(this.tools.field, 'keydown', f);
//		addEvent(this.tools.field, 'keypress', f);
		addEvent(this.tools.field, 'mousedown', f);
		//addEvent(this.tools.field, 'blur', f);
		addEvent(this.tools.field, 'focus', f);

		addEvent(this.tools.result, 'mousedown', function(evt){
			me.stopDelayedHide();
			//return cancelEvent(checkEvent(evt));
		});

		addEvent(document.body, 'mousedown', function(evt){
			me.delayedHide();
			//return cancelEvent(checkEvent(evt));
		});
	}
};

MapPointSearch.prototype.setFoundPoints=function(points){
	this.foundPoints=points;
};

/** @return {Array} */
MapPointSearch.prototype.getFoundPoints=function(){
	return this.foundPoints;
};

/**
 * Dispatch search field events
 * @param {Event} evt
 */
MapPointSearch.prototype.dispatchEvent=function(evt){
	if((evt=checkEvent(evt))){
		var searchStr=this.tools.field.value;
		switch(evt.type){
			case 'keydown':
				if(evt.keyCode == 38 || evt.keyCode == 40){
					this.navigate((evt.keyCode == 38) ? -1 : 1);
//					return cancelEvent(evt);
				}
				break;
			case 'keyup':
			case 'keypress':
				if(evt.keyCode == 38 || evt.keyCode == 40){
					break;
				}
				else if(this.getFoundPoints()){
					//check if we want to navigate through search results
					if(evt.keyCode == 13){
						//press 'enter' key, go to selected point
						var items=this.tools.result.getElementsByTagName('div');
						var sel_item;
						for(var i=0; i<items.length; i++){
							if(items[i].className.indexOf('selected') != -1){
								sel_item=items[i];
								break;
							}
						}

						if(sel_item && this.goToPoint(sel_item.id.substr(1))){
							this.tools.field.blur();
							this.reset();
							return;
						}
					}
				}
//				else{
					this._makeSearch(searchStr);
//				}

				break;
			case 'mousedown':
				this.preventHiding(true);
				break;
			case 'focus':
			case 'change':
				this._makeSearch(searchStr);
				break;
			case 'blur':
				this.delayedHide();
				break;
		}
	}
};

MapPointSearch.prototype.preventHiding=function(state){
	if(state != undefined)
		this._preventHiding=state;

	return this._preventHiding;
};

MapPointSearch.prototype.delayedHide=function(){
	if(this.preventHiding()){
		this.preventHiding(false);
		return;
	}

	var me=this;
	this._timer=setTimeout(function(){
		me.reset();
	}, 200);
};

MapPointSearch.prototype.stopDelayedHide=function(){
	if(this._timer)
		clearTimeout(this._timer);
	this.preventHiding(true);
};

MapPointSearch.prototype._makeSearch=function(str){
	if((!str || str.length < 3) && this.getFoundPoints()){
		this.clear();
		return;
	}
	else if(str && str.length >= 3 && str != this._lastSearch){
		this.search(str);
		if(this.getFoundPoints()){
			this.show();
		}
		else{
			this.clear();
		}
	}
};

MapPointSearch.prototype.goToPoint=function(id){
	var iface=this.getIface();
	var map=iface.getMap();
	var point=map.getPointById(id);
	if(point){
		this.setCurrentPoint(point);
		map.setCoordinates(point);
		map.setZoomLevel(map.switchMapThreshold);
		this.tools.field.blur();
		this.clear();
		iface.loadMap();
		return true;
	}
	return false;
};

MapPointSearch.prototype.onResultClick=function(evt){
	if((evt=checkEvent(evt))){
		var elem=(evt.target.nodeType == 3) ? evt.target.parentNode : evt.target;
		var item_id=elem.id.substr(1);
		this.goToPoint(item_id);
		this.reset();
		return cancelEvent(evt);
	}
};

MapPointSearch.prototype.search=function(str){
	var iface=this.getIface();
	var map=iface.getMap();
	this.setFoundPoints(map.searchPoint(str));
	this._lastSearch = str;
};

MapPointSearch.prototype.show=function(){
	this.clear();
	var s=this.getFoundPoints();
	if(s){
		removeClass(this.tools.result, 'hidden');
		var len=Math.min(20, s.length);
		var me=this;
		var f1=function(evt){me.hiliteResult(evt);};
		var f2=function(evt){me.onResultClick(evt);};
		for(var i=0; i<len; i++){
			var item=document.createElement('div');
			item.id='p'+s[i].id;
			item.appendChild(document.createTextNode(s[i].description));
			this.tools.result.appendChild(item);
			addEvent(item, 'mouseover', f1);
			addEvent(item, 'mousedown', f2);
		}
	}
};

MapPointSearch.prototype.navigate=function(offset){
	//find selected item
	var items=this.tools.result.getElementsByTagName('div');

	if(!items.length)
		return;

	var sel_item=null, sel_pos=-1;
	for(var i=0; i<items.length; i++){
		if(matchClass(items[i], 'selected')){
			sel_item=items[i];
			sel_pos=i;
			break;
		}
	}
	var new_pos=0;
	if(sel_item){
		new_pos=Math.max(Math.min(sel_pos+offset, items.length-1), 0);
		removeClass(sel_item, 'selected');
	}
	addClass(items[new_pos], 'selected');
	this.scrollIntoView(items[new_pos]);
//	items[new_pos].scrollIntoView(false);
};

MapPointSearch.prototype.scrollIntoView = function(elem){
	var pos = elem.offsetTop;
	var parent = elem.parentNode;

	var p_height = parent.offsetHeight;
	var p_scroll = parent.scrollTop;

	var visible_pos = p_scroll + p_height*.7;
	if(pos > visible_pos || pos < p_scroll){
		parent.scrollTop = pos - p_height/2;
	}
};

MapPointSearch.prototype.hiliteResult=function(evt){
	if((evt=checkEvent(evt))){
		var elem=(evt.target.nodeType == 3) ? evt.target.parentNode : evt.target;
		var items=this.tools.result.getElementsByTagName('div');
		for(var i=0; i<items.length; i++){
			if(matchClass(items[i], 'selected')){
				removeClass(items[i], 'selected');
				break;
			}
		}
		addClass(elem, 'selected');
	}
};

MapPointSearch.prototype.clear=function(){
	var ch=this.tools.result.childNodes;
	for(var i=ch.length-1; i>=0; i--){
		ch[i].parentNode.removeChild(ch[i]);
	}
	addClass(this.tools.result, 'hidden');
};

MapPointSearch.prototype.reset = function(){
	this.clear();
	this.setFoundPoints(null);
	this._lastSearch = '';
}

MapPointSearch.prototype.onMouseDown=function(){
//	this.delayedHide();
};

/**
 * Region selector class
 * @extends {MapExtension}
 * @param {String, Element} elem ID or element where selectors should be
 */
function MapRegionSelector(elem, url){
	MapRegionSelector.baseConstructor.call(this);
	this._url=url||'proxy.html';
	//this._url='regions.xml';
	this._method='POST';
	this.ajax=null;
	this._default_params={
		MapName: 'Ukraine200A',
		Language: 'rus'
	};

	this.setContainer(elem);
};

MapRegionSelector.inheritFrom(MapExtension);

MapRegionSelector.prototype.setContainer=function(elem){
	this._container=getElement(elem);
	this.region=this.createSelector('region');
	this.district=this.createSelector('district');
	this.city=this.createSelector('city');
	this.go_btn=this.createGoButton();

	var me=this;
	addEvent(this.region, 'change', function(evt){me.loadDistricts(evt);});
	addEvent(this.district, 'change', function(evt){me.loadCities(evt);});
	addEvent(this.go_btn, 'click', function(evt){me.goToPoint(evt);});

	this.loadRegions();
};

/** @return {Element} */
MapRegionSelector.prototype.getContainer=function(){
	return this._container;
};

/**
 * Creates SELECT element with specified name
 * @param {String} name Name for SELECT element
 */
MapRegionSelector.prototype.createSelector=function(name){
	var cont=this.getContainer();
	var elem=document.createElement('select');
	elem.setAttribute('name', name);
	elem.id=name;
	cont.appendChild(elem);
	addClass(elem, 'hidden');
	return elem;
};

/**
 * Makes parameters object needed to load proper xml
 * @param {Object} obj Paameters object
 * @return {Object}
 */
MapRegionSelector.prototype.makeParams=function(obj){
	var new_obj={};
	for(var a in this._default_params){
		new_obj[a]=this._default_params[a];
	}
	for(var a in obj){
		new_obj[a]=obj[a];
	}

	return new_obj;
};

MapRegionSelector.prototype.loadRegions=function(){
	var me=this;
	this._setLoadingState(true);
	this.ajax=new AJAXRequestClass(this._url, this._method);
	this.ajax.setParams( this.makeParams({cmd: 'getOblast'}) );
	this.ajax.setCallback(function(xhr){me.parseRegions(xhr);});
	this.ajax.load();
};

MapRegionSelector.prototype.loadDistricts=function(evt){
	if((evt=checkEvent(evt)) && evt.target.value != '0'){
		this._setLoadingState(true);
		var me=this;
		this._clearNode(this.district);
		this._clearNode(this.city);
		this.ajax=new AJAXRequestClass(this._url, this._method);
		this.ajax.setParams( this.makeParams({cmd: 'getDistr', OblKO: evt.target.value}) );
		this.ajax.setCallback(function(xhr){me.parseDistricts(xhr);});
		this.ajax.load();
	}
};

MapRegionSelector.prototype.loadCities=function(evt){
	if((evt=checkEvent(evt)) && evt.target.value != '0'){
		this._setLoadingState(true);
		var me=this;
		this._clearNode(this.city);
		this.ajax=new AJAXRequestClass(this._url, this._method);
		this.ajax.setParams( this.makeParams({cmd: 'getSettl', DistrKO: evt.target.value}) );
		this.ajax.setCallback(function(xhr){me.parseCities(xhr);});
		this.ajax.load();
	}
};

/**
 * Get regions from loaded XML
 * @param {XMLHttpRequest} xhr
 */
MapRegionSelector.prototype.parseRegions=function(xhr){
	this._setLoadingState(false);
	this._regions=this._parseXml(xhr.responseXML, 'KOATUU', ['RusName', 'Centroid_X', 'Centroid_Y']);
	this._clearNode(this.region);
	this._clearNode(this.district);
	this._clearNode(this.city);
	this._addOption(this.region, 0, 'любая область');
	this._createOptions(this.region, this._regions, 'RusName');
	this.region.selectedIndex=0;
};

/**
 * Get districts from loaded XML
 * @param {XMLHttpRequest} xhr
 */
MapRegionSelector.prototype.parseDistricts=function(xhr){
	this._setLoadingState(false);
	this._districts=this._parseXml(xhr.responseXML, 'KOATUU', ['RusName', 'Centroid_X', 'Centroid_Y']);
	this._clearNode(this.district);
	this._clearNode(this.city);
	this._addOption(this.district, 0, 'любой район');
	this._createOptions(this.district, this._districts, 'RusName');
	this.district.selectedIndex=0;
};

/**
 * Get cities from loaded XML
 * @param {XMLHttpRequest} xhr
 */
MapRegionSelector.prototype.parseCities=function(xhr){
	this._setLoadingState(false);
	this._cities=this._parseXml(xhr.responseXML, 'KOATUU', ['RusName', 'Centroid_X', 'Centroid_Y']);
	this._clearNode(this.city);
	this._addOption(this.city, 0, 'любой город');
	this._createOptions(this.city, this._cities, 'RusName');
	this.city.selectedIndex=0;
};

MapRegionSelector.prototype.goToPoint=function(evt){
	var point, zoom;
	if(this._getSelectorValue(this.city) != '0'){
		point=this._cities[this._getSelectorValue(this.city)];
		zoom=0.5;
	}
	else if (this._getSelectorValue(this.district) != '0'){
		point=this._districts[this._getSelectorValue(this.district)];
		zoom=0.3;
	}
	else if (this._getSelectorValue(this.region) != '0'){
		point=this._regions[this._getSelectorValue(this.region)];
		zoom=0.2;
	}

	var iface=this.getIface();

	if(point && iface){
		var map=iface.getMap();
		map.setCoordinates({x: parseFloat(point.Centroid_X), y: parseFloat(point.Centroid_Y)});
		map.setZoom(zoom);
		iface.loadMap();
		//alert('going to '+point.Centroid_X+', '+point.Centroid_Y);
	}
};

/**
 * Return value of passed selector
 * @param {Element} selector
 */
MapRegionSelector.prototype._getSelectorValue=function(selector){
	if(selector.options.length){
		return selector.options[selector.selectedIndex].value;
	}
	else{
		return '0';
	}
};

/**
 * Reads loaded xml into array
 * @param {Document} xml XML with regions
 * @param {String} id_param XML item's attribute to interpret as ID
 * @param {Array} Attributes to read
 * @return {Array}
 */
MapRegionSelector.prototype._parseXml=function(xml, id_param, attrs){
	var result=[];
	if(xml){
		var items=xml.getElementsByTagName('row');
		if(!items.length){
			items=xml.getElementsByTagName('z:row');
		}

		var o, item, id, jl=attrs.length;
		for(var i=0; i<items.length; i++){
			item=items[i];
			id=item.getAttribute(id_param);

			o={};
			for(var j=0; j<jl; j++){
				o[attrs[j]]=item.getAttribute(attrs[j]);
			}

			result[id]=o;
		}
	}
	return result;
};

/**
 * Removes all children from element
 * @param {Element} elem
 */
MapRegionSelector.prototype._clearNode=function(elem){
	var ch=elem.childNodes;
	var len=ch.length;
	for(var i=len-1; i>=0; i--){
		ch[i].parentNode.removeChild(ch[i]);
	}
	addClass(elem, 'hidden');
};

/**
 * Creates and appends OPTION nodes to SELECT list
 * @param {Element} selector SELECT element pointer
 * @param {Array} collection Attributes collection
 * @param {String} name_attr Attribute's property to interpret as OPTION label
 */
MapRegionSelector.prototype._createOptions=function(selector, collection, name_attr){
	var option, count=0;
	for(var a in collection){
		if(!(collection[a] instanceof Function)){
			this._addOption(selector, a, collection[a][name_attr]);
			count++;
		}
	}
	if(count){
		removeClass(selector, 'hidden');
	}
	selector.selectedIndex=0;
};

/**
 * Add option to SELECT element
 * @param {Element} selector SELECT element
 * @param {String} value Option's value
 * @param {String} label Option's label
 */
MapRegionSelector.prototype._addOption=function(selector, value, label){
	var option=document.createElement('option');
	option.setAttribute('value', value);
	if(label){
		option.appendChild(document.createTextNode(label));
	}
	selector.appendChild(option);
};

/**
 * Sets container status which shows if something is loading or not
 * @param {Boolean} state
 */
MapRegionSelector.prototype._setLoadingState=function(state){
	var cont=this.getContainer();
	if(state){
		addClass(cont, 'loading');
	}
	else{
		removeClass(cont, 'loading');
	}
};

MapRegionSelector.prototype.createGoButton=function(){
	var cont=this.getContainer();
	var btn=document.createElement('input');
	btn.setAttribute('type', 'button');
	btn.setAttribute('value', 'Перейти');
	cont.appendChild(btn);
	return btn;
};

/**
 * Map point search bubble extension
 * @constructor
 * @extends {MapExtension}
 * @param {String, Element} elem Bubble container pointer
 */
function MapSearchBubble(elem){
	MapPointSearch.baseConstructor.call(this);
	this.currentPoint=null;
	this._visible=false;
	this._drag=false;
	this._point=null;
	this.x=0;
	this.y=0;
	this._content='';
	this.tools={};
	this.setContainer(elem);
};

MapSearchBubble.inheritFrom(MapExtension);

MapSearchBubble.prototype.findTools=function(){
	var cont=this.getContainer();
	var items=cont.getElementsByTagName('img');
	var me=this;
	this.tools={
		content: cont.getElementsByTagName('p')[0]
	};
	for(var i=0; i<items.length; i++){
		if(matchClass(items[i], 'close')){
			addEvent(items[i], 'click', function(evt){me.close(evt);});
		}
	}

	addEvent(cont, 'mousedown', function(evt){me.onBubbleClick(evt);});
};

MapSearchBubble.prototype.setContainer=function(elem){
	this._container=getElement(elem);
	this.findTools();
};

/** @return {Element} */
MapSearchBubble.prototype.getContainer=function(){
	return this._container;
};

MapSearchBubble.prototype.setPoint=function(point){
	this._point=point;
	this.setCoordinates(point.x, point.y);
	this.setContent(point.description);
};

MapSearchBubble.prototype.getPoint=function(){
	return this._point;
};

MapSearchBubble.prototype.setCoordinates=function(x, y){
	this.x=x;
	this.y=y;
	_pos=this.getAbsolutePos();
	dalert('move to '+_pos.x+', '+_pos.y);
	this.move(_pos.x, _pos.y);
};

MapSearchBubble.prototype.getCoordinates=function(){
	return {x: this.x, y: this.y};
};

MapSearchBubble.prototype.setContent=function(content){
	this._content=content;
	var ch=this.tools.content.childNodes;
	for(var i=ch.length-1; i>=0; i--){
		if(ch[i].nodeType == 3){
			ch[i].parentNode.removeChild(ch[i]);
		}
	}
	this.tools.content.appendChild(document.createTextNode(content));
};

/** @return {String} */
MapSearchBubble.prototype.getContent=function(){
	return this._content;
};

/**
 * Shows bubble with point info
 */
MapSearchBubble.prototype.show=function(){
	var cont=this.getContainer();
	var iface=this.getIface();
	var map=iface.getMap();
	if(map && cont){
		removeClass(cont, 'hidden');
		this._visible=true;
		//cont.style.left=(map.getWidth()/2)+'px';
		//cont.style.top=(map.getHeight()/2)+'px';
	}
};

MapSearchBubble.prototype.close=function(){
	var cont=this.getContainer();
	if(cont){
		addClass(cont, 'hidden');
		this._visible=false;
	}
};

/** @return {Boolean} */
MapSearchBubble.prototype.isVisible=function(){
	return this._visible;
};

/**
 * Returns bubble position in pixels
 */
MapSearchBubble.prototype.getAbsolutePos=function(){
	var iface=this.getIface();
	if(iface){
		var map=iface.getMap();
		var c1=this.getCoordinates();
		var c2=map.getCoordinates();
		var hx=map.getWidth()/2;
		var hy=map.getHeight()/2;
		var delta=this.geoToPixels(c1.x-c2.x, c1.y-c2.y);
		return {x: delta.x+hx, y: hy-delta.y};
	}
};

MapSearchBubble.prototype.geoToPixels=function(dx, dy){
	var iface=this.getIface();
	var map=iface.getMap();
	var scale=map.getScale();
	cx=map.Earth.equator*dx/scale;
	cy=map.Earth.meridian*dy/scale;
	return {x: cx, y: cy};
};

MapSearchBubble.prototype.move=function(x, y){
	//if(this.isVisible()){
		var bubble=this.getContainer();
		if(bubble){
			bubble.style.left=x+'px';
			bubble.style.top=y+'px';
		}
	//}
};

MapSearchBubble.prototype.onBubbleClick=function(evt){
	return cancelEvent(evt);
};

MapSearchBubble.prototype.onMapStartLoading=function(){
	this.block();
	var iface=this.getIface();
	var search_ext=iface.findExtensionByConstructor(MapPointSearch);
	if(search_ext){
		var point=search_ext.getCurrentPoint();
		if(point && point != this.getPoint()){
			this.close();
		}
	}
};

MapSearchBubble.prototype.onMapFinishLoading=function(){
	this.unblock();
	var iface=this.getIface();
	var map=iface.getMap();

	if(this.isVisible()){ //если пузырь виден, значит он привязан к какой-то координате, которую нужно отслеживать
		var _pos=this.getAbsolutePos();
		if(_pos.x < 0 || _pos.x > map.getWidth() || _pos.y < 0 || _pos.y > map.getHeight()){
			this.close();
		}
		else{
			this.move(_pos.x, _pos.y);
		}
	}
	else{ //в противном случае смотрим: если плагин поиска точки содержит выбранную точку, показываем пузырь в центре карты
		var search_ext=iface.findExtensionByConstructor(MapPointSearch), p;
		if(search_ext && (p=search_ext.getCurrentPoint())){
			this.setPoint(p);
			this.move(map.getWidth()/2, map.getHeight()/2);
			this.show();
			search_ext.setCurrentPoint(null);
		}
	}
};

MapSearchBubble.prototype.block=function(){
	this.blocked=true;
};

MapSearchBubble.prototype.unblock=function(){
	this.blocked=false;
};

MapSearchBubble.prototype.isBlocked=function(){
	return this.blocked;
};

MapSearchBubble.prototype.onMouseDown=function(evt){
	if(this.isVisible() && !this.isBlocked()){
		var bubble=this.getContainer();
		var iface=this.getIface();
		var toolbar_ext=iface.findExtensionByConstructor(MapToolbar);
		if(!iface.isEventOutsideMap(evt) && toolbar_ext && toolbar_ext.getTool() == 'pan'){
			this._drag=true;
			this._dragStart={x: evt.clientX, y: evt.clientY};
			this._bubblePos={x: bubble.offsetLeft, y: bubble.offsetTop};
		}
	}
};

MapSearchBubble.prototype.onMouseUp=function(){
	this._drag=false;
};

MapSearchBubble.prototype.onDrag=function(evt){
	if(this._drag){
		var bubble=this.getContainer();
		var x=(evt.clientX - this._dragStart.x + this._bubblePos.x);
		var y=(evt.clientY - this._dragStart.y + this._bubblePos.y);
		bubble.style.left=x+'px';
		bubble.style.top=y+'px';
	}
};

/**
 * Map zoom slider extension
 * @constructor
 * @extends {MapExtension}
 * @param {String, Element} elem Slider container pointer
 */
function MapZoomSlider(elem){
	MapZoomSlider.baseConstructor.call(this);
	this.tools={};
	this._drag=false;
	this.old_value=0;
	this.setMaxValue(1);
	this.setContainer(elem);
};

MapZoomSlider.inheritFrom(MapExtension);

MapZoomSlider.prototype.setContainer=function(elem){
	this._container=getElement(elem);
	this.findTools();
	this.setSize(this._container.offsetWidth - this.tools.head.offsetWidth);
	this.setValue(0);
};

MapZoomSlider.prototype.getValue=function(){
	return this._value;
};

MapZoomSlider.prototype.setValue=function(value){
	this._value=value;
};

MapZoomSlider.prototype.getMaxValue=function(){
	return this._maxValue;
};

MapZoomSlider.prototype.setMaxValue=function(value){
	this._maxValue=value;
};

MapZoomSlider.prototype.getSize=function(){
	return this._size;
};

MapZoomSlider.prototype.setSize=function(size){
	this._size=size;
};

/**
 * @return {Element}
 */
MapZoomSlider.prototype.getContainer=function(){
	return this._container;
};

MapZoomSlider.prototype.findTools=function(){
	var cont=this.getContainer();
	if(cont){
		this.tools={};
		var items=cont.getElementsByTagName('div');
		for(var i=0; i<items.length; i++){
			if(matchClass(items[i], 'zoom-plus')){
				this.tools.plus=items[i];
			}
			else if(matchClass(items[i], 'zoom-minus')){
				this.tools.minus=items[i];
			}
			else if(matchClass(items[i], 'zoom-slider-head')){
				this.tools.head=items[i];
			}
			else if(matchClass(items[i], 'zoom-slider')){
				this.tools.shaft=items[i];
			}
		}
	}
};

MapZoomSlider.prototype.onMouseDown=function(evt){
	var iface=this.getIface();
	switch(evt.target){
		case this.tools.plus:
			iface.zoomIn();
			iface.loadMap();
			break;
		case this.tools.minus:
			iface.zoomOut();
			iface.loadMap();
			break;
		case this.tools.shaft:
			this.move((evt.layerX-this.tools.head.offsetWidth/2)/this.getSize());
		case this.tools.head:
			this._drag=true;
			this._dragStart={x: evt.clientX, y: evt.clientY};
			this.old_value=this.getValue();
			break;
	}
};

MapZoomSlider.prototype.onMouseUp=function(evt){
	if(this._drag){
		var iface=this.getIface();
		var map=iface.getMap();
		map.setZoom(this.getValue());
		iface.loadMap();
	}
	this._drag=false;
};

MapZoomSlider.prototype.onMapStartLoading=function(){
	this.sync();
};

MapZoomSlider.prototype.onMapFinishLoading=function(){
	this.sync();
};


MapZoomSlider.prototype.onDrag=function(evt){
	if(this._drag){
		var offset=(evt.clientX - this._dragStart.x)/this.getSize();
		this.move(offset + this.old_value);
	}
};

/**
 * Moves slider head
 * @param {Number} pos Slider's position (in percents)
 */
MapZoomSlider.prototype.move=function(pos){
	if(this.tools.head){
		pos=Math.min(Math.max(0, pos), this.getMaxValue());
		this.setValue(pos);
		this.tools.head.style.left=(pos*this.getSize())+'px';
	}
};

/**
 * Syncronize slider with map
 */
MapZoomSlider.prototype.sync=function(){
	var iface=this.getIface();
	var map=iface.getMap();
	if(map){
		var zoom=map.getZoom();
		this.move(zoom);

		var total_levels=map.getZoomLevels().length - 1;
		var max_level=map.getMaxAvailableZoomLevel();
		var current_zoom_lev=map.findZoomLevel(zoom);

		if(max_level < total_levels){
			this._createDisabledShaft(max_level/total_levels);
		}
		else{
			this._removeDisabledShaft();
		}

		if(current_zoom_lev == max_level){
			addClass(this.tools.plus, 'disabled');
		}
		else{
			removeClass(this.tools.plus, 'disabled');
		}

		if(!current_zoom_lev){
			addClass(this.tools.minus, 'disabled');
		}
		else{
			removeClass(this.tools.minus, 'disabled');
		}
	}
};

MapZoomSlider.prototype._createDisabledShaft=function(value){
	if(!this._disabledShaft){
		this.setMaxValue(value);
		var size=this.getSize();
		var pos=value*size+this.tools.head.offsetWidth;
		var width=(1-value)*size;

		var elem=document.createElement('div');
		elem.className='disabled-shaft';
		elem.style.left=pos+'px';
		elem.style.width=width+'px';

		this._disabledShaft=elem;
		this.tools.shaft.appendChild(elem);
	}

};

MapZoomSlider.prototype._removeDisabledShaft=function(){
	if(this._disabledShaft){
		this.setMaxValue(1);
		this._disabledShaft.parentNode.removeChild(this._disabledShaft);
		this._disabledShaft=null;
	}
};

/**
 * City to country map switcher. Controls 'MapCityList' extension
 * @constructor
 * @extends {MapExtension}
 */
function MapSwitcher(elem){
	MapSwitcher.baseConstructor.call(this);
	this._city=null;
};

MapSwitcher.inheritFrom(MapExtension);

MapSwitcher.prototype.inCity=function(){
	var iface=this.getIface();
	var map=iface.getMap();
	return map.inCity();
};

MapSwitcher.prototype.setCity=function(city){
	this._city=city;
};

MapSwitcher.prototype.getCity=function(){
	return this._city;
};

MapSwitcher.prototype.onMapStartLoading=function(){
	var iface=this.getIface();
	var map=iface.getMap();
	var citylist_ext=iface.findExtensionByConstructor(MapCityList);
	if(citylist_ext){
		if(this.inCity() && !this.getCity()){
			var city=map.findCityMap();
			this.setCity(city);
			citylist_ext.selectCity(city);
		}
		else if(!this.inCity() && this.getCity()){
			this.setCity(null);
			citylist_ext.removeCitySelection();
		}
	}
};

/**
 * Map toolbar extension
 * @constructor
 * @extends {MapExtension}
 * @param {String, Element} elem_toolbar Toolbar pointer
 * @param {String, Element} elem_point_info Point info container
 */
function MapToolbar(elem_toolbar, elem_point_info){
	MapToolbar.baseConstructor.call(this);
	this.tools={};
	this._defaultSelected=null;
	this._drag=false;
	this.blocked=false;
	this.setContainer(elem_toolbar);
	this.setPointInfoContainer(elem_point_info);
	this.setCanvas(new SimpleCanvas('map-canvas'));
}

MapToolbar.inheritFrom(MapExtension);

MapToolbar.prototype.setContainer=function(elem){
	this._container=getElement(elem);
	this.findTools();
};

/**
 * @return {Element}
 */
MapToolbar.prototype.getContainer=function(){
	return this._container;
};


MapToolbar.prototype.setCanvas=function(obj){
	this._canvas=obj;
	addClass(obj.getContainer(), 'hidden');
};

MapToolbar.prototype.getCanvas=function(){
	return this._canvas;
};

MapToolbar.prototype.setPointInfoContainer=function(elem){
	this._point_container=getElement(elem);
};

/**
 * @return {Element}
 */
MapToolbar.prototype.getPointInfoContainer=function(){
	return this._point_container;
};

/**
 * Find available tools in toolbar
 */
MapToolbar.prototype.findTools=function(){
	var cont=this.getContainer();
	if(cont){
		this.tools={};
		var items=cont.getElementsByTagName('div');
		var re=/^([\w\-]+)/, cl, m, me=this;
		for(var i=0; i<items.length; i++){
			cl=items[i].className;
			if(cl && (m = re.exec(cl))){
				this.tools[m[1]]=items[i];
				addEvent(items[i], 'mousedown', function(evt){me.setTool(evt);});
				if(matchClass(items[i], 'selected')){
					this._defaultSelected=items[i];
				}
			}
		}
	}
};

/**
 * Set map interface provider
 * @param {MapIFace} iface
 */
MapToolbar.prototype.setIface=function(iface){
	MapToolbar.baseConstructor.prototype.setIface.call(this, iface);
	if(this._defaultSelected){
		this.setTool({target: this._defaultSelected});
		this._defaultSelected=null;
	}
};


/**
 * Set current tool
 * @param {Event} evt
 */
MapToolbar.prototype.setTool=function(evt){
	if((evt=checkEvent(evt))){
		var elem=evt.target, prev_tool, cur_tool;
		var iface=this.getIface();
		var map_container=iface.getContainer();
		//find previous and current tool
		for(var a in this.tools){
			if(matchClass(this.tools[a], 'selected')){
				prev_tool=a;
			}
			if(this.tools[a] == elem){
				cur_tool=a;
			}
		}

		if(prev_tool && prev_tool != cur_tool){
			removeClass(map_container, 'tool-'+prev_tool);
			removeClass(this.tools[prev_tool], 'selected');
		}

		if(cur_tool){
			addClass(map_container, 'tool-'+cur_tool);
			addClass(this.tools[cur_tool], 'selected');
			this._selectedTool=cur_tool;
		}
		return cancelEvent(evt);
	}
};

/**
 * Returns name of the current tool
 * @return {String}
 */
MapToolbar.prototype.getTool=function(){
	return this._selectedTool;
};

MapToolbar.prototype.block=function(){
	this.blocked=true;
};

MapToolbar.prototype.unblock=function(){
	this.blocked=false;
};

MapToolbar.prototype.isBlocked=function(){
	return this.blocked;
};

MapToolbar.prototype.onMouseDown=function(evt){
	if(this.isBlocked()){
		return;
	}

	var iface=this.getIface();

	if(!iface.isEventOutsideMap(evt)){
		var p=iface.findPointOnMap(evt);
		this._drag=true;
		this._dragStart={x: evt.clientX, y: evt.clientY};
		this._layerPos={x: p.x, y: p.y};

		switch(this.getTool()){
			case 'pan':
				var elem=iface.getMapImage();
				this._imagePos={x: elem.offsetLeft, y: elem.offsetTop};
				break;
			case 'zoomin':
				this.showZoomRectangle(this._layerPos.x, this._layerPos.y);
				break;
			case 'point-info':
				this.findPoint(this._layerPos.x, this._layerPos.y);
				break;
		}
	}
};

MapToolbar.prototype.onDrag=function(evt){
	if(this.isBlocked() || !this._drag){
		return;
	}
	switch(this.getTool()){
		case 'pan':
			this.moveMap(evt);
			break;
		case 'zoomin':
			this.drawZoomRectangle(evt);
			break;
	}
};

MapToolbar.prototype.onMouseUp=function(evt){
	if(this.isBlocked()){
		return;
	}

	this._drag=false;
	var iface=this.getIface();
	if(!iface.isEventOutsideMap(evt)){
		var map=iface.getMap();
		switch(this.getTool()){
			case 'zoomin':
				var elem=this._zoomRect;
					if(elem){
					var w=elem.offsetWidth, h=elem.offsetHeight;
					if(w > 10 && h > 10){ //prevent from mistaken selection
						this.updateByRectangle(elem.offsetLeft, elem.offsetTop, w, h);
					}
					else{
						iface.offsetMap(map.getWidth()/2 - this._layerPos.x, map.getHeight()/2 - this._layerPos.y);
						iface.zoomIn();
					}
					this.hideZoomRectangle();
					iface.loadMap();
				}
				break;
			case 'zoomout':
				iface.offsetMap(map.getWidth()/2 - this._layerPos.x, map.getHeight()/2 - this._layerPos.y);
				iface.zoomOut();
				iface.loadMap();
				break;
			case 'pan':
				var elem=iface.getMapImage();
				iface.offsetMap(elem.offsetLeft, elem.offsetTop);
				iface.loadMap();
				break;

		}
	}
};

MapToolbar.prototype.onMapStartLoading=function(){
	this.block();
};

MapToolbar.prototype.onMapFinishLoading=function(){
	this.unblock();
	this.findPoints();
};

/**
 * Map movement
 * @param {Event} evt
 */
MapToolbar.prototype.moveMap=function(evt){
	var iface=this.getIface();
	var elem=iface.getMapImage();
	var x=(evt.clientX - this._dragStart.x + this._imagePos.x);
	var y=(evt.clientY - this._dragStart.y + this._imagePos.y);
	elem.style.left=x+'px';
	elem.style.top=y+'px';

	return cancelEvent(evt);
};

/**
 * Show zoom rectangle in specified position (creates it if doesn't exists)
 * @param {Number} x
 * @param {Number} y
 */
MapToolbar.prototype.showZoomRectangle=function(x, y){
	var iface=this.getIface();
	var map_image=iface.getMapImage();
	if(!this._zoomRect){
		this._zoomRect=document.createElement('div');
		this._zoomRect.className='zoom-rectangle';
		map_image.parentNode.appendChild(this._zoomRect);
	}
	else{
		removeClass(this._zoomRect, 'hidden');
	}

	var s=this._zoomRect.style;
	s.left=x+'px';
	s.top=y+'px';
	s.width='1px';
	s.height='1px';
};

/**
 * Hide zoom rectangle
 */
MapToolbar.prototype.hideZoomRectangle=function(){
	if(this._zoomRect){
		addClass(this._zoomRect, 'hidden');
	}
};

MapToolbar.prototype.drawZoomRectangle=function(evt){
	if(this._zoomRect){
		var dx=evt.clientX - this._dragStart.x;
		var dy=evt.clientY - this._dragStart.y;
		var s=this._zoomRect.style;

		s.width=Math.abs(dx)+'px';
		s.height=Math.abs(dy)+'px';
		s.left=((dx < 0) ? this._layerPos.x + dx : this._layerPos.x)+'px';
		s.top=((dy < 0) ? this._layerPos.y + dy : this._layerPos.y)+'px';
	}
};

/**
 * Update map info by rectangle (used with zoom)
 * @param {Number} x
 * @param {Number} y
 * @param {Number} width
 * @param {Number} height
 */
MapToolbar.prototype.updateByRectangle=function(x, y, width, height){
	var iface=this.getIface();
	var map=iface.getMap(), k;
	iface.offsetMap(map.getWidth()/2 - x - width/2, map.getHeight()/2 - y - height/2);

	k=(width > height) ? width/map.getWidth() : height/map.getHeight();
	var zoom=map.getZoomValue();
	var new_zoom_ix=map.getZoomIndexByValue(zoom*k);
	map.setZoomLevel(new_zoom_ix);
};

MapToolbar.prototype.clearPointInfo=function(){
	var p_inf=this.getPointInfoContainer();
	if(p_inf){
		var items=p_inf.getElementsByTagName('ul');
		for(var i=items.length-1; i>=0; i--){
			items[i].parentNode.removeChild(items[i]);
		}

		addClass(p_inf, 'hidden');
	}

	this.getCanvas().clear();
};

/**
 * Find point by its coordinate
 * @param {Number} x
 * @param {Number} y
 */
MapToolbar.prototype.findPoint=function(x, y){
	var map=this.getIface().getMap();
	var offset=map.getOffset(x - map.getWidth()/2, y - map.getHeight()/2);
	var coords=map.getCoordinates();

	var cx=coords.x+offset.x;
	var cy=coords.y-offset.y;

	var d=map.getOffset(7, 7);
	var points=map.findPoints({
		x1: cx - d.x,
		y1: cy - d.y,
		x2: cx + d.x,
		y2: cy + d.y
	});

	if(points.length)
		this.drawPoints(points);
	else
		this.clearPointInfo();
};

/**
 * Find and draw points on the current map fragment
 */
MapToolbar.prototype.findPoints=function(){
	var map=this.getIface().getMap();
	var half_width=map.getWidth()/2, half_height=map.getHeight()/2;
	var offset=map.getOffset(half_width, half_height);
	var coords=map.getCoordinates();

	var points=map.findPoints({
		x1: coords.x - offset.x,
		y1: coords.y - offset.y,
		x2: coords.x + offset.x,
		y2: coords.y + offset.y
	});

	this.drawPoints(points);
};

/**
 * Show found points info
 * @param {Array} points
 * @param {Boolean} [force_draw] Permanently draw line, don't draw by events
 */
MapToolbar.prototype.drawPoints=function(points, force_draw){
	this.clearPointInfo();
	var cont=this.getPointInfoContainer();
	if(cont){
		var list_elem=document.createElement('ul');
		list_elem.className='points';
		var il=Math.min(points.length, 5);
		if(il){
			for(var i=0; i<il; i++){
				list_elem.appendChild(this._drawPoint(points[i]));
			}
			cont.appendChild(list_elem);

			var items=list_elem.getElementsByTagName('li');

			if(force_draw){
				for(var i=0, il=items.length; i<il; i++){
					this.drawLine(items[i]);
				}
			}
			else{
				var me=this;
				var f1=function(evt){me.drawLine(evt);};
				var f2=function(evt){me.clearLine(evt);};

				for(var i=0, il=items.length; i<il; i++){
					addEvent(items[i], 'mouseover', f1);
					addEvent(items[i], 'mouseout', f2);
				}
			}
			removeClass(cont, 'hidden');
		}
	}
};

/**
 * Draw line from point on map to point info
 * @param {Event, Element} elem
 */
MapToolbar.prototype.drawLine=function(elem){
	//find list item

	if(elem && !elem.getElementsByTagName){
		//пришло событие, нужно достать необходимый элемент
		evt=checkEvent(elem);
		elem=evt.target;
		do{
			if(elem.nodeType == 1 && elem.nodeName == 'LI'){
				break;
			}
		}while((elem=elem.parentNode));
	}

	if(elem){
		var map=this.getIface().getMap(), cv=this.getCanvas();
		var map_coords=map.getCoordinates();
		var point=map.getPointById(elem.id.substr(2));
		var hx=map.getWidth()/2;
		var hy=map.getHeight()/2;
		var point_from={x: 682, y: elem.offsetTop+elem.parentNode.offsetTop+8};
		var point_to=map.geoToPixels(point.x - map_coords.x, point.y - map_coords.y);
		point_to.x+=hx+10;
		point_to.y=hy-point_to.y+10;

		removeClass(cv.getContainer(), 'hidden');
		cv.drawLine(point_from, point_to, {color:'#999999', width:1});
		point_from.y+=1;
		point_to.y+=1;
		cv.drawLine(point_from, point_to, {color:'#ffffff', width:0.5});
	}
};

MapToolbar.prototype.clearLine=function(){
	var cv=this.getCanvas();
	cv.clear();
	addClass(cv.getContainer(), 'hidden');
};

MapToolbar.prototype._drawPoint=function(point){
	var point_elem=document.createElement('li');
	point_elem.className=point.type;
	point_elem.id='id'+point.id;
	var point_name=document.createElement('h3');
	point_name.appendChild(document.createTextNode(point.description));
	point_elem.appendChild(point_name);

	var fuels_elem=document.createElement('div');
	fuels_elem.className='fuel';
	fuels_elem.appendChild(document.createTextNode(point.fuel.join(', ')));
	point_elem.appendChild(fuels_elem);

	var services_elem=document.createElement('div');
	services_elem.className='service';
	services_elem.appendChild(document.createTextNode(point.service.join(', ')));
	point_elem.appendChild(services_elem);

	return point_elem;
};

/**
 * Map extension for work with URLs (save/load)
 * Available paramters
 * - x: map center x-coordinate;
 * - y: map center y-coordinate;
 * - zoom: map zoom;
 * - px: pointer bubble x-coordinate;
 * - py: pointer bubble y-coordinate;
 * - text: pointer bubble text
 * @constructor
 * @extends {MapExtension}
 */
function MapUrl(){
	MapUrl.baseConstructor.call(this);

	this.features=['centerX', 'centerY', 'zoom'];

	this.container=document.getElementById('map-href');
	this.bubble=document.getElementById('map-href-info');

	var me=this;
	addEvent(this.container.getElementsByTagName('span')[0], 'click', function(){
		if(matchClass(me.container, 'opened')){
			removeClass(me.container, 'opened');
		}
		else{
			me.drawUrl();
			addClass(me.container, 'opened');
		}

	});

};

MapUrl.inheritFrom(MapExtension);

/**
 * Revert map state from passed query string (usually 'window.location.search')
 * @param {String} str Query string
 */
MapUrl.prototype.revert=function(str){
	if(str.charAt(0) == '?'){
		str=str.substr(1);
	}

	var iface=this.getIface();
	var map=iface.getMap();

	//get features param names
	var f_names=[];
	for(var i=0; i<this.features.length; i++){
		f_names.push(map.getFeature(this.features[i]));
	}

	//parse string

	var _s=str.split('&'), _m, params={};
	for(var i=0; i<_s.length; i++){
		_m=_s[i].split('=');
		params[_m[0]]=unescape1251(_m[1]);
	}

	//set map provider params
	for(var i=0; i<f_names.length; i++){
		if(params[f_names[i]]){
			map.setParam(f_names[i], parseFloat(params[f_names[i]]));
		}
	}

	//explicitly set zoom to load proper map
	if(params[map.getFeature('zoom')]){
		//alert(map.getZoomIndexByValue(params[map.getFeature('zoom')]) + ' - ' +params[map.getFeature('zoom')]);
		map.setZoomLevel(map.getZoomIndexByValue(params[map.getFeature('zoom')]));
	}

	//show bubble
	if(params.px && params.py && params.text){
		var bubble_ext=iface.findExtensionByConstructor(MapSearchBubble);
		var _px=parseFloat(params.px);
		var _py=parseFloat(params.py);
		if(bubble_ext && !isNaN(_px) && !isNaN(_py)){
			bubble_ext.setCoordinates(_px, _py);
			bubble_ext.setContent(params.text);
			bubble_ext.show();
		}
	}
};

/**
 * revert map state from URL
 */
MapUrl.prototype.revertFromUrl=function(){
	if(window.location.search){
		this.revert(window.location.search);
	}
};

/**
 * Returns current map fragment's url
 * @return {String}
 */
MapUrl.prototype.get=function(){
	var iface=this.getIface();
	var map=iface.getMap();

	var params=[], f;
	for(var i=0; i<this.features.length; i++){
		f=map.getFeature(this.features[i]);
		params.push(f+'='+escape(map.getParam(f)));
	}

	var bubble_ext=iface.findExtensionByConstructor(MapSearchBubble);
	if(bubble_ext && bubble_ext.isVisible()){
		var _coords=bubble_ext.getCoordinates();
		params.push('px='+_coords.x);
		params.push('py='+_coords.y);
		params.push('text='+escape_win1251(bubble_ext.getContent()));
	}

	var url='http://'+window.location.host+window.location.pathname+'?'+params.join('&');
	return url;
};

MapUrl.prototype.drawUrl=function(){
	var url=this.get();
	var _url = url, substrings = [];
	var chunk_len = 40;
	while(_url.length > chunk_len){
		substrings.push(_url.substr(0, chunk_len).replace('&', '&amp;'));
		_url=_url.substr(chunk_len);
	}

	substrings.push(_url);
	this.bubble.innerHTML='<a href="'+url+'">'+substrings.join('<wbr />')+'</a>';

};