/*!
 * Regio MapCat Components
 * Copyright Regio AS
 * http://mapcat.regio.ee
 */ 

/*! 
 * Build: Mon Jun 30 15:09:42 EEST 2008
 * api/Namespace.js
 * api/Listener.js (ver 1.0)
 * api/Listeners.js (ver 1.4.2)
 * api/Log.js
 * api/Component.js (ver 2.8.2)
 * components/controls/controls.js
 * components/other/utils.js
 * components/features/features.renderer.js
 * components/controls/digitizing.js (ver 1.1)
 * components/other/collection.js (ver 1.6)
 * components/other/listView.js (ver 1.3)
 * components/controls/featureList.js (ver 1.25)
 * components/other/treeView2.js (ver 2.3)
 * components/controls/layersTree2.js (ver 3.1)
 * components/features/feature.js (ver 1.2)
 * components/other/lonlat.js
 * components/features/layer.js (ver 1.1)
 * components/features/layersGroup.js (ver 1.0)
 * components/controls/layersTree2.adapter.ft.js (ver 1.0)
 * components/features/features.renderer.ft.js (ver 1.1)
 * components/features/features.renderer.openlayers.js (ver 1.0)
 * components/gcadmin/gcAdmin.js (ver 0.9)
 * components/gcadmin/gcAdmin.adapter.ft.js
 * components/other/coords.js
 * components/search/searchList.js (ver 1.14)
 * components/search/searchList.ext.js
 * components/gcadmin/gcAdmin.adapter.search.js
 * components/gcadmin/gcAdmin.rgc.js (ver 1.4)
 * components/jquery/colorDialog.js
 * components/jquery/colorPicker.js (ver 1.5)
 * components/jquery/iconsList.js (ver 1.0)
 * components/jquery/rangeSlider.js
 * components/other/autocomplete.js (ver 1.2)
 * components/other/cookies.js (ver 09.04.08)
 * components/other/coords2.js
 * components/other/debug.js (ver 2.4.5)
 * components/other/delayedExecutor.js (ver 1.0)
 * components/other/treeview.js (ver 20.11.07)
 * components/other/layersTree.js (ver 1.66)
 * components/other/preview.2.js (ver 2.1)
 * components/other/symbolList.js (ver 1.0)
 * components/route/route.js
 * components/route/route.provider.delfi.js (ver 1.3)
 * components/route/route.renderer.ft.js
 * components/route/route.renderer.gmap.js
 * components/search/search.provider.jgc.js (ver 2.71)
 * components/search/search.provider.multi.js (ver 1.0)
 * components/state/layer.statefetcher.js (ver 1.0)
 * components/state/layersTree.statefetcher.js (ver 1.1)
 * components/state/routing.statefetcher.js (ver 1.1)
 * components/state/search.statefetcher.js (ver 1.22)
 * components/state/stateList.js (ver 1.1)
 * components/state/stateList.serverConnection.js (ver 1.0)
 */
if (typeof(Regio) == "undefined") {
	Regio = {};
}



/**
 * Component: MapCat.Namespace
 * MapCat FlashTile API Namespace
 * 
 * Author: Alexandr Smirnov (alex@regio.ee)
 * 
 * Group: MapCat FlashTile API
 * 
*/

MapCat = { // namespace
	Version: '2.x'
}




/**
 * Component: MapCat.Listener
 * Listener descriptor object. Basically contains: callback, contest and set of functions to manipulate (ie. remove, call)
 * 
 * Group: MapCat FlashTile API
 * 
 * Version: 1.0
 * 
 * Requires: MapCat.Namespace
 * 
 * Author:
 * Alexandr Smirnov (alex@regio.ee)
 */
MapCat.Listener = function(listeners, callback, contest) {
	this.callback = callback;
	this.contest = contest;
	
	this.call = function(/*args for callback*/) {
		return callback.apply(contest, arguments);
	}
	
	this.remove = function() {
		listeners.removeByFunction(callback);
	}
}


/**
 * Component: MapCat.Listeners
 * Class implementing publish/subscribe pattern.
 * 
 * Group: MapCat FlashTile API
 * 
 * Version: 1.4.2
 * 
 * Requires: MapCat.Namespace, MapCat.Listener
 * 
 * Author:
 * Alexandr Smirnov (alex@regio.ee)
 */
MapCat.Listeners = function() {
	this.list = {};
}

MapCat.Listeners.prototype = {
	/**
	 * Method: add
	 * Add new listener to array.
	 * Parameters:
	 * eventName - String, Name of event that this listener is interested in
	 * callback - Function
	 * contest - "this"
	 * Returns: 
	 * This MapCat.Listeners object again. So, it could be used as follows: listeners.add(xxx).add(xxx)...
	 */
	add: function(eventName, callback, contest) {
		this.addEx.apply(this, arguments);
		return this;
	},
	/**
	 * Method: addEx
	 * Same as <add> but returns <MapCat.Listener>.
	 * Parameters:
	 * eventName - String, Name of event that this listener is interested in
	 * callback - Function
	 * contest - "this"
	 * Returns: 
	 * <MapCat.Listener>
	 */
	addEx: function(eventName, callback, contest) { // ver 1.4
		var me = this;
		
		var rec = new MapCat.Listener(this, callback, contest)
		
		if (this.list[eventName]) {
			this.list[eventName].push(rec);
		} else {
			this.list[eventName] = [rec];
		};
		
		return rec;
	},
	/**
	 * Method: removeByFunction
	 * Remove event listener by function pointer.
	 * 
	 * Parameters:
	 * func - Function, pointer to callback function defined in <add> or <addEx>
	 * 
	 * Returns: 
	 * This MapCat.Listeners object again.
	 */
	removeByFunction: function(func) { // ver 1.4
		for(var a in this.list) {
			var arr = this.list[a];
			for(var i=arr.length-1; i>=0; i--) {
				var rec = arr[i];
				if(rec.callback == func) {
					arr.splice(i, 1);
				}
			}
			if(arr.length == 0) {
				delete this.list[a];
			}
		}
		return this;
	},
	/**
	 * Method: broadcast
	 * 
	 * Call all listeners that are interested in this event. Function can have any number of arguments. 
	 * All of them, except first (eventName) will be transfered to callback function. 
	 * 
	 * Parameters:
	 * eventName - String, Name of event that this listener is interested in
	 * ... - set of parameters to be passed to event handler
	 * 
	 * Returns:
	 * Result from last event handler that returned something different from "undefined"
	 */
	broadcast: function() {
		var args = [undefined];
		for(var i=0; i<arguments.length; i++) {
			args.push(arguments[i]);
		}
		return this.broadcastEx.apply(this, args);
	},
	/**
	 * Method: broadcastEx
	 * Same as <broadcast> but has additional (first) parameter "filter".
	 * 
	 * Parameters:
	 * filter - Function, that accepts one argument <MapCat.Listener>. Should return true if this Listener should be processes for this broadcast or false if not. If argument is ommited, then function acts exzatly like regular <broadcast>.
	 * eventName - String, Name of event that this listener is interested in
	 * ... - set of parameters to be passed to event handler
	 * 
	 * Returns:
	 * Result from last event handler that returned something different from "undefined"
	 */
	broadcastEx: function(filter, eventName /*, params*/) {
		var result, l, args;
		
		function processQueue(l, args) {
			for(var i=0;i<l.length;i++) {
				var rec = l[i];
				if (filter && !filter(rec)) {
					continue;
				}
				var r = rec.call.apply(rec, args);
				if (typeof(r) != "undefined" && (r != null)) {
					result = r;
				}
			}
		}
		
		// with arguments without first (filter)
		args = [];
		for(var i=1;i<arguments.length;i++) {
			args.push(arguments[i]);
		}
		
		if(l = this.list["*"]) {
			processQueue(l, args);
		}
		
		// remove eventName argument from arguments list
		args.shift();
		
		if(l = this.list[eventName]) {
			processQueue(l, args);
		}
		
		return result;
	},
	/**
	 * Method: broadcast
	 * 
	 * Same as <broadcast>
	 */
	call: function() { // ver 1.4
		return this.broadcast.apply(this, arguments);
	}
}



/**
 * Component: MapCat.Log
 * Provides basic routines for logging (debugging). Uses FireBug console if available. 
 * If not available, passes strings into window.status. 
 * 
 * *NB! In non-debug builds, this functions will do nothing*
 * 
 * Requires: MapCat.Namespace
 * 
 * Group: MapCat FlashTile API
 */
MapCat.Log = {
	/**
	 * Function: MapCat.Log.add
	 * Static function. Add string to the log.
	 * 
	 * Parameters:
	 * str - string
	 */
	add: function(str) {
		
	}
}


/**
 * Component: MapCat.Component
 * Creates and initializises MapCat FlashTile component.
 * 
 * Group: MapCat FlashTile API
 * 
 * Version: 2.8.2
 * 
 * Requires: MapCat.Namespace, MapCat.Listeners, MapCat.Log
 * 
 * Parameters:
 * divid - Maybe string (id of div) or actual div object itself where to put Flash embed HTML
 * cfg - Configuration
 * listeners - optional, Specifies MapCat.Listeners object to be connected to component (if not specified, object will be created internally)
 * 
 * Author: 
 * Alexandr Smirnov (alex@regio.ee)
 * 
 * Usage: 
 * |var mapcat = new MapCat.Component('map', {
 * |    src: "mapcat/map.swf",
 * |    vars: "confUrl=mapcat/conf.xml%3F&bgcolor=0x738aa0&cur_map=0&printing=0"
 * |}); 
 */
MapCat.Component = function(){
    if (typeof(cfg) == 'string') {
        var result = this.initializationAttach.apply(this, arguments);
    }
    else {
        var result = this.initialization.apply(this, arguments);
    }
    
    this.stack = [];
    this.addCallback("mapcat.loaded", this._flashLoadedEvent, this);
    
    return result;
}

// from TagHTML.js
MapCat._getTagHTML = function(cfg) {
	cfg.id = cfg.id ? cfg.id : "";
	cfg.vars = cfg.vars ? cfg.vars : "";
	cfg.src = cfg.src ? cfg.src: "map.swf";
	cfg.width = typeof(cfg.width) == "undefined" ? "100%" : cfg.width;
	cfg.height = typeof(cfg.height) == "undefined" ? "100%" : cfg.height;
	cfg.wmode = typeof(cfg.wmode) == "undefined" ? "window" : cfg.wmode;
	cfg.bgcolor = typeof(cfg.bgcolor) == "undefined" ? "738aa0" : cfg.bgcolor;
	cfg.scale = typeof(cfg.scale) == "undefined" ? "noscale" : cfg.scale;
	cfg.quality = typeof(cfg.quality) == "undefined" ? "high" : cfg.quality;
	with(cfg) {
		var html =
			'<object codebase="http://fpdownload.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=8,0,0,0" '+
				'width="'+width+'" height="'+height+'"'+
				'classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000" id="'+cfg.id+'">'+
			'<param name="quality" value="'+quality+'" /> '+
			'<param name="scale" value="'+scale+'" /> '+
			'<param name="allowScriptAccess" value="always" /> '+
			'<param name="bgcolor" value="'+bgcolor+'" /> '+
			'<param name="flashvars" value="jsApiID='+apiID+'&'+vars+'" /> '+
			'<param name="movie" value="'+src+'" /> '+
			'<param name="wmode" value="'+wmode+'" /> '+
			'<embed quality="'+quality+'" scale="'+scale+'" width="'+width+'" height="'+height+'"'+
				'allowscriptaccess="always" bgcolor="'+bgcolor+'" '+
				'flashvars="jsApiID='+apiID+'&'+vars+'" '+
				'pluginspage="http://www.macromedia.com/go/getflashplayer" '+
				'src="'+src+'" wmode="'+wmode+'" type="application/x-shockwave-flash" id="'+cfg.id+'" >'+
			'</embed>'+
			'</object>';
	}
	return html;
}

// from APIList.js
MapCat._apiList = {}; // APIs list

MapCat._apiList.register = function (api) {
	MapCat._apiList[api.id] = api;
}

// from FlashGateway.js
MapCat._flashGateway = function(apiID, func, args) {
	if (MapCat && MapCat._apiList) {
		var api = MapCat._apiList[apiID];
		if (api) {
			return api[func].apply(api, args);
		}
	}
	return null;
}

MapCat.Component.prototype = {
    initialization: function(divid, cfg, listeners /*optional*/){
        this.id = "mapcat_api_" + Math.random().toString().split(".")[1];
        cfg.apiID = this.id;
        if (!cfg.id) 
            cfg.id = this.id; // IE requires that flash embeds must have some ID for ExternalInterface to work properly
        MapCat._apiList.register(this);
        
		/**
		 * Property: listeners
		 * Refers to <MapCat.Listeners> class of component
		 */
        if (!listeners) {
            this.listeners = new MapCat.Listeners();
        }
        else {
            this.listeners = listeners;
        }
        
        var flashListener = this.listeners.addEx("*", function(){
            return this._callFlash.apply(this, arguments);
        }, this);
        
        // used in _callLocal
        this._flashListenerFilter = function(listener) {
    		return (listener != flashListener);
		}
        
        var div = false;
        if (typeof divid == "string") {
            div = document.getElementById(divid);
        }
        else {
            div = divid;
        }
        this.div = div;
        this.cfg = cfg;
        
        // if Safari or wmode is opaque or transparent
        if(navigator.userAgent.match(/safari/i) || (cfg.wmode || '').match(/opaque|transparent/i)) {
        	this.initMouseWheel();
        }
        
        return this.restart();
    },
    initMouseWheel: function() { // ver 2.7.5
		var me = this;
		
		// http://javascript.about.com/library/bldom21.htm
		function addEvent(el, eType, fn, uC) {
			if (el.addEventListener) {
				el.addEventListener(eType, fn, uC);
				return true;
			} else if (el.attachEvent) {
				return el.attachEvent('on' + eType, fn);
			} else {
				el['on' + eType] = fn;
			}
		}
		
		// http://webdev.org.ua/node/424
		function cancelEvent(e) {
			e = e ? e : window.event;
			if (e.stopPropagation) {
				e.stopPropagation();
			}
			if(e.preventDefault) {
				e.preventDefault();
			}
			e.cancelBubble = true;
			e.cancel = true;
			e.returnValue = false;
			
			return false;
		}
		
		function wheelCallback(e) {
			e = e ? e : window.event;
			var wheelData = (e.detail ? e.detail * -1 : e.wheelDelta / 40) / 3;
			var x = typeof e.offsetX != "undefined" ? e.offsetX : e.layerX;
			var y = typeof e.offsetY != "undefined" ? e.offsetY : e.layerY;
			
			me.ready(function() {
				me.broadcast('map.zoom', { x: x, y: y, delta: wheelData });
			});
			
			return cancelEvent(e);
		}
		
		addEvent(this.div, "DOMMouseScroll", wheelCallback, false); // FF
		addEvent(this.div, "mousewheel", wheelCallback, false); // all other
    },
    /*
     Method: restart
     Available from version 2.4.
     Clear HTML and re-initialize component. Also clears "flashLoaded" flag, so that 
     ready function will wait for "loaded" event from FlashTile again. This function 
     needs to be called any time flash component reinitializes for some reason (especially usefull 
     for Firefox browser, as it reloads Flash embeds any time you change its "position" or "display" style)
     */
    restart: function(){ // ver 2.4
        this.flashLoaded = false;
        
        var div = this.div, cfg = this.cfg;
        var result = false;
        
        if (div) {
            div.innerHTML = MapCat._getTagHTML(cfg);
            //this.flash = div.firstChild;
            //result = true;
            
            var embeds = div.getElementsByTagName("embed");
            // for ie, there are no embeds
            if (!embeds || embeds.length == 0) {
                embeds = div.getElementsByTagName("object");
            }
            result = embeds.length > 0;
            if (result) {
                this.flash = embeds[0];
            }
        }
        else {
            result = false;
        }
        
        return result;
    },
    /**
     * Method: addCallback
     * Add observer (subscriber) of event. Shortcut to listeners.add.
     * 
     * See:
     * <MapCat.Listeners.add>
     */
    addCallback: function(eventName, callback, me){
        this.listeners.add(eventName, callback, me);
    },
    /**
     * Method: broadcast
     * Broadcast (publish) some event to listeners. NB: This is asynchroneous method, which means that 
     * it MAY not neccessary execute immediately (currently it does, but in feature versions it will NOT).
     * 
     * See:
     * <MapCat.Listeners.broadcast>
     */
    broadcast: function(){
        this.call.apply(this, arguments);
    },
    /**
     * Method: call
     * Same as broadcast, but will return result from callbacks. Synchroneous, executes immediately and 
     * does not return until all handlers will process event.
     * 
	 * Returns:
	 * Result from last event handler that returned something different from "undefined"
     * 
     * See: 
     * <broadcast>
     */
    call: function(){
		var res = this.listeners.broadcast.apply(this.listeners, arguments);
		return res;
    },
    /**
     * Execute func only if (or only after) flash completely loaded and inited. NB: "this" will refer to function itself.
     * @param {Object} func
     * @method
     */
    ready: function(func){
        if (this.flashLoaded) {
            func.apply(func);
        }
        else {
            this.stack.push(func);
        }
    },
    _flashListener: undefined,
    _flashListener: undefined, // defined in init
    _callLocal: function() {
    	// call all listeners except OUR/CURRENT FlashTile
		var args = [this._flashListenerFilter];
		for(var i=0; i<arguments.length; i++) {
			args.push(arguments[i]);
		}
		return this.listeners.broadcastEx.apply(this.listeners, args);
    },
    _callFlash: function(){
        if (this.flash && this.flash._javascriptGateway) {
            // for some reason, flash interface do not understand arguments in it original contest,
            // recreate it as Array
            var args = [];
            for (var i = 0; i < arguments.length; i++) {
                args.push(arguments[i]);
            }
            
            var flashresult = null;
            try {
                flashresult = this.flash._javascriptGateway(this.id, "callLocal", args);
            } 
            catch (e) {
                MapCat.Log.add('Exception while calling Flash callback: ' + e, MapCat.Log.ERROR);
            }
            return flashresult;
        }
        return null;
    },
    _flashLoadedEvent: function(){
        this.flashLoaded = true;
        while (this.stack.length > 0) {
            var f = this.stack.shift();
            f.apply(f);
        }
    }
}




/**
 * Component: Regio.Controls
 * Regio.Controls namespace
 * 
 * Group: Controls
 * 
 * Author: 
 * Alexandr Smirnov (alex@regio.ee)
 */
 
 Regio.Controls = {};
 


/**
 * Component: Regio.Utils
 * Some everywhere used functions.
 * 
 * Author: Alexandr Smirnov (alex@regio.ee)
 */

Regio.Utils = {};

/**
 * Function: Regio.Utils.format
 * Formats string according to template. Can work in two ways:
 * - Regio.Utils.format('{0}_{1}_{2}', 1, 2, 3) will return string "1_2_3"
 * - Regio.Utils.format('{a}_{b}_{c}', { a: 1, b: 2, c: 3 }) will return string "1_2_3"
 * 
 * Parameters:
 * str - format string
 * obj OR ... - either object ({objectKey1}...{objectKeyN} will be substituted) 
 * or any number of parameters ({0}..{arguments count-2} will be substituted)
 */
Regio.Utils.format = function(str, obj) {
	if (typeof(obj) == "object") {
		for(var i in obj) {
			var re = new RegExp('\\{' + (i) + '\\}','gmi');
			str = str.replace(re, obj[i]);
		}
	} else {
		for(var i=1;i<arguments.length;i++) {
			var re = new RegExp('\\{' + (i-1) + '\\}','gmi');
			str = str.replace(re, arguments[i]);
		}
	}	
	return str;
}

/**
 * Function: Regio.Utils.removeSubstr
 * Removes all substring from specified string
 * 
 * Parameters:
 * str - string to remove substrings from
 * ... - strings, any number of arguments containg substrings to remove
 */
Regio.Utils.removeSubstr = function(str) {
	// TODO: make replace function and use it here
	for (var i=1;i<arguments.length;i++) {
		var re = new RegExp(arguments[i], 'g');
		str = str.replace(re, '');
	}
	
	return str;
}

/**
 * Function: Regio.Utils.initTemplate
 * Take inner contents of div and return it formatted accroding to specifyed *type*
 * 
 * Parameters:
 * div - DOM element (or element id), whose contents should be taken
 * type - if ="html" then div.innerHTML will be returned unmodifyed. If ="object", then eval(div.innerHTML) will be returned.
 * 
 * Throws:
 * - Error if specified div does not exists or is not DOM element.
 * - Error if type="object" and eval failed with exception.
 */
Regio.Utils.initTemplate = function(div, type) {
	div = Regio.Utils.getElement(div);
	if (!div) {
		throw new Error('Regio.Utils.initTemplate, incorrect div');
	}
	
	var s = Regio.Utils.removeSubstr(new String(div.innerHTML), '<!--', '-->', ' *\n *', ' *\r *', '\t');
	Regio.Utils.empty(div);
	
	if (type == "html") {
		// treat it as html/string template
		return s;
	} else {
		// default type == "object"
		// try it as object
		if (s == '') {
			return {};
		}
		try {
			return eval('('+s+')');
		} catch(err) {
			var msg = (err.description ? err.description : err);
			throw new Error('Regio.Utils.initTemplate('+(div.id ? div.id : div.name)+') failed with message: '+msg);
		}
	}
}

Regio.Utils.empty = function(div) {
	while(div.childNodes.length > 0) {
		div.removeChild(div.childNodes[0]);
	}
	return div;
}

/**
 * Function: Regio.Utils.applyDefaults
 * Apply default values for given object. If key exists in second specified object (values) but does not exists in first specified object (cfg) then add that key/value pair to the first object.
 * 
 * Parameters:
 * cfg - object to be modifyed
 * values - objects, containing default values
 */
Regio.Utils.applyDefaults = function(cfg, values) {
	for (var key in values) {
		if (cfg[key] == null) {
			cfg[key] = values[key];
		}
	}
	return cfg;
}

/**
 * Function: Regio.Utils.getElement
 * If provided div is string, then this function will return DOM element with ID=div, else this function will return provided div untouched
 * 
 * Parameter:
 * div - DOM element OR string (div ID)
 */
Regio.Utils.getElement = function(div) {
	if (typeof(div) == "string") {
		div = document.getElementById(div);
	}
	return div;
}

/**
 * Function: Regio.Utils.isArray
 * Return true if _obj_ is array
 * 
 * Parameters:
 * obj - object to be tested
 */
Regio.Utils.isArray = function(obj) {
   return ((typeof(obj) == "object") && (obj.constructor == Array));
}

/**
 * Function: Regio.Utils.getElementsByClassName
 * Return all childs of _element_ with class name=clsName.
 * 
 * Parameters:
 * element - parent element which childs to search
 * clsName - class name to search for
 * 
 * Returns:
 * Array of all element childs with specified class name
 */
Regio.Utils.getElementsByClassName = function(element, clsName){
	// http://www.netlobo.com/javascript_getelementsbyclassname.html
	
    var retVal = new Array();
    var elements = element.getElementsByTagName("*");
    for(var i = 0;i < elements.length;i++){
        if(elements[i].className.indexOf(" ") >= 0){
            var classes = elements[i].className.split(" ");
            for(var j = 0;j < classes.length;j++){
                if(classes[j] == clsName)
                    retVal.push(elements[i]);
            }
        }
        else if(elements[i].className == clsName)
            retVal.push(elements[i]);
    }
    return retVal;
}

Regio.Utils.createUniqueId = function(prefix) {
	return prefix + Math.random().toString().split(".")[1];
}



/**
 * Component: Regio.FeaturesRenderer
 * Abstract features renderer and Regio.FeaturesRenderer namespace
 * 
 * Group: Features
 * 
 * Requires: Regio.Utils
 * 
 * Author: 
 * Alexandr Smirnov (alex@regio.ee)
 */

Regio.FeaturesRenderer = function() {
	this.listeners = new MapCat.Listeners();
	this.layers = {};
}

Regio.FeaturesRenderer.prototype = {
	createLayer: function(layer) {
		this.listeners.broadcast("layercreated", layer);
	},
	registerLayer: function(layer) {
		var old = this.layers[layer.id];
		if (old) old.remove();
		
		this.layers[layer.id] = layer;
	},
	removeLayer: function(layer) {
		delete this.layers[layer.id];
		this.listeners.broadcast("layerremoved", layer);
	},
	setEditingFeature: function(feature) {
	},
	stopEditing: function() {
		this.listeners.broadcast("endEditingFeature", this.getEditingFeature());
	},
	getEditingFeature: function() {
	}
}


/**
 * Component: Regio.Controls.Digitizing
 * Binds digitizing elements in HTML template to specific actions.
 * 
 * Group: Controls
 * 
 * Requires: Regio.Controls, Regio.Utils, Regio.FeaturesRenderer, Regio.Controls, MapCat.Listeners, jQuery
 * 
 * Version: 1.1
 * 
 * Author: 
 * Alexandr Smirnov (alex@regio.ee)
 */



Regio.Controls.Digitizing = {};

(function() {

/*
 Constructor: Regio.Controls.Digitizing.createFromMarkup
 Create new Regio.Controls.Digitizing controller.
 
 Parameters:
 parent - DOM Element containing all digitizing controller elements (could be "document" for global scope)
 layer - Regio.Layer which will be used for digitizing.
 
 Uses following CSS selectors:

 .only_visible_when_digitizing - element and all its contents will be hidden while nothing is being edited (digitizing stopped)
 .create_feature - clickable element that starts digitizing feature of specified type (see <digitizeFeature>). Must have attribute "feature" that specifies feature type. This element will also recieve class "active" when feature of its type is being edited/digitized.
 ie. <a href="#" class="create_feature" feature="point">Point</a>

 .only_visible_for_feature_page - element which is being showed only when editing/digitizing feature of specified (in *feature* attribute) type. Must contain attribute *feature* that specifies feature type to which this element applyes. This is uses for displaying element pages.

 .feature_style - element, that controls feature style (color, line width, opacity, etc. etc.). 
 Must contain following attributes: 
 *feature*=feature type, 
 *styleName*=style name that this element is changing (ie. strokeWidth). 
 Optionally can contain following attributes: 
 *styleValue*=style value that will be applied to the feature when this element is clicked, 
 *setToDefault*=true set elements value to default value (see <Regio.Feature.DefaultStyle>) if this attrubute is set, 
 then elements value will be set to default value. if this attribute 
 is apsent then elements value will be treated as default value for this feature type.
 Examples:
 (code)
<a href="#" class="feature_style" feature="point" styleName="pointIcon" styleValue="boulder">boulder</a>
<select class="feature_style" feature="point" styleName="pointTransparency" setToDefault="true">
	<option value="100">100%</option>
	<option value="75">75%</option>
	<option value="50">50%</option>
	<option value="25">25%</option>
	<option value="0">0%</option>
</select>
 (end code)

 .digitizing_stop - clickable element that stops digitizing/editing
 .digitizing_stop_and_restart - clickable element that stops digitizing and immediately restarts it with the same feature type (this will look like "save and start new feature")
 .digitizing_delete - clickable element that removes currently edited feature
 .digitizing_delete_and_restart - clickable element that removes current feature and immediately starts digitizing new feature with the same type
 .digitizing_remove_all_features - clickable element that clears all features from the layer
 .feature_title - input element which value will be filled with currently edited feature title. If this element value is changed by user (onChange event) than feature title will also be updated. Optionally can contain attribute *defaultTitle* that specifies title that will be set on feature creation.
 .default_feature_page - any element acting as configuration parameter that specifies feature type that should be displayed by default. Must contain attribute *feature* that specifies feature type.
 .active - some elements recieve this class when some action is in progress that directly relates to that element (see .create_feature, .feature_style)
*/
Regio.Controls.Digitizing.createFromMarkup = function(parent, layer) {
	var featureUserStyles = {}; // holds only styles that were passed with this.addFeatureStyle (can be overwritted by template styles)
	var me = this;
	var listeners = this.listeners = new MapCat.Listeners();
	var changingTemplate = false;
	var restarting = false;
	
	setEditingElements(false);
	
	fillTemplateWithDefaults();
	
	showDefaultFeaturePage();
	
	$('.only_visible_when_digitizing', parent).hide();
	
	layer.renderer.listeners
	.add("beginEditingFeature", function(feature) {
		$('.only_visible_when_digitizing', parent).show();
		
		initTitle();
		
		setEditingElements(true);
		
		showFeaturePage(feature.type);
		$('.create_feature[feature='+feature.type.toLowerCase()+']', parent).addClass("active");
		listeners.broadcast('beginEditingFeature', feature);
	})
	.add("endEditingFeature", function(feature) {
		if(!restarting) {
			$('.only_visible_when_digitizing', parent).hide();
			setEditingElements(false);
		}
		
		doneTitle();
		
		$('.create_feature', parent).removeClass("active");
		listeners.broadcast('endEditingFeature', feature);
		if(!feature.isInited()) {
			feature.remove();
		}
		
		if(!restarting) {
			showDefaultFeaturePage();
		}
	});
	
	$('.create_feature, .create_feature *', parent).click(function() {
		var type = $(this).attr('feature');
		if (type) {
			restarting = true;
			digitizeFeature(type);
			restarting = false;
			return false;
		}
	});
	$('.feature_style', parent).each(function() {
		var $e = $(this);
		$("click,change".split(',')).each(function() {
			$e.bind(this, function() {
				if (!changingTemplate) {
					var f = $e.attr("feature").toLowerCase(), v = $e.attr("styleValue"), n = $e.attr("styleName");
					if (v) {
						var o = {};	o[n] = v;
						me.addFeatureStyle(f, o);
					}
					me.updateFeature(f, $e[0]);
				}
				return false;
			});
		});
	});
	
	$('.digitizing_stop', parent).click(function() {
		var f = layer.renderer.getEditingFeature();
		if (f) {
			f.setEditable(false);
		}
		return false;
	});
	
	$('.digitizing_stop_and_restart', parent).click(function() {
		var f = layer.renderer.getEditingFeature();
		if (f) {
			restarting = true;
			f.setEditable(false);
			restarting = false;
			digitizeFeature(f.type);
		}
		return false;
	});
	
	$('.digitizing_delete', parent).click(function() {
		var f = layer.renderer.getEditingFeature();
		if (f) {
			f.remove();
		}
		return false;
	});
	
	$('.digitizing_delete_and_restart', parent).click(function() {
		var f = layer.renderer.getEditingFeature();
		if (f) {
			restarting = true;
			f.remove();
			restarting = false;
			digitizeFeature(f.type);
		}
		return false;
	});
	
	$('.digitizing_remove_all_features', parent).click(function() {
		layer.clear();
		return false;
	});
	
	// init complete
	
	$('.feature_title', parent).change(function() {
		var f = layer.renderer.getEditingFeature();
		if (f) {
			setFeatureTitle(f, $(this).val());
		}
	});
	
	function setFeatureTitle(f, t) {
		f.setTitle(t ? t : ($('.feature_title', parent).attr('defaultTitle') || "Unnamed"));
	}
	
	function initTitle() {
		var f = layer.renderer.getEditingFeature();
		if (f) {
			if (typeof f.title == "undefined") {
				setFeatureTitle(f);
			}
			
			$('.feature_title', parent).val(f.title);
		}
	}
	
	function doneTitle() {
		$('.feature_title', parent).val('');
	}
	
	function fillTemplateWithDefaults() {
		$('.feature_style[setToDefault]', parent).each(function() {
			var $this = $(this);
			var style = addAliaces(Regio.Feature.DefaultStyle);
			$this.val(style[$this.attr('styleName')]);
		});
	}
	
	function showDefaultFeaturePage() {
		$('.default_feature_page', parent).each(function() {
			var t = $(this).attr('feature');
			showFeaturePage(t);
			digitizeFeature(t);
		});
	}
	
	function showFeaturePage(type) {
		$('.only_visible_for_feature_page[feature!='+type.toLowerCase()+']', parent).hide();
		$('.only_visible_for_feature_page[feature='+type.toLowerCase()+']', parent).show();
		notifyStyleChange(type, collectFeatureStyle(type));
	}
	
	function setEditingElements(enabled) {
		$('.digitizing_stop, .digitizing_delete, .digitizing_delete_and_restart, .digitizing_stop_and_restart, .feature_title', parent)[enabled ? "removeAttr" : "attr"]("disabled", "true")[enabled ? "removeClass" : "addClass"]("disabled");
	}
	
	function getCamelizedTypeName(type) {
		if (!type) return '';
		type = (''+type).toLowerCase();
		type = type.substr(0,1).toUpperCase() + type.substr(1); // camelize
		return type;
	}
	
	/*
	 Method: digitizeFeature
	 Start digitizing new feature.
	 
	 Parameters:
	 type - feature type (point, line, polygon, etc.)
	*/
	function digitizeFeature(type) {
		var methodName = 'create'+getCamelizedTypeName(type);
		
		if (layer[methodName]) {
			var feature = layer[methodName]("digitize", collectFeatureStyle(type));
			feature.listeners.add("click", function() {
				feature.setEditable(true);
			});
			feature.listeners.add("titlechanged", function() {
				if (feature == layer.renderer.getEditingFeature()) {
					$('.feature_title', parent).val(feature.title);
				}
			});
			return true;
		} else {
			return false;
		}
	}
	
	function collectFeatureStyle(type, priorityElem) {
		var style = Regio.Feature.DefaultStyle;
		if (featureUserStyles[type]) {
			style = jQuery.extend({}, style, featureUserStyles[type]);
		}
		
		$priorityElem = $(priorityElem);
		
		// move priorityElem to the end of list
		$('.feature_style[feature="'+type+'"]', parent).not($priorityElem).add($priorityElem).each(function(){
			var $elem = $(this);
			var s = $elem.attr("styleName");
			
			if (typeof this.value != "undefined") {
				var v = $elem.val();
				style[s] = v;
			}
		});
		return style;
	}
	
	function addAliaces(style) {
		// aliaces
		var style = jQuery.extend({}, style);
		for(var i in style) {
			// transparency is added only if it does not exists. opacity is overwritted by transparency.
			
			var p = i.indexOf("Opacity");
			if (p >= 0) {
				var v = 100 - style[i];
				if (v<0) v = 0;
				if (v>100) v = 100;
				var key = i.substring(0, p)+"Transparency";
				if (!style[key]) {
					style[key] = v;
				}
			}
			
			var p = i.indexOf("Transparency");
			if (p >= 0) {
				var v = 100 - style[i];
				if (v<0) v = 0;
				if (v>100) v = 100;
				var key = i.substring(0, p)+"Opacity";
				style[key] = v;
			}
		}
		return style;
	}
	
	// checks if style is different from previous notifyStyleChange, and if it is, 
	// then updates template to reflect selected and active elements
	function notifyStyleChange(type, style, excludeElems) {
		function removeEqual(a, b) {
			var r = true, cnt1=0, cnt2=0;
			for(var i in a) {
				if(a[i] == b[i]) {
					r = false;
					delete a[i];
					delete b[i];
				}
				cnt1++;
			}
			for(var i in b) {
				if(a[i] == b[i]) {
					r = false;
					delete a[i];
					delete b[i];
				}
				cnt2++;
			}
			if(r) r = cnt1 == cnt2;
			return r;
		}
		
		if (!arguments.callee.prev) {
			arguments.callee.prev = {};
		}
		
		$excludeElems = $(excludeElems);
		
		// enter critical section to avoid loop
		changingTemplate = true;
		
		var prev = arguments.callee.prev;
		var p = prev[type];
		if(!p) p = {};
		
		var featureElems = $('.feature_style[feature='+type+']').not($excludeElems);
		
		var styleChanges = jQuery.extend({}, style);
		var equal = removeEqual(p, styleChanges);
		
		if(!equal) {
			// remove old actives
			for(var i in p) {
				$(featureElems).filter('[styleName='+i+'][styleValue='+p[i]+']').removeClass('active');
			}
			// notify listeners
			listeners.broadcast("stylechange", type, style);
		}
		// set new
		for(var i in styleChanges) {
			var styleElems = $(featureElems).filter('[styleName='+i+']');
			$(styleElems).filter('[styleValue='+styleChanges[i]+']').addClass('active');
			$(styleElems).filter(':not([styleValue])').val(styleChanges[i]).change();
		}
		
		// leave critical section
		changingTemplate = false;
		
		
		arguments.callee.prev[type] = jQuery.extend({}, style);
	}
	
	/*
	 Method: updateFeature
	 Updates feature style of specified type.
	 
	 Parameters:
	 type - string, feature type
	*/
	updateFeature.updateOncePer = 250;
	
	function updateFeature(ftype, calledByElem) {
		// preiodical updater code (make function run not more that once per "updateOncePer" ms)
		var func = arguments.callee;
		var lastTime = func.last, curTime = Number(new Date());
		clearTimeout(func.timer);
		if (curTime - lastTime < func.updateOncePer) {
			var args = arguments;
			func.timer = setTimeout(function() { func.apply(this, [ftype]) }, func.updateOncePer/4);
			return;
		}
		arguments.callee.last = curTime;
		// --
		
		var f = layer.renderer.getEditingFeature();
		if (f) {
			if(f.type == ftype) {
				var s = collectFeatureStyle(ftype, calledByElem);
				var sWithAliaces = addAliaces(s);
				f.setStyle(sWithAliaces);
				f.draw();
				notifyStyleChange(ftype, s, calledByElem);
			}
		}
	}
	
	this.digitizeFeature = digitizeFeature;
	/*
	 Method: addFeatureStyle
	 Add style atributes for specified feature type. Has same effect as template elements do, but has lower priority (can be overwritten by template element)
	 
	 Parameters:
	 type - string, feature type
	 userStyle - object, specifies style attributes to be updated (ie. { strokeColor: '00FF00' })
	*/
	this.addFeatureStyle = function(ftype, userStyle) {
		featureUserStyles[ftype] = jQuery.extend({}, featureUserStyles[ftype], userStyle);
		updateFeature(ftype);
	}
	this.updateFeature = updateFeature;
}

})();


/**
 * Component: Regio.Collection
 * 
 * Requires: Regio.Utils
 * 
 * Version: 1.6
 *
 * Author: 
 * Alexandr Smirnov (Regio)
 */



/**
 * Constructor: Regio.Collection
 */
Regio.Collection = function(div, cfg) {
	cfg = cfg || {};
	div = Regio.Utils.getElement(div);
	
	var me = this;
	var data = [];
	
	Regio.Utils.applyDefaults(cfg, {
		rowTemplate: ''
	});
	
	clear();
	
	// end of init
	
	function addItem(obj) {
		// check if it is a first row added to the list
		if ((data.length == 0) && (me.beforeFirstRowAdded)) {
			me.beforeFirstRowAdded(obj);
		}
		
		// add row
		var childs = addRow(obj);
		
		if (me.onNewItem) {
			me.onNewItem(obj, childs);
		}
		
		data.push({ childs: childs, object: obj });
		
		return childs;
	}
	
	function addItems(arr) {
		for(var i=0; i<arr.length; i++) {
			addItem(arr[i]);
		}
	}
	
	function removeItem(obj) {
		for(var i=data.length-1; i>=0; i--) {
			if(data[i].object == obj) {
				with(data[i]) {
					if (me.onRemoveItem) {
						me.onRemoveItem(obj, childs);
					}
					for(var j=childs.length-1; j>=0; j--) {
						childs[j].parentNode.removeChild(childs[j]);
					}
					childs.length = 0;
				}
				data.splice(i, 1);
			}
		}
		// check if it was the last row?
		if ((data.length == 0) && (me.afterLastRowRemoved)) {
			me.afterLastRowRemoved(obj);
		}
	}
	
	function getItemChilds(obj) {
		for(var i=data.length-1; i>=0; i--) {
			if(data[i].object == obj) {
				return data[i].childs;
			}
		}
		return false;
	}
	
	function clear() {
		while(data.length > 0) {
			removeItem(data[data.length-1]);
		}
	}
	
	function addRow(obj) {
		var elem = document.createElement(div.tagName);
		
		// The innerHTML property of the TABLE, TFOOT, THEAD, and TR elements are read-only in IE. :(
		if(window.jQuery) {
			jQuery(elem).append(jQuery(Regio.Utils.format(cfg.rowTemplate, obj)));
		} else {
			elem.innerHTML = Regio.Utils.format(cfg.rowTemplate, obj);
		}
		
		if (me.beforeDrawRow)
			me.beforeDrawRow(obj, elem.childNodes);
		
		var childs = [];
		
		while(elem.childNodes.length > 0) {
			var c = elem.childNodes[0];
		    div.appendChild(c);
		    childs.push(c);
		}
		
		return childs;
	}
	
	function getCount() {
		return data.length;
	}
	
	this.addItem = addItem;
	this.addItems = addItems;
	this.removeItem = removeItem;
	this.getItemChilds = getItemChilds;
	this.clear = clear;
	this.count = getCount;
}


/**
 * Component: Regio.ListView
 * Formats data array using provided templates.
 * Supports filtering of data, etc.
 * 
 * Requires: Regio.Utils
 * 
 * Version: 1.3
 *
 * Author: 
 * Alexandr Smirnov (Regio)
 * 
 * Usage:
 * | list = Regio.ListView('list', {
 * |   rowTemplate: '<div>{a} {b}</div>'
 * | });
 * | 
 * | list.setData([
 * |   { a: 'first', b: 1 },
 * |   { a: 'second', b: 2 },
 * |   { a: 'third', b: 3 }
 * | ]);
 * | 		
 * | list.setFilter(function(arr) {
 * |   return arr.b > 2;
 * | });
 * This sample, will fill div (with id="list") with following HTML:
 * | <div>first 1</div>
 * | <div>second 2</div>
 * | <div>third 3</div>
 * And after that will apply filter (see <setFilter>) which will set div innerHTML to:
 * | <div>third 3</div>
 */



/**
 * Constructor: Regio.ListView
 * Create and initialize ListView component.
 * 
 * Parameters:
 * div - DOM element, where list items will be drawn
 * cfg - Configuration
 * 
 * Configuration:
 * rowTemplate - template of each row (should contain substitution tags like {aaa}, see <Regio.Utils.format>). Before drawing each row, component will take this template, fill it with information provided with <setData> function and then, will add resulting HTML to DOM (into provided div). Can be something like: "<div class="searchList_row">{text}</div>" (this will work, if <setData> will be called with something like setData([{text: 'aaa'}, {text: 'bbb'}])
 * filter - optional, function, filter that will be applyed be default (intially) (default: function() { return true })
 */
Regio.ListView = function(div, cfg) {
	cfg = cfg || {};
	div = Regio.Utils.getElement(div);
	
	var me = this;
	var data = [];
	var filterObj = { filter: function() { return true } }; // show all by deafult
	
	Regio.Utils.applyDefaults(cfg, {
		rowTemplate: ''
	});
	
	if (typeof(cfg.filter) != "undefined") {
		this.setFilter(cfg.filter);
	}
	
	clear();
	
	// end of init
	
	function clear() {
		if (me.beforeClear) me.beforeClear();
		Regio.Utils.empty(div);
	}
	
	function drawRow(row, i, cnt) {
		var elem = document.createElement(div.tagName);
		
		// The innerHTML property of the TABLE, TFOOT, THEAD, and TR elements are read-only in IE. :(
		if(window.jQuery) {
			jQuery(elem).append(jQuery(Regio.Utils.format(cfg.rowTemplate, row)));
		} else {
			elem.innerHTML = Regio.Utils.format(cfg.rowTemplate, row);
		}
		
		if (this.beforeDrawRow)
			this.beforeDrawRow(row, elem.childNodes, i, cnt);
		
		while(elem.childNodes.length > 0) {
			var c = elem.childNodes[0];
			if (this.beforeDrawRowChildNode)
				this.beforeDrawRowChildNode(c, div, row);
			
		    div.appendChild(c);
			
			if (this.afterDrawRowChildNode)
				this.afterDrawRowChildNode(c, div, row);
		}
	}
	
	function redraw() {
		clear();
		execute('current', drawRow, me);
	}
	
	/**
	 * Function: execute
	 * Take current ListView data, apply filter to it and do callback for all accepted rows.
	 * If filterObject == string 'current' then take current listView filter.
	 * This function does not change contents/data of ListView. 
	 * 
	 * Parameters:
	 * filterObject - filtering *object* (see <setFilter>)
	 * callback - callback for each resulting row data
	 * me - "this" for callback function
	 */
	function execute(filterObject, callback, me) {
		if (filterObject == 'current') {
			filterObject = filterObj;
		}
		
		if (filterObject.start) filterObject.start();
		
		var cnt = 0;
		for (var i=0; i<data.length; i++) {
			if (filterObject.filter ? filterObject.filter(data[i], cnt) : true) { // draw only allowed rows
				callback.call(me, data[i], i, cnt);
				cnt++;
			}
		}
		
		if (filterObject.finish) filterObject.finish();
	}
	
	// see execute()
	this.execute = execute; // public
	
	/**
	 * Function: setData
	 * Set data of listView. This data will be filtered, then merged with provided rowTemplate (see <Regio.ListView>) and then, resulting HTML will be applyed to DOM element div.
	 * 
	 * Parameter:
	 * arr - array of objects. For each line - one object. Keys from this object will be used for substitution into rowTemplate.
	 */
	this.setData = function(arr) { // public
		if (typeof(arr) != "object") {
			throw new Error('Regio.ListView.setData, arr is not array');
		}
		data = arr;
		redraw();
	}
	
	/**
	 * Function: getData
	 * Returns data, previously set by setData
	 */
	this.getData = function() {
		return data;
	}
	
	/**
	 * Function: clear
	 * Clears ListView data. Equal to setData([])
	 */
	this.clear = function() { // public
		this.setData([]);
	}
	
	/**
	 * Function: setFilter
	 * Sets filtering of data. Filtering can be implemented for displaying paged results for example.
	 * 
	 * Parameters:
	 * callback - *function* OR *object*. If *function* provided, then this function will be applyed for each row of listView data. And if this function will return TRUE, then this row will be displayed in div. If *object* is provided, then this object must contain "filter" method that will act same way. Also, this object can optionally contain "start" and "finish" methods that will be called before filtering start or after finltering finished.
	 */
	// can accept either function (to be treated as filter function) or
	// object (in this case object should contain "filter" callback/method
	// and optionally "start" and "finish" callbacks)
	this.setFilter = function(callback) { // public
		if (typeof(callback) == "function") {
			filterObj.filter = callback;
		} else if (typeof(callback) == "object") {
			if (!callback.filter) {
				throw new Error('ListView.setFilter callback seems to be filter object but doe not contain "filter" method');
			}
			filterObj = callback;
		}
		redraw();
	}
	
	/**
	 * Function: getFilter
	 * Returns currently active filter *object*. See <setFilter>
	 */
	this.getFilter = function() {
		return filterObj;
	}
	
	/**
	 * Function: refresh
	 * Clears DIV and redraws its contents.
	 */
	this.refresh = function() { // public
		redraw();
	}
	
	return this;
}


/**
 * Component: Regio.Controls.FeatureList
 * Displays list of features from FeatureRenderer.Layer. Can remove and edit features.
 * 
 * Group: Controls
 * 
 * Requires: Regio.Collection, Regio.Utils, Regio.FeaturesRenderer, Regio.Controls, MapCat.Listeners, Regio.ListView, jQuery
 * 
 * Version: 1.25
 * 
 * Author: 
 * Alexandr Smirnov (alex@regio.ee)
 */



if(1);

(function() {

/*
 Constructor: Regio.Controls.FeatureList
 Creates new feature list. Takes template from div that is passed to constructor.
 
 Paramteres:
 div - HTML Element this is where list will be created and *row template will be taken from!*
 layer - Regio.Layer which features to display
 cfg - onfiguration
 
 Configuration:
 none yet
 
 Uses following CSS selectors in *row template* (all optional):
 .edit_feature - clickable element that starts feature editing
 .remove_feature - clickable element that deletes feature
 .static_title - span/div/etc which innerHTML will be set to feature title
 .editable_title - input, which value will be set to feature title and which onChange will be set to update feature title
 .active - top element of row template will recieve this classname in case feature is being edited/digitized
*/
Regio.Controls.FeatureList = function(div, layer, cfg) {
	cfg = cfg || {};
	
	div = Regio.Utils.getElement(div);
	
	var list = new Regio.Collection(div, {
		rowTemplate: Regio.Utils.initTemplate(div, 'html')
	});
	
	layer.listeners.add("featurecreated", function(feature) {
		var nodes = list.addItem(feature);
		
		$('.edit_feature', nodes).click(function() {
			feature.setEditable(true);
			return false;
		});
		
		$('.remove_feature', nodes).click(function() {
			feature.remove();
			return false;
		});
		
		$('.hide_show_feature:checkbox', nodes).click(function(e) {
			if($(this).attr('checked')) {
				feature.show();
			} else {
				feature.hide();
			}
			e.stopPropagation();
		});
		
		$('.static_title', nodes).text(feature.title);
		$('.editable_title', nodes).bind("change", function() {
			feature.title = $(this).val();
			feature.listeners.broadcast("titlechanged");
		}).val(feature.title);
		
		feature.listeners.add("titlechanged", function() {
			$('.static_title', nodes).text(feature.title);
			$('.editable_title', nodes).val(feature.title);
		});
		
		/*feature.listeners.add("visibilitychanged", function(visible) {
			$('.hide_show_feature:checkbox', nodes)[visible ? "attr" : "removeAttr"]("checked", "true");
		});*/
	});
	
	layer.listeners.add("featureremoved", function(feature) {
		list.removeItem(feature);
	});
	
	layer.renderer.listeners
	.add("beginEditingFeature", function(feature) {
		var nodes = list.getItemChilds(feature);
		$(nodes).addClass('active');
		$('.main', nodes).addClass('active');
	})
	.add("endEditingFeature", function(feature) {
		var nodes = list.getItemChilds(feature);
		$(nodes).removeClass('active');
		$('.main', nodes).removeClass('active');
	});
	
	return list;
}

})();


/**
 * Component: Regio.TreeView2
 * TreeView component. Formats data hierarchially, according to provided template.
 * 
 * Version: 2.3
 * 
 * Requires: Regio.Utils
 * 
 * Author: 
 * Alexandr Smirnov (Regio)
 * 
 * Usage:
 * see test/treeView.html
 */



/**
 * Constructor: Regio.TreeView2
 * Creates new TreeView.
 * 
 * Paramters:
 * div - DOM element, where tree contents will be drawn
 * cfg - Configuration
 * 
 * Configuration:
 * treeTemplate - default:
 * | // schematic tree structure:
 * | '<div>'+ 						// record
 * | '	<div>{a} {b}</div>'+ 		// header
 * | '	<div class="childs">'+ 		// one element with class "childs" is required!
 * | //		again record or child here
 * | '		<div>{a} {b}</div>'+ 	// child template
 * | '	</div>'+					// end of child template
 * | '</div>'						// end of record template
 * treeStartsWithHeader - if true, then whole treeTemplate will be appended to initial "div". if false - then "div" will be treated as childs node (default false)
 * branchFirstItemIsHeaderDescriptor - default false
 */
Regio.TreeView2 = function(div, cfg) {
	div = Regio.Utils.getElement(div);
	
	var me = this;
	var data = {};
	var drawingFirstBranch = false;
	
	Regio.Utils.applyDefaults(cfg, {
		treeStartsWithHeader: false,		// if true, then whole treeTemplate will be appended to initial "div". if false - then "div" will be treated as childs node
		treeTemplate: 
			// schematic tree structure:
			'<div>'+ 						// record
			'	<div>{a} {b}</div>'+ 		// header
			'	<div class="childs">'+ 		// one element with class "childs" is required!
			//		again record or child here
			'		<div>{a} {b}</div>'+ 	// child template
			'	</div>'+					// end of child template
			'</div>',						// end of record template
		branchFirstItemIsHeaderDescriptor: false
	});
	
	parseTemplate();
	
	function parseTemplate() {
		var tmp = document.createElement('div');
		tmp.innerHTML = cfg.treeTemplate;
		
		var childsElems = Regio.Utils.getElementsByClassName(tmp, 'childs');
		if (!childsElems || !childsElems.length || (childsElems.length != 1)) {
			throw new Error('TreeView2, no or too many "childs" classes in treeTemplate');
		}
		
		cfg.childTemplate = (childsElems[0].innerHTML+'').replace('%7B', '{').replace('%7D', '}'); // ugly fix huh :(
		
		childsElems[0].innerHTML = '';
		
		cfg.recordTemplate = (tmp.innerHTML).replace('%7B', '{').replace('%7D', '}'); // ugly fix huh :(
		
		tmp = null;
	}
	
	function drawTree(arr) {
		div.innerHTML = "";
		drawingFirstBranch = true;
		drawBranch(div, arr, [], cfg);
	}
	
	function drawBranch(parentElement, arr, position, params) {
		var childsElem;
		
		if (!Regio.Utils.isArray(arr)) {
			throw new Error('TreeView2, branch must be an array!');
		}
		
		if ((position.length > 0) || (params && params.treeStartsWithHeader)) {
			var obj = me.getHeaderDescriptor(arr, position);
			
			if(cfg.branchFirstItemIsHeaderDescriptor) {
				arr = arr.slice(1);
			}
			
			childsElem = appendTemplateAndReturnChildsElem(parentElement, cfg.recordTemplate, obj, 'beforeDrawHeader', position);
		} else {
			// usually here for root level
			childsElem = parentElement;
		}
		
		for(var i=0; i<arr.length; i++) {
			var child = arr[i];
			if (Regio.Utils.isArray(child)) {
				// position - is array describing current branch position
				drawBranch(childsElem, child, position.concat([i]));
				// we have finished drawing branch, so next branches will not be first
				drawingFirstBranch = false;
			} else {
				drawNode(childsElem, child, position.concat([i]));
			}
		}
	}
	
	function drawNode(parentElement, obj, position) {
		appendTemplateAndReturnChildsElem(parentElement, cfg.childTemplate, obj, 'beforeDrawNode', position);
	}
	
	/**
	 * Function: beforeDrawHeader
	 * Can be overriden to apply additional formatting for DOM elements of particular TreeView header.
	 * 
	 * Parameters:
	 * arr - array describing current branch (see <setData>)
	 * childs - DOM elements of header (created from treeTemplate)
	 * firstBranch - boolean, true if currently formatting first branch
	 */
	/**
	 * Function: beforeDrawNode
	 * Can be overriden to apply additional formatting for DOM elements of particular TreeView node.
	 * 
	 * Parameters:
	 * arr - array describing current branch (see <setData>)
	 * childs - DOM elements of node (created from treeTemplate)
	 */
	function appendTemplateAndReturnChildsElem(parentElement, template, obj, callbackName, position) {
		// TODO: might need to separate in several subs
		
		template = Regio.Utils.format(template, obj);
		
		var tmp = document.createElement('div');
		tmp.innerHTML = template;
		var childsElems = Regio.Utils.getElementsByClassName(tmp, 'childs');
		
		if (me[callbackName]) {
			me[callbackName].call(me, obj, tmp.childNodes, drawingFirstBranch, position)
		}
		
		while (tmp.childNodes.length>0) {
			parentElement.appendChild(tmp.childNodes[0]);
		}
		
		tmp = null;
		
		return (childsElems && childsElems.length > 0) ? childsElems[0] : false;
	}
	
	/**
	 * Function: getHeaderDescriptor
	 * Can be overriden to provide data for tree branch headers.
	 * 
	 * Parameters:
	 * branchArr - array of child nodes for the branch
	 * pos - array describing current position in tree
	 */
	this.getHeaderDescriptor = function(branchArr, pos) { // can be overriden
		if (cfg.branchFirstItemIsHeaderDescriptor) {
			if (branchArr.length < 1) {
				throw new Error('TreeView2, in branch array, first item is describing header and must exists!');
			}
			return branchArr[0];
		} else {
			return {};
		}
	}
	
	/**
	 * Function: setData
	 * Sets TreeView data.
	 * 
	 * obj - array with TreeData. Should be formatted in following way:
	 * | tree.setData([
	 * |   { a: 'child 1.0', b: 123 },
	 * |   [
	 * |     { a: 'child 2.0', b: 321 },
	 * |     [
	 * |       { a: 'child 3.0', b: 321 },
	 * |       { a: 'child 3.1', b: 321 },
	 * |       { a: 'child 3.2', b: 321 }
	 * |     ],
	 * |     { a: 'child 2.1', b: 321 },
	 * |     { a: 'child 2.2', b: 321 }
	 * |   ]
	 * | ]);
	 */
	this.setData = function(obj) { // public
		drawTree(obj);
	}
	
	return this;
}



/**
 * Component: Regio.Controls.LayersTree
 * New layers menu component.
 * 
 * Requires: Regio.Utils, Regio.Controls, Regio.TreeView2, jQuery, jQuery.Sortable
 * 
 * Version: 3.1
 * 
 * Group: Controls
 * 
 * Author:
 * Alexandr Smirnov (alex@regio.ee)
 */

Regio.Controls.LayersTree = function(div, adapter, cfg) {
	div = Regio.Utils.getElement(div);
	cfg = cfg || {};
	var data = [];
	
	Regio.Utils.applyDefaults(cfg, {
		sortable: true,
		collapseAllButFirstByDefault: true,
		collapseClasses: ['collapser', 'expander']
	});
	
	var tree = new Regio.TreeView2(div, {
		treeTemplate: cfg.treeTemplate || Regio.Utils.initTemplate(div, 'html'),
		branchFirstItemIsHeaderDescriptor: true,
		treeStartsWithHeader: false
	});
	
	tree.beforeDrawHeader = function(arr, childs, firstBranch) {
		var $subs = $('.childs', childs);
		$('.collapse_branch', childs).click(function() {
			var closing = $subs.is(':visible');
			$subs[closing ? "slideUp" : "slideDown"](150);
			$(this)[closing ? "addClass" : "removeClass"](cfg.collapseClasses[1])[closing ? "removeClass" : "addClass"](cfg.collapseClasses[0]);
			return false;
		});
		if (!firstBranch && cfg.collapseAllButFirstByDefault) {
			$subs.hide();
		}
	}
	
	adapter.listeners.add("layercreated", function(layer) {
		registerLayer(layer);
	});
	
	function sortable(action) {
		if (cfg.sortable) {
			if (action == "destroy") {
				$('.childs', div).sortable("destroy");
				$(div).sortable("destroy");
			} else {
				$('.childs', div).sortable({
					axis: 'y',
					opacity: 0.4
				});
				$(div).sortable({
					axis: 'y',
					scroll: true,
					opacity: 0.4
				});
			}
		}
	}
	
	function redraw() {
		sortable("destroy");
		tree.setData(data);
		sortable();
	}
	
	function registerLayer(layer) {
		var g = layer.getGroup();
		if (!g) {
			g = { name: "Ungrouped", id: "_ungrouped" };
		}
		
		var set = false;
		
		for (var i=0; i<data.length; i++) {
		    if(data[i][0] == g) {
		    	data[i].push(layer);
		    	set = true;
		    }
		}
		
		if (!set) {
			data.push([g]);
			data[data.length-1].push(layer);
		}
		
		redraw();
	}
}



/**
 * Component: Regio.Feature
 * Abstract feature and Regio.Feature naespace. Also contain Regio.Feature.DefaultStyle
 * 
 * Group: Features
 * 
 * Requires: Regio.Utils, MapCat.Listeners, jQuery
 * 
 * Version: 1.2
 * 
 * Author: 
 * Alexandr Smirnov (alex@regio.ee)
 */



/*
 Constructor: Regio.Feature
*/
Regio.Feature = function(type, layer, style) {
	var me = this, listeners = new MapCat.Listeners();
	var hidden = false;
	
	draw = function() {}; // abstract!
	
	me.type = type;
	me.layer = layer;
	me.id = Regio.Utils.createUniqueId("o_");
	me.listeners = listeners;
	
	this.remove = function() {
		layer.removeFeature(me);
	}
	
	this.setCoords = function(coords) {
		me.coords = coords;
		listeners.broadcast("coordschanged");
	}
	
	this.getCoords = function() {
		return me.coords;
	}
	
	this.setStyle = function(style) {
		layer.setFeatureStyle(me, style);
	}
	
	this.getStyle = function() {
		return layer.getFeatureStyle(me);
	}
	
	this.setTitle = function(title) {
		this.title = title;
		listeners.broadcast("titlechanged");
	}
	
	this.getTitle = function() {
		return this.title;
	}
	
	this.setEditable = function(edit) {
		if (edit) {
			layer.renderer.setEditingFeature(me);
		} else {
			layer.renderer.stopEditing();
		}
	}
	
	this.draw = function() {
		if (this.isInited()) {
			draw(me);
			listeners.broadcast("drawn");
		}
	}
	
	this.erase = function() {
		//
	}
	
	this.hide = function() {
		hidden = true;
		listeners.broadcast("visibilitychanged", !hidden);
		me.erase();
	}
	
	this.show = function() {
		hidden = false;
		listeners.broadcast("visibilitychanged", !hidden);
		me.draw();
	}
	
	this.isVisible = function() {
		return !hidden;
	}
	
	this.isInited = function() { // if digitizing just started, there might be no coords yet.
		return (typeof(me.coords) != "undefined");
	}
	
	this.setStyle(style);
	
	layer.registerFeature(me);
};

/*
 Const: Regio.Feature.DefaultStyle
 Contains default values for all features styles. Can be changed at runtime.
 
 Following is default:
 (code)
	Regio.Feature.DefaultStyle = {
		strokeColor: "FF0000",
		strokeWidth: 5,
		strokeOpacity: 45,
		
		fillColor: "000000",
		fillOpacity: 45,
		
		pointIcon: "a",
		pointColor: "00FF00",
		pointOpacity: 75
	}
 (end code)
*/
Regio.Feature.DefaultStyle = {
	strokeColor: "FF0000",
	strokeWidth: 5,
	strokeOpacity: 45,
	
	fillColor: "000000",
	fillOpacity: 45,
	
	pointIcon: "a",
	pointColor: "00FF00",
	pointOpacity: 100
}

Regio.Feature.getCompoundStyle = function(userStyle) {
	userStyle = userStyle || {};
	
	function oneOf() {
		for(var i=0; i<arguments.length; i++) {
			if (typeof(arguments[i]) != "undefined") return arguments[i];
		}
	}

	var s = jQuery.extend({}, Regio.Feature.DefaultStyle, userStyle);
	
	// define synonims/aliases
/*	s.strokeColor = oneOf(s.strokeColor, s.lineColor);
	s.strokeWidth = oneOf(s.strokeWidth, s.lineWidth);
	s.strokeOpacity = oneOf(s.strokeOpacity, s.lineOpacity, s.lineAlpha, s.alpha);

	s.fillOpacity = oneOf(s.fillOpacity, s.alpha);
*/
	s.pointColor = oneOf(s.pointColor, s.color, s.strokeColor);
	s.pointOpacity = oneOf(s.pointOpacity, s.alpha, s.strokeOpacity);
	
	return s;
}



/**
 * Component: Regio.LonLat
 * Base type for geo points.
 * 
 * Author: 
 * Alexandr Smirnov (alex@regio.ee)
 */
 
 Regio.LonLat = function(e, n) {
	this.e = e;
	this.n = n;
}


/**
 * Component: Regio.Layer
 * Abstract layer. Features Renderer should override most of the methods.
 * 
 * Group: Features
 * 
 * Requires: Regio.Utils, Regio.LonLat, Regio.Feature, Regio.FeaturesRenderer, MapCat.Listeners, jQuery
 * 
 * Version: 1.1
 * 
 * Author: 
 * Alexandr Smirnov (alex@regio.ee)
 */



if(1);

(function() {

/*function throwAbstract(method) {
	throw "Method '"+method+"' is abstract!";
}*/

Regio.Layer = function(renderer, lid) {
	var lid = lid || Regio.Utils.createUniqueId("l_");
	
	this.id = lid;
	this.renderer = renderer;
	this.features = [];
	this.listeners = new MapCat.Listeners();
	this.visible = true;
	
	renderer.registerLayer(this);
}

Regio.Layer.prototype = {
	createPoint: function(coords, userStyle) {
		if ((coords != "digitize") && (!coords || !coords.e || !coords.n)) {
			throw 'No or bad coords given for createPoint';
		}
		
		//throwAbstract("createPoint");
	},
	createLine: function(points, userStyle) {
		if ((points != "digitize") && (!points || !Regio.Utils.isArray(points))) {
			throw 'No or bad points given for createLine';
		}
		
		//throwAbstract("createLine");
	},
	createPolygon: function(points, userStyle) {
		if ((points != "digitize") && (!points || !Regio.Utils.isArray(points))) {
			throw 'No or bad points given for createPolygon';
		}
		
		//throwAbstract("createPolygon");
	},
	getFeatureStyle: function(feature) {
		return feature.style;
	},
	setFeatureStyle: function(feature, style) {
		style = style || {};
		feature.style = jQuery.extend({}, Regio.Feature.DefaultStyle, feature.style, style);
		feature.listeners.broadcast("stylechanged");
	},
	removeFeature: function(feature) {
		delete this.features[feature.id];
		feature.listeners.broadcast("removed");
		this.listeners.broadcast("featureremoved", feature);
	},
	registerFeature: function(feature) {
		var old = this.features[feature.id];
		if (old) old.remove();
		this.features[feature.id] = feature;
		
		this.listeners.broadcast("featurecreated", feature);
	},
	setEditable: function(feature, edit) {
		//throwAbstract("setEditable");
	},
	clear: function() {
		for(var i in this.features) {
			this.removeFeature(this.features[i]);
		}
	},
	remove: function() {
		this.renderer.removeLayer(this);
	},
	getState: function() {
		var arr = [];
		for(var i in this.features) {
			with (this.features[i]) arr.push({
				type: type,
				title: getTitle(),
				coords: getCoords(),
				style: getStyle(),
				visible: isVisible()
			});
		}
		return arr;
	},
	setState: function(state) {
		this.clear();
		for(var i=0; i<state.length; i++) {
			
			var type = state[i].type;
			type = (''+type).toLowerCase();
			type = type.substr(0,1).toUpperCase() + type.substr(1); // camelize
			
			var method = "create"+type;
			
			var feature = this[method](state[i].coords, state[i].style);
			feature.setTitle(state[i].title);
			if(!state[i].visible) {
				feature.hide();
			}
		}
	},
	setGroup: function(group) {
		this.group = group;
	},
	getGroup: function() {
		return this.group;
	},
	setVisibility: function(visible) {
		this.visible = visible;
	},
	getVisibility: function() {
		return this.visible;
	}
}

})();


/**
 * Component: Regio.LayersGroup
 * 
 * Group: Features
 * 
 * Requires: Regio.Utils, MapCat.Listeners, jQuery
 * 
 * Version: 1.0
 * 
 * Author: 
 * Alexandr Smirnov (alex@regio.ee)
 */

(function() {

Regio.LayersGroup = function(name, id) {
	var id = id || Regio.Utils.createUniqueId("g_");
	
	this.id = id;
	this.name = name;
}

})();


/**
 * Component: Regio.Controls.LayersTree.FlashTileAdapter
 * Adapter for Regio.Controls.LayersTree. Allows using LayersTree with FlashTile directly.
 * 
 * Requires: Regio.Utils, MapCat.Listeners, Regio.Controls.LayersTree, Regio.LayersGroup, Regio.Layer
 * 
 * Version: 1.0
 * 
 * Group: Controls
 * 
 * Author:
 * Alexandr Smirnov (alex@regio.ee)
 */

(function() {

var GroupAdapter = function(rec) {
	this.id = rec.id;
	this.name = rec.name;
}

var LayerAdapter = function(renderer, rec, group) {
	var layer = new Regio.Layer(renderer, rec.name);
	layer.setGroup(group);
	layer.setVisibility(rec.checked);
	
	if (!rec.enabled) {
		layer.listeners.broadcast("layerdisabled", layer);
	}
	
	return layer;
}

Regio.Controls.LayersTree.FlashTileAdapter = function(mapcat) {
	var listeners = this.listeners = new MapCat.Listeners();
	var me = this;
	var layers = [];
	
	mapcat.addCallback("mapcat.layersData.loaded", function(list) {
		for(var g in list) {
			var groupRec = list[g];
			var group = new Regio.LayersGroup(groupRec.name, groupRec.id);
			
			for(var l in groupRec.layers) {
				var layerRec = groupRec.layers[l];
				var layer = new LayerAdapter(me, layerRec, group);
				listeners.broadcast("layercreated", layer);
			}
		}
	});
	
	this.registerLayer = function(layer) {
		layers.push(layer);
	}
}

})();


/**
 * Component: Regio.FeaturesRenderer.FT
 * Features Renderer for MapCat FlashTile.
 * 
 * Group: Features
 * 
 * Requires: Regio.Utils, Regio.LonLat, Regio.Feature, Regio.FeaturesRenderer, Regio.Layer, MapCat.Listeners, jQuery
 * 
 * Version: 1.1
 * 
 * Author: 
 * Alexandr Smirnov (alex@regio.ee)
 */



if(1);

(function() {

var typeMap = {
	point: 			{ id: 1, name: "POINT", fixCoords: true },
	line: 			{ id: 2, name: "LINESTRING", fixCoords: false },
	polygon: 		{ id: 3, name: "POLYGON", fixCoords: true },
	circle: 		{ id: 4, name: "CIRCLE", fixCoords: false },
	rectangle: 		{ id: 5, name: "RECTANGLE", fixCoords: false }
}

function typeMapById(id) {
	for(var i in typeMap) {
		if (typeMap[i].id == id) {
			return typeMap[i];
		}
	}
}

var FTLayer = function(renderer, lid) {
	Regio.Layer.apply(this, arguments);
	
	var lid = this.id;
	
	function mc() { // shortcut
		var args = arguments;
		renderer.map.ready(function() {
			renderer.map.broadcast.apply(renderer.map, args)
		});
	};
	
	mc("layers.addSystemLayer", lid);
	
	this.createPoint = function(points, userStyle) {
		Regio.Layer.prototype.createPoint.apply(this, arguments);
		
		var f = this._createObject("point", points, userStyle);
		
		return f;
	}

	this.createLine = function(points, userStyle) {
		Regio.Layer.prototype.createLine.apply(this, arguments);
		
		var f = this._createObject("line", points, userStyle);
		
		return f;
	}
	
	this.createPolygon = function(points, userStyle) {
		Regio.Layer.prototype.createPolygon.apply(this, arguments);
		
		var f = this._createObject("polygon", points, userStyle);
		
		return f;
	}
	
	this.createCircle = function(points, userStyle) {
		var f = this._createObject("circle", points, userStyle);
		
		return f;
	}

	this.createRectangle = function(points, userStyle) {
		var f = this._createObject("rectangle", points, userStyle);
		
		return f;
	}

	this._createObject = function(type, points, userStyle) {
		with(typeMap[type]) {
			var typeId = id;
			var typeName = name;
			var coordsFix = fixCoords;
		}
		if ((points != "digitize") && (!points)) {
			throw 'No or bad points given for createObject';
		}
		
		var f = new Regio.Feature(type.toLowerCase(), this, userStyle);
		
		f.draw = function() {
			
			var s = f.style;
			
			if (renderer.getEditingFeature() == f) {
				// feature is being edited or digitized
				mc("mapcat.restyleEditedObject", jQuery.extend({}, {
					style: {
						line: {
							lines: [{ color: s.strokeColor, width: s.strokeWidth }],
							alpha: s.strokeOpacity
						},
						area: {
							lineColor: s.strokeColor,
							lineWidth: s.strokeWidth,
							lineAlpha: s.strokeOpacity,
							fillColor: s.fillColor,
							fillAlpha: s.fillOpacity
						},
						point: {
							color: s.pointColor,
							alpha: s.pointOpacity
						}
					},
					symbolId: s.pointIcon,
					symbol: s.pointIcon
				}, f.style));
				
			} else {
			
				mc("layers.addObjectToSystemLayer", lid, jQuery.extend({
					objectId: f.id,
					featureId: f.id, // only for FeatureRenderer
					typeName: typeName,
					geo: coordsFix ? [f.coords] : f.coords,
					style: {
						line: {
							lines: [{ color: s.strokeColor, width: s.strokeWidth }],
							alpha: s.strokeOpacity
						},
						area: {
							lineColor: s.strokeColor,
							lineWidth: s.strokeWidth,
							lineAlpha: s.strokeOpacity,
							fillColor: s.fillColor,
							fillAlpha: s.fillOpacity
						},
						point: {
							color: s.pointColor,
							alpha: s.pointOpacity
						}
					},
					symbolId: s.pointIcon,
					symbol: s.pointIcon
				}, f.style));
			}
		}
		
		if (points != "digitize") {
			f.setCoords(points);
			f.draw();
		} else {
			var s = f.style;
			
			renderer.stopEditing();
			
			mc("mapcat.startEditMode", jQuery.extend({}, {
				type: typeId,
				lid: lid,
				sendResponse: 1,
				featureId: f.id, // only for FeatureRenderer
				style: {
					line: {
						lines: [{ color: s.strokeColor, width: s.strokeWidth }],
						alpha: s.strokeOpacity
					},
					area: {
						lineColor: s.strokeColor,
						lineWidth: s.strokeWidth,
						lineAlpha: s.strokeOpacity,
						fillColor: s.fillColor,
						fillAlpha: s.fillOpacity
					},
					point: {
						color: s.pointColor,
						alpha: s.pointOpacity
					}
				},
				symbolId: s.pointIcon,
				symbol: s.pointIcon
			}, f.style));
		}
		
		f.erase = function() {
			f.setEditable(false);
			mc("layers.removeObjectFromSystemLayer", lid, f.id);
		}
		
		return f;
	}
	
	this.setFeatureStyle = function(feature, style) {
		Regio.Layer.prototype.setFeatureStyle.apply(this, arguments);
		
		var s = feature.style;
		
		for(var i in s) {
			if(i.indexOf("Color")) {
				s[i] = Regio.Utils.removeSubstr(''+s[i], "#");
			}
		}
	}
	
	this.removeFeature = function(feature) {
		feature.erase();
		Regio.Layer.prototype.removeFeature.apply(this, arguments);
	}
}

jQuery.extend(FTLayer.prototype, Regio.Layer.prototype);

function registerFeatureEventListeners(map, layers) {
	// executes only if layer and feature exists
	function broadcastFeatureEvent(lid, fid, event, args) {
		var l, f, c;
		args = args || [];
		if ((l = layers[lid]) && (f = l.features[fid]) && (c = f.listeners)) {
			args.unshift(event);
			return c.broadcast.apply(c, args);
		}
	};
	
	map.addCallback("objects.onMouseOver", function(a) {
		broadcastFeatureEvent(a.layerId, a.objectId, "mouseover");
	});
	
	map.addCallback("objects.onMouseOut", function(a) {
		broadcastFeatureEvent(a.layerId, a.objectId, "mouseout");
	});
	
	map.addCallback("objects.onClick", function(a) {
		broadcastFeatureEvent(a.layerId, a.objectId, "click");
	});
}

Regio.FeaturesRenderer.FT = function(map) {
	Regio.FeaturesRenderer.apply(this, arguments);
	
	function mc() { // shortcut
		var args = arguments;
		map.ready(function() {
			map.broadcast.apply(map, args)
		});
	};
	
	var editingFeature = false;
	this.map = map;
	var me = this;
	
	// mouseover/out/click
	registerFeatureEventListeners(map, this.layers);
	
	function stopEditingFeature(success) {
		if (editingFeature) {
			var tmp = editingFeature;
			editingFeature = false;
			tmp.draw();
			
			me.listeners.broadcast("endEditingFeature", tmp);
		}
	}
	
	map.addCallback("mapcat.startEditMode", function(a) {
		if (editingFeature) {
			stopEditingFeature(true);
		}
		
		var l,f;
		// is it about our feature?
		if ((l = me.layers[a.lid]) && (f = l.features[a.featureId ? a.featureId : a.objectId])) {
			editingFeature = f;
			
			me.listeners.broadcast("beginEditingFeature", f);
		}
	});
	
	map.addCallback("mapcat.editModeResponse", function(a) {
		if (editingFeature) {
			editingFeature.setCoords(typeMapById(a.type).fixCoords ? a.geo[0] : a.geo);
		}
	});
	
	map.addCallback("mapcat.stopEditMode", stopEditingFeature);
	
	this.createLayer = function(lid) {
		var layer = new FTLayer(this, lid);
		
		Regio.FeaturesRenderer.prototype.createLayer.apply(this, [layer]);
		
		return layer;
	}
	
	this.setEditingFeature = function(feature) {
		if (editingFeature) {
			this.stopEditing();
		}
		mc("mapcat.startEditMode", { objectId: feature.id, lid: feature.layer.id, action: "COPY_AND_HIDE", sendResponse: 1 });
		
		Regio.FeaturesRenderer.prototype.setEditingFeature.apply(this, arguments);
	}
	
	this.stopEditing = function() {
		if (editingFeature) {
			mc("mapcat.stopEditMode", true);
		}
	}
	
	this.getEditingFeature = function() {
		Regio.FeaturesRenderer.prototype.getEditingFeature.apply(this, arguments);
		
		return editingFeature;
	}
}

jQuery.extend(Regio.FeaturesRenderer.FT.prototype, Regio.FeaturesRenderer.prototype);

})();


/**
 * Component: Regio.FeaturesRenderer.OpenLayers
 * Features Renderer for OpenLayers. CreateLayers.CreateFeatures(Manual+Digitizing).RestyleFeatures
 * 
 * Group: Features
 * 
 * Requires: Regio.Utils, Regio.LonLat, Regio.Feature, Regio.FeaturesRenderer, MapCat.Listeners, jQuery
 * 
 * Version: 1.0
 * 
 * Author: 
 * Alexandr Smirnov (alex@regio.ee)
 */



if(1);

(function() {

Regio.FeaturesRenderer.OpenLayers = function(map, cfg) {
	cfg = cfg || {};
	
	Regio.Utils.applyDefaults(cfg, {
		srs: 'EST'
	});
	
	function toPoint(p) {
		var p = Regio.Coords.Convert(p.e, p.n, cfg.srs, 'WGS84');
		return new OpenLayers.Geometry.Point(p.e, p.n);
	}

	function toLinearRing(p) {
		var r = [];
		for(var i=0; i<p.length; i++) {
			r.push(toPoint(p[i]));
		}
		return new OpenLayers.Geometry.LinearRing(r);
	}


	var layers = {};
	var editingFeature;
	var listeners = new MapCat.Listeners();
	
	this.createLayer = function() {
		var lid = Regio.Utils.createUniqueId("l_");
		var vectorLayer = new OpenLayers.Layer.Vector(lid);
		map.addLayers([vectorLayer]);
		
		var modifyControl = new OpenLayers.Control.ModifyFeature(vectorLayer, {
			clickout: false,
			toggle: false
		});
		var createControl;
		
		modifyControl.mode = OpenLayers.Control.ModifyFeature.RESHAPE | OpenLayers.Control.ModifyFeature.DRAG;
		map.addControl(modifyControl);
		
		var layer = {
			id: lid,
			features: {},
			createPoint: function(coords, userStyle) {
				var t; alert(t = "OpenLayers FeaturesRenderer does not support createPoint!"); throw(t);
			},
			createLine: function(points, userStyle) {
				var t; alert(t = "OpenLayers FeaturesRenderer does not support createLine!"); throw(t);
			},
			createPolygon: function(points, userStyle) {
				if ((points != "digitize") && (!points || !Regio.Utils.isArray(points))) {
					throw 'No or bad points given for createPolygon';
				}
				
				var f = new Regio.Feature("polygon", this, userStyle);
				var polygonFeature = new OpenLayers.Feature.Vector(new OpenLayers.Geometry.Polygon([]), null);
				vectorLayer.addFeatures([polygonFeature]);
				
				f.mapFeature = polygonFeature;
				
				var old = f.setCoords;
				f.setCoords = function(points) {
					if ((points.length > 0) && (!Regio.Utils.isArray(points[0]))) {
						points = [toLinearRing(points)];
					} else {
						for(var i=0; i<p.length; i++) {
							points[i] = toLinearRing(points[i]);
						}
					}
					old.call(f, points);
				}
				f.draw = function() {
					f.mapFeature.style = f.style;
					f.mapFeature.geometry.components = f.coords;
					f.mapFeature.layer.drawFeature(f.mapFeature);
				}
				
				if (points != "digitize") {
					f.setCoords(points);
					// f.draw();
				} else {
					modifyControl.deactivate();
					
					var control = new OpenLayers.Control.DrawFeature(vectorLayer, OpenLayers.Handler.Polygon, { handlerOptions: { style: f.style } });
					map.addControl(control);
					control.activate();
					createControl = control;
					
					control.featureAdded = function(feature) {
						f.mapFeature.geometry = feature.geometry.clone();
						f.mapFeature.style = f.style;
						f.mapFeature.layer.drawFeature(f.mapFeature);
						
						vectorLayer.removeFeatures([feature]);
						
						map.removeControl(control);
						control.deactivate();
						control.destroy();
						createControl = undefined;
						
						f.setEditable(true);
					}

					editingFeature = f;
				}
				
				return f;
			},
			getFeatureStyle: function(feature) {
				return feature.style;
			},
			setFeatureStyle: function(feature, style) {
				style = style || {};
				var s = jQuery.extend({}, Regio.Feature.DefaultStyle, feature.style, style);
				
				for(var i in s) {
					if((i.indexOf("Opacity") >= 0) && (s[i] > 1)) {
						s[i] = s[i] / 100;
					} else
					if((i.indexOf("Color") >= 0) && (s[i].indexOf("#") != 0)) {
						s[i] = "#" + s[i];
					}
				}
				feature.style = s;
				feature.draw();
			},
			removeFeature: function(feature) {
				if (feature.layer == this) {
					var l;
					if (l = layers[lid]) {
						delete l.features[feature.id];
					}
				}
			},
			registerFeature: function(feature) {
				var old = this.features[feature.id];
				if (old) old.remove();
				this.features[feature.id] = feature;
			},
			setEditable: function(feature, edit) {
				if (!feature) return;
				if(!edit) {
					if (editingFeature == feature) {
						modifyControl.deactivate();
						if(createControl) {
							createControl.destroy();
						}
					}
					editingFeature = undefined;
				} else {
					editingFeature = feature;

					modifyControl.activate();
					modifyControl.selectControl.select(feature.mapFeature);
					modifyControl.selectControl.handler.deactivate();
					
					if(createControl) {
						createControl.destroy();
					}
				}
			},
			getEditingFeature: function() {
				return editingFeature;
			}
		}
		
		layers[lid] = layer;
		
		listeners.broadcast("layercreated", layer);
		
		return layer;
	}
	
	this.removeLayer = function(layer) {
		// TODO: !!!
		listeners.broadcast("layerremoved", layer);
	}
}

})();


/**
 * Component: Regio.GCAdmin
 * Base component for GCAdmin adapters
 * 
 * Group: GCAdmin
 * 
 * Requires: Regio.Utils, MapCat.Listeners, jQuery
 * 
 * Version: 0.9
 * 
 * Author: 
 * Alexandr Smirnov (alex@regio.ee)
 */



/*
 * Constructor: Regio.GCAdmin
 * 
 * Creates gcAdmin component in gived DIV conteiner
 * 
 * Parameters:
 * div - DOM element
 * cfg - Configuration
 * 
 * Configuration:
 * hideShow - specifies if gcAdmin should control visibility of component DIV (default is false)
 * openedByDefault - if true, <open> event will be processed immediately after component is <loaded>
 * 
 * Usage:
 * | gca = new Regio.GCAdmin('gcAdminDivID', {
 * |    hideShow: true
 * | });
 * | 
 * | gcaFT = Regio.GCAdmin.addFlashTileAdapter(gca, {
 * |    src: "mapcat/map.swf",
 * |    vars: "confUrl=mapcat/conf.xml%3F&bgcolor=0x738aa0&cur_map=0&printing=0"
 * | });
 * 
 * See also:
 * <Regio.GCAdmin.addSearchAdapter>, <Regio.GCAdmin.addFlashTileAdapter>
 * 
 * Uses following HTML elements/CSS selectors (if available) in GCAdmin container:
 * .gca_clear - clear GCAdmin (fires event "clear")
 * .gca_close - close GCAdmin container
 * .gca_done - report intermediate result (des not close GCA!)
 * .gca_done_and_close - report result AND close GCA
 */
Regio.GCAdmin = function(div, cfg) {
	var me = this, lastResult;
	div = Regio.Utils.getElement(div);
	if(!cfg) cfg = {};
	
	var listeners = new MapCat.Listeners();
	
	var opened = $(div).is(':visible');
	
	// gcAdmin template bindings
	$('.gca_clear', div).click(function() {
		listeners.broadcast("clear");
		return false;
	});
	$('.gca_close', div).click(function() {
		listeners.broadcast("close");
		return false;
	});
	$('.gca_done', div).click(function() {
		notifyDone();
		return false;
	});
	$('.gca_done_and_close', div).click(function() {
		notifyDone();
		listeners.broadcast("close");
		return false;
	});
	
	// gcAdmin general events
	
	function notifyDone() {
		listeners.broadcast("confirmDone", lastResult);
		/*
		 * Event: done
		 * Occures when gcAdmin was closed (and not cancelled). Contains information on object, selected by user.
		 * *Usually redefined by adapters*.
		 * 
		 * Parameters:
		 * type - object type (or "undefined" if no object was selected)
		 * data - object data, center coordinates or line/polygon coordinates array (oe "undefined" if no object selected)
		 */
		if (lastResult) {
			listeners.broadcast("done", lastResult.type, lastResult.data);
		} else {
			listeners.broadcast("done");
		}
	}
	
	listeners
	/*
	 * Event: result
	 * Regulary fired by adapters to indicate intermediate results. When user will 
	 * close gcAdmin, last <result> will be repeated by <done> event.
	 * 
	 * Parameters:
	 * type - object type
	 * data - object data
	 * 
	 * See:
	 * <done> event.
	 */
	.add("result", function(type, data) {
		lastResult = {
			type: type,
			data: data
		}
	})
	/*
	 * Event: close
	 * Listens for this event to close component div (slideUp, only if hideShow=true)
	 * Fires events <closeBegin> and <closeEnd>
	 */
	.add("close", function() {
		if (opened) {
			opened = false;
			/*
			 * Event: closeBegin
			 * Indicates, that closing process has begun (has sence only if hideShow=true, to indicate starting point of animation)
			 */
			listeners.broadcast("closeBegin");
			if (cfg.hideShow) {
				$(div).slideUp("normal", function() {
					/*
					 * Event: closeEnd
					 * Indicates, that closing process has completed (has sence only if hideShow=true, to indicate ending point of animation)
					 */
					listeners.broadcast("closeEnd");
				});
			} else {
				listeners.broadcast("closeEnd");
			}
		}
	})
	/*
	 * Event: open
	 * Listens for this event to open component div (slideDown, only if hideShow=true)
	 * Fires events <openBegin> and <openEnd>
	 */
	.add("open", function() {
		if (!opened) {
			/*
			 * Event: openBegin
			 * Indicates, that opening process has begun (has sence only if hideShow=true, to indicate starting point of animation)
			 */
			listeners.broadcast("openBegin");
			if (cfg.hideShow) {
				$(div).slideDown("normal", function() {
					/*
					 * Event: openEnd
					 * Indicates, that opening process has completed (has sence only if hideShow=true, to indicate ending point of animation)
					 */
					listeners.broadcast("openEnd");
				});
			} else {
				listeners.broadcast("openEnd");
			}
			opened = true;
		}
	})
	/*
	 * Event: isopened
	 * Listens for this event and returns current open/close status (true if component is opened)
	 */
	.add("isopened", function() {
		return opened;
	})
	.add("loaded", function() {
		if (cfg.openedByDefault) {
			listeners.broadcast("open");
		}
	});
	
	/*
	 * Event: loaded
	 * Fired when gcAdmin is loaded and ready to accept commands
	 */
	// all done, notify listeners
	/*
	 * Property: loaded
	 * True if component was successfully loaded and ready.
	 */
	this.loaded = true;
	listeners.broadcast("loaded");
	
	/*
	 * Property: listeners
	 * Event dispatcher interface. See <MapCat.Listeners>
	 */
	this.listeners = listeners; // public
	
	/*
	 * Method: open
	 * Open component (slide down). Has visual effect only if configuration hideShow=true
	 * (shortcut for listeners.broadcast("open"))
	 */
	this.open = function() { listeners.broadcast("open") };
	/*
	 * Method: close
	 * Close component (slide up). Has visual effect only if configuration hideShow=true
	 * (shortcut for listeners.broadcast("close"))
	 */
	this.close = function() { listeners.broadcast("close") };
	/*
	 * Method: isOpened
	 * Return true if DIV is visible OR was opened by <open> method (shortcut for listeners.broadcast("isopened"))
	 */
	this.isOpened = function() { return opened };
	/*
	 * Method: clear
	 * Shortcut to listeners.broadcast("clear").
	 * 
	 * See:
	 * <clear> event
	 */
	this.clear = function() { listeners.broadcast("clear") };
	/*
	 * Method: getDiv
	 * Returns component DOM element container
	 * 
	 * See:
	 * <Regio.GCAdmin>
	 */
	this.getDiv = function() { return div };
	/*
	 * Method: getConfig
	 * Returns component configuration object
	 * 
	 * See:
	 * <Regio.GCAdmin>
	 */
	this.getConfig = function() { return cfg };
	
	/*
	 * Method: done
	 * Notify adapters completion event. This is equal to clicking on DOM element withing gcAdmin containg with class=".gca_done". 
	 */
	this.done = function() { notifyDone() }
}


/**
 * Component: Regio.GCAdmin.FlashTileAdapter
 * Adapter for GCAdmin component.
 * 
 * Group: GCAdmin
 * 
 * Requires: Regio.Utils, Regio.GCAdmin, MapCat.Listeners, jQuery
 * 
 * See also:
 * <MapCat.Component>
 * 
 * Author: 
 * Alexandr Smirnov (alex@regio.ee)
 */



if(true) {}; // dummy, do not remove

(function() {

function installDigitizingButtons(mapcat, gcaDiv) {
	// does active tab management PASSIVE
	var digitizer = {
		_selected: false,
		_select: function(s) {
			this._deselect();
			(this._selected = $(s, gcaDiv)).addClass("active");
		},
		_deselect: function() {
			if (this._selected) {
				this._selected.removeClass("active");
			}
		},
		stop: function() {
			this._deselect();
		},
		point: function() {
			this._select('.digitizer_point');
		},
		line: function() {
			this._select('.digitizer_line');
		},
		polygon: function() {
			this._select('.digitizer_polygon');
		}
	}
	
	mapcat.addCallback("mapcat.stopEditMode", function() {
		digitizer.stop();
	});
	
	mapcat.addCallback("mapcat.startEditMode", function(args) {
		switch (parseInt(args.type)) {
			case 1: digitizer.point(); break;
			case 2: digitizer.line(); break;
			case 3: digitizer.polygon(); break;
		}
	});
}

function installSelectMapButtons(mapcat, gcaDiv) {
	var selected;
	
	selectButton(0);
	
	// just in case something changes when map loaded
	mapcat.ready(function() {
		var state = mapcat.call("map.getMapState");
		if (state && (typeof(state.cur_map) != "undefined")) {
			selectButton(state.cur_map);
		}
	});
	
	// install template bindings
	$('.ft_select_map').click(function() {
		var mapId = $(this).attr('selectMap');
		if ((typeof(mapId) != "undefined") && (selected != mapId)) {
			mapcat.ready(function() {
				// this will call map.onMapParams and button will be selected by itself
				mapcat.broadcast("tiles.selectServer", mapId);
			});
		}
		return false;
	});
	
	// react on state change
	function selectButton(mapId) {
		$('.ft_select_map[selectMap='+mapId+']').addClass('active');
		$('.ft_select_map[selectMap!='+mapId+']').removeClass('active');
		selected = mapId;
	}
	
	mapcat.addCallback("map.onMapParams", function(args) {
		var mapId = args.cur_map;
		selectButton(mapId);
	});
}

/*
 * Function: Regio.GCAdmin.addFlashTileAdapter
 * Creates and assigns new FlashTileAdapter (see <MapCat.Component>) to <Regio.GCAdmin> component. 
 * If user finishes digitizing, then "result" event fired (see <Regio.GCAdmin>) with 
 * type = "POINT" or "LINESTRING" or "POLYGON" (see FlashTile documentation)
 * 
 * Parameters:
 * gca - gcAdmin component
 * cfg - Configuration
 * mapListeners - <MapCat.Listeners> to be assigned to FlashTile (<MapCat.Component>).
 * 
 * Configuration:
 * div - string, element id within <Regio.GCAdmin> container to be used by <MapCat.Component> (deafult ".map_container")
 * ... - all other possible configuration parameters that <MapCat.Component> accepts
 * 
 * See also:
 * <Regio.GCAdmin>, <MapCat.Component>
 * 
 * Uses following HTML elements/CSS selectors (if available) in GCAdmin container:
 * .map_container - see *Configuration*
 * .digitizer_point, .digitizer_line, .digitizer_polygon - set of anchors by clicking on each 
 * FlashTile will start corresponding editMode. Also this anchors will be assigned class with name "active"
 * if corresponding anchor is selected.
 * .active - class being assigned to selected buttons (see .digitizer_xxx)
 * .ft_select_map - set of anchors clicking on which, flashtile will select corresponding map type (usually this types are: "0" - regular map; "1" - orto; "2" - hybrid). For more info, see FlashTile documenation.
 * To determine which map should be selected, anchor must have atttribute "selectMap". For example anchor may look like this:
 * *<a href="#" class="ft_select_map" selectMap="2">Switch to Hybrid maps</a>*
 */
Regio.GCAdmin.addFlashTileAdapter = function(gca, cfg, mapListeners) {
	var gcaDiv = gca.getDiv();
	var gcaCfg = gca.getConfig();
	var gcaListeners = gca.listeners;
	
	Regio.Utils.applyDefaults(cfg, {
		div: '.map_container'
	});
	
	var j = $(cfg.div, gcaDiv);
	if (j.length <= 0) {
		throw 'Regio.GCAdmin, element with selector "'+cfg.div+'" missing (need it for embedding MapCatFT)';
	}
	
	var mapcatDiv = j[0];
	var mapcat = new MapCat.Component(mapcatDiv, cfg, mapListeners);
	
	installDigitizingButtons(mapcat, gcaDiv);
	installSelectMapButtons(mapcat, gcaDiv);
	
	// shortcut for ready(mapcat.broadcast)
	function mc() {
		var args = arguments;
		mapcat.ready(function() {
			mapcat.broadcast.apply(mapcat, args);
		});
	}
	
	// mapcat events
	mapcat.addCallback("mapcat.editModeResponse", function(args) {
		gcaListeners.broadcast("result", args.typeName, args);
	});
	
	// add gcAdmin component callbacks
	gcaListeners
	.add("result", function(type, arr) {
		if (type == "ADDRESS") {
			mc("mapcat.stopEditMode");
			mc("mapcat.makeSearchResult", {
				e: arr.e, n: arr.n, z: arr.z,
				name: arr.address
			});
		}
	})
	.add("clear", function() {
		mc("mapcat.stopEditMode");
		mc("mapcat.removeSearchResult");
	})
	.add("openBegin", function() {
		if (gcaCfg.hideShow) {
			$(mapcatDiv).hide();
			mapcat.restart(); // some browsers recreate flash on display: none... opera also has problems with ExternalInterface in this case, so just recreate it from scratch for all browsers
		}
	})
	.add("openEnd", function() {
		if (gcaCfg.hideShow) {
			$(mapcatDiv).show();
		}
	})
	.add("closeBegin", function() {
		if (gcaCfg.hideShow) {
			$(mapcatDiv).hide();
			mapcat.restart(); // some browsers recreate flash on display: none... opera also has problems with ExternalInterface in this case, so just recreate it from scratch for all browsers
		}
	})
	.add("close", function() {
		mc("mapcat.stopEditMode");
	});
	
	// static template bindings
	$('.digitizer_point', gcaDiv).click(function() {
		if ($(this).hasClass('active')) {
			mc("mapcat.stopEditMode");
		} else {
			mc("mapcat.startEditMode", { "type": 1, "lid": "points", "sendResponse": 1 });
		}
		return false;
	});
	$('.digitizer_line', gcaDiv).click(function() {
		if ($(this).hasClass('active')) {
			mc("mapcat.stopEditMode");
		} else {
			mc("mapcat.startEditMode", { "type": 2, "lid": "lines", "sendResponse": 1 });
		}
		return false;
	});
	$('.digitizer_polygon', gcaDiv).click(function() {
		if ($(this).hasClass('active')) {
			mc("mapcat.stopEditMode");
		} else {
			mc("mapcat.startEditMode", { "type": 3, "lid": "area", "sendResponse": 1 });
		}
		return false;
	});
	
	return mapcat;
}

})();



/**
 * Component: Regio.Coords
 * EXPERIMENT component for converting coordinates between UTM zones. Has some unfixed issues with precision.
 * Unfinished, use at your own risk.
 * Initially from http://www.jstott.me.uk/jscoord.
 * 
 * Converted by:
 * Alexandr Sminrov (alex@regio.ee)
 */

// converted by Alexandr
// initially from http://www.jstott.me.uk/jscoord

if (typeof(Regio) == "undefined") {
	Regio = {};
}

Regio.Coords = {};

Regio.Coords.defaultUTM = [34, 'V'];

(function() {

/**
 *  Work out the UTM latitude zone from the latitude
 *
 * @param latitude
 * @return
 * @since 0.2
 */
function getUTMLatitudeZoneLetter(latitude) {
  if ((84 >= latitude) && (latitude >= 72)) return "X";
  else if (( 72 > latitude) && (latitude >=  64)) return "W";
  else if (( 64 > latitude) && (latitude >=  56)) return "V";
  else if (( 56 > latitude) && (latitude >=  48)) return "U";
  else if (( 48 > latitude) && (latitude >=  40)) return "T";
  else if (( 40 > latitude) && (latitude >=  32)) return "S";
  else if (( 32 > latitude) && (latitude >=  24)) return "R";
  else if (( 24 > latitude) && (latitude >=  16)) return "Q";
  else if (( 16 > latitude) && (latitude >=   8)) return "P";
  else if ((  8 > latitude) && (latitude >=   0)) return "N";
  else if ((  0 > latitude) && (latitude >=  -8)) return "M";
  else if (( -8 > latitude) && (latitude >= -16)) return "L";
  else if ((-16 > latitude) && (latitude >= -24)) return "K";
  else if ((-24 > latitude) && (latitude >= -32)) return "J";
  else if ((-32 > latitude) && (latitude >= -40)) return "H";
  else if ((-40 > latitude) && (latitude >= -48)) return "G";
  else if ((-48 > latitude) && (latitude >= -56)) return "F";
  else if ((-56 > latitude) && (latitude >= -64)) return "E";
  else if ((-64 > latitude) && (latitude >= -72)) return "D";
  else if ((-72 > latitude) && (latitude >= -80)) return "C";
  else return 'Z';
}

function ord(x) {
  var c = x.charAt(0);
  var i;
  for (i = 0; i < 256; ++ i) {
    var h = i.toString (16);
    if (h.length == 1)
      h = "0" + h;
    h = "%" + h;
    h = unescape (h);
    if (h == c)
      break;
  }
  return i;
}

/**
 * Function: Regio.Coords.UTMToLonLat
 * Convert an UTM reference to a latitude and longitude
 * 
 * Parameters:
 * easting - E
 * northing - N
 * lngZone - UTM longitude zone
 * latZone - UTM latitude zone
 */
Regio.Coords.UTMToLonLat = function(easting, northing, lngZone, latZone) {
	if (!lngZone || !latZone) {
		lngZone = Regio.Coords.defaultUTM[0];
		latZone = Regio.Coords.defaultUTM[1];
	}
	
	var UTM_F0 = 0.9996;
	var a = 6378137;
	var b = 6356752.314;
	var eSquared = ((a * a) - (b * b)) / (a * a);
	var ePrimeSquared = eSquared / (1.0 - eSquared);
	var e1 = (1 - Math.sqrt(1 - eSquared)) / (1 + Math.sqrt(1 - eSquared));
	var x = easting - 500000.0;;
	var y = northing;
	var zoneNumber = lngZone;
	var zoneLetter = latZone;

	var longitudeOrigin = (zoneNumber - 1.0) * 6.0 - 180.0 + 3.0;

	// Correct y for southern hemisphere
	if ((ord(zoneLetter) - ord("N")) < 0) {
	y -= 10000000.0;
	}

	var m = y / UTM_F0;
	var mu =
	m
	  / (a
	    * (1.0
	      - eSquared / 4.0
	      - 3.0 * eSquared * eSquared / 64.0
	      - 5.0
	        * Math.pow(eSquared, 3.0)
	        / 256.0));

	var phi1Rad =
	mu
	  + (3.0 * e1 / 2.0 - 27.0 * Math.pow(e1, 3.0) / 32.0) * Math.sin(2.0 * mu)
	  + (21.0 * e1 * e1 / 16.0 - 55.0 * Math.pow(e1, 4.0) / 32.0)
	    * Math.sin(4.0 * mu)
	  + (151.0 * Math.pow(e1, 3.0) / 96.0) * Math.sin(6.0 * mu);

	var n =
	a
	  / Math.sqrt(1.0 - eSquared * Math.sin(phi1Rad) * Math.sin(phi1Rad));
	var t = Math.tan(phi1Rad) * Math.tan(phi1Rad);
	var c = ePrimeSquared * Math.cos(phi1Rad) * Math.cos(phi1Rad);
	var r =
	a
	  * (1.0 - eSquared)
	  / Math.pow(
	    1.0 - eSquared * Math.sin(phi1Rad) * Math.sin(phi1Rad),
	    1.5);
	var d = x / (n * UTM_F0);

	var latitude = (
	phi1Rad
	  - (n * Math.tan(phi1Rad) / r)
	    * (d * d / 2.0
	      - (5.0
	        + (3.0 * t)
	        + (10.0 * c)
	        - (4.0 * c * c)
	        - (9.0 * ePrimeSquared))
	        * Math.pow(d, 4.0)
	        / 24.0
	      + (61.0
	        + (90.0 * t)
	        + (298.0 * c)
	        + (45.0 * t * t)
	        - (252.0 * ePrimeSquared)
	        - (3.0 * c * c))
	        * Math.pow(d, 6.0)
	        / 720.0)) * (180.0 / Math.PI);

	var longitude = longitudeOrigin + (
	(d
	  - (1.0 + 2.0 * t + c) * Math.pow(d, 3.0) / 6.0
	  + (5.0
	    - (2.0 * c)
	    + (28.0 * t)
	    - (3.0 * c * c)
	    + (8.0 * ePrimeSquared)
	    + (24.0 * t * t))
	    * Math.pow(d, 5.0)
	    / 120.0)
	  / Math.cos(phi1Rad)) * (180.0 / Math.PI);

	return { lat: latitude, lon: longitude };
}


/**
 * Function: Regio.Coords.LonLatToUTM
 * Convert a latitude and longitude to an UTM reference
 * 
 * Parameters:
 * lng - longitude
 * lat - latitude
 */
Regio.Coords.LonLatToUTM = function(lng, lat) {
	var UTM_F0 = 0.9996;
	var a = 6378137;
	var b = 6356752.314;
	var eSquared = ((a * a) - (b * b)) / (a * a);
	var longitude = lng;
	var latitude = lat;

	var latitudeRad = latitude * (Math.PI / 180.0);
	var longitudeRad = longitude * (Math.PI / 180.0);
	var longitudeZone = Math.floor((longitude + 180.0) / 6.0) + 1;

	// Special zone for Norway
	if (latitude >= 56.0
	&& latitude < 64.0
	&& longitude >= 3.0
	&& longitude < 12.0) {
	longitudeZone = 32;
	}

	// Special zones for Svalbard
	if (latitude >= 72.0 && latitude < 84.0) {
	if (longitude >= 0.0 && longitude < 9.0) {
	  longitudeZone = 31;
	} else if (longitude >= 9.0 && longitude < 21.0) {
	  longitudeZone = 33;
	} else if (longitude >= 21.0 && longitude < 33.0) {
	  longitudeZone = 35;
	} else if (longitude >= 33.0 && longitude < 42.0) {
	  longitudeZone = 37;
	}
	}

	var longitudeOrigin = (longitudeZone - 1) * 6 - 180 + 3;
	var longitudeOriginRad = longitudeOrigin * (Math.PI / 180.0);

	var UTMZone = getUTMLatitudeZoneLetter(latitude);

	ePrimeSquared = (eSquared) / (1 - eSquared);

	var n = a / Math.sqrt(1 - eSquared * Math.sin(latitudeRad) * Math.sin(latitudeRad));
	var t = Math.tan(latitudeRad) * Math.tan(latitudeRad);
	var c = ePrimeSquared * Math.cos(latitudeRad) * Math.cos(latitudeRad);
	var A = Math.cos(latitudeRad) * (longitudeRad - longitudeOriginRad);

	var M =
	a
	  * ((1
	    - eSquared / 4
	    - 3 * eSquared * eSquared / 64
	    - 5 * eSquared * eSquared * eSquared / 256)
	    * latitudeRad
	    - (3 * eSquared / 8
	      + 3 * eSquared * eSquared / 32
	      + 45 * eSquared * eSquared * eSquared / 1024)
	      * Math.sin(2 * latitudeRad)
	    + (15 * eSquared * eSquared / 256
	      + 45 * eSquared * eSquared * eSquared / 1024)
	      * Math.sin(4 * latitudeRad)
	    - (35 * eSquared * eSquared * eSquared / 3072)
	      * Math.sin(6 * latitudeRad));

	var UTMEasting =
	(UTM_F0
	  * n
	  * (A
	    + (1 - t + c) * Math.pow(A, 3.0) / 6
	    + (5 - 18 * t + t * t + 72 * c - 58 * ePrimeSquared)
	      * Math.pow(A, 5.0)
	      / 120)
	  + 500000.0);

	var UTMNorthing =
	(UTM_F0
	  * (M
	    + n
	      * Math.tan(latitudeRad)
	      * (A * A / 2
	        + (5 - t + (9 * c) + (4 * c * c)) * Math.pow(A, 4.0) / 24
	        + (61 - (58 * t) + (t * t) + (600 * c) - (330 * ePrimeSquared))
	          * Math.pow(A, 6.0)
	          / 720)));

	// Adjust for the southern hemisphere
	if (latitude < 0) {
	UTMNorthing += 10000000.0;
	}

	return { e: UTMEasting, n: UTMNorthing, lngZone: longitudeZone, latZone: UTMZone };
}

})();


/**
 * Component: Regio.SearchList
 * Search component base. Fetches data from one of providers and 
 * displays a paged list of results using ListView component.
 * 
 * Group: Search
 * 
 * Requires: Regio.Utils, Regio.ListView, MapCat.Listeners
 * 
 * Version: 1.14
 * 
 * Other Requirements:
 * jQuery - OPTIONAL !! Animation is disabled without it AND providers MUST be functions without it.
 * 
 * See also:
 * <Regio.SearchList.createFromMarkup> - for simpler use of search component
 * 
 * Author:
 * Alexandr Smirnov (alex@regio.ee)
 */



/**
 * Constructor: Regio.SearchList
 * Creates SearchList component in provided DIV (DOM element).
 * 
 * Parameters:
 * div - DOM element, that will contain search results
 * cfg - Configuration
 * 
 * Configuration:
 * providers - object, describing available providers (see <Regio.SearchList.ProviderRegioJGC>). Sample: *{ jgc: Regio.SearchList.ProviderRegioJGC() }*
 * rowsPerPage - number of rows to be shown per one page
 * rowTemplate - see <Regio.ListView> for format description
 * tabFilter - optional, function(tabNum, rowObject) to apply to each row for particular tab
 */
/**
 * Event: query
 */
/**
 * Event: row
 */
/**
 * Event: clear
 */
/**
 * Event: page
 */
/**
 * Event: tab
 */
/**
 * Event: message
 * Fired when message should be shown to user.
 * 
 * Parameters:
 * type - "empty", "wait", "error" or "clear"
 * searchType - optional, provider name
 * errorDesc - optional, error description
 */
/**
 * Event: items
 */
/**
 * Event: submit
 */
Regio.SearchList = function(div, cfg) {
	/**
	 * Property: listeners
	 * Events dispatcher.
	 */
	var listeners = new MapCat.Listeners();
	var div = Regio.Utils.getElement(div);
	if (!div) {
		throw new Error('Regio.SearchList, provided DIV is incorrect!');
	}
	
	var submitting = false;
	var lastAjax = null, lastQuery, lastProvider;
	
	Regio.Utils.applyDefaults(cfg, {
		rowsPerPage: 20,
		tabFilter: function() {}
	});
	
	if (!cfg.rowTemplate) {
		cfg.rowTemplate = Regio.Utils.initTemplate(div, 'html');
	}
	
	if (typeof(cfg.providers) != "object") {
		throw new Error('Regio.SearchList, no providers defined!');
	}
	
	var list = new Regio.ListView(div, cfg);
	list.beforeDrawRow = function(obj, childNodes, i, cnt) {
		listeners.broadcast("row", obj, childNodes, i, cnt);
	}
	list.beforeClear = function() {
		listeners.broadcast("clear");
	}
	
	// does everything related to paging. only filter actually matters here.
	var paging = {
		curPage: 0, // read only, == 0 even if no data
		pageCount: 0, // read only
		filter: function(obj, cnt, num) {
			// cnt = number of items already in list
			// num = current item number in data array
			return (num > this.curPage * cfg.rowsPerPage) && (num <= (this.curPage + 1) * cfg.rowsPerPage);
		},
		setPageCount: function(cnt) {
			if (this.pageCount != cnt) {
				this.pageCount = cnt;
				if (this.curPage > cnt-1) {
					this.curPage = cnt-1;
				}
				if (this.curPage < 0) {
					this.curPage = 0;
				}
				listeners.broadcast("page", this.pageCount == 0 ? 0 : this.curPage+1, this.pageCount);
			}
		},
		resetToRowsCount: function(cnt) {
			var newPageCnt = Math.ceil(cnt / cfg.rowsPerPage);
			if (this.pageCount != newPageCnt) {
				this.setPageCount(newPageCnt);
				this.curPage = 0;
			}
		},
		setPage: function(p) {
			if ((this.curPage != p) && (p >= 0) && (p < this.pageCount)) {
				this.curPage = p;
				
				list.refresh();
				listeners.broadcast("page", this.curPage+1, this.pageCount);
			}
		},
		nextPage: function() { this.setPage(this.curPage+1) },
		prevPage: function() { this.setPage(this.curPage-1) }
	}
	paging.setPageCount(0);
	
	// does everything about tabs. you need only filter from here.
	var tabs = {
		curTab: 0, // read only
		filter: function(obj, cnt) {
			// cnt = number of items already in list
			var res = cfg.tabFilter(this.curTab, obj, cnt);
			if (typeof(res) == "undefined") {
				res = true;
			}
			return res;
		},
		setTab: function(num) {
			if (this.curTab != num) {
				this.curTab = num;
				paging.setPage(0);
				list.refresh();
				listeners.broadcast("tab", this.curTab);
			}
		}
	}
	
	var filter = {
		num: 0,
		start: function() {
			this.num = 0;
		},
		filter: function(obj, cnt) {
			var tabAccepts = tabs.filter(obj, cnt);
			if (tabAccepts) {
				this.num++;
			}
			obj.num = this.num; // process {num} tags
			return paging.filter(obj, cnt, this.num) && tabAccepts;
		},
		finish: function() {
			paging.resetToRowsCount(this.num);
		}
	}
	list.setFilter(filter);
	
	function showMessage(msgType, searchType, desc) {
		listeners.broadcast("message", msgType, searchType, desc);
	}
	
	function setListData(arr) {
		list.setData(arr);
		listeners.broadcast("items", arr);
		if (arr.length == 0) {
			showMessage("empty");
		}
	}
	
	/**
	 * Function: submit
	 * Submit search to particular provider
	 * 
	 * Parameters:
	 * query - query can be string OR object (ie. { query: 'tartu' }). it will be transfered to provider
	 * type - optional, string, provider name (see _providers_ in configuration). if omitted, then first provider from cfg will be used.
	 */
	// query can be string OR object (ie. { query: 'tartu' }). it will be transfered to provider
	this.submit = function(query, type) { // public
		var q = listeners.broadcast("query", query, type);
		
		if(q) {
			query = q;
		}
		
		if (submitting && lastAjax) {
			lastAjax.abort();
		}
		submitting = true;
		
		clear();
		
		// get first providewr if type is not defined
		if(!type) {
			for(var t in cfg.providers) {
				type = t;
				break;
			}
		}
		
		lastProvider = type;
		lastQuery = query;
		
		function parseData(arr) {
			// install quick animation...just for fun
			var old = list.beforeDrawRow;
			if (window.jQuery && cfg.animation) {
				list.beforeDrawRow = function(obj, nodes, i, cnt) {
					if(old) old.apply(list, arguments);
					if((nodes.length > 0) && (nodes[0].tagName)) {
						var jj = jQuery(nodes[0]);
						jj.hide(1)
						window.setTimeout(function() {
							jj.fadeIn(300);
						}, cnt * 100);
					}
				}
			}
			
			setListData(arr);
			
			// remove animation
			list.beforeDrawRow = old;
		}
		
    	showMessage("wait", type);
    	
    	var provider = cfg.providers[type];
    	if (typeof(provider) == "function" || !jQuery) {
    		// assume provider is handling request itself
    		var onSuccess = function(data) {
				parseData(data);
			}
    		var onError = function(txt) {
		    	showMessage("error", type, txt);
			}
			provider(query, listeners, onSuccess, onError);
    	} else {
    		var opts = {
			    url: Regio.Utils.format(provider.uri, {
			    	query: (typeof(query) == 'object') ? encodeURIComponent(query.query) : /*string*/ encodeURIComponent(query)
			    }),
			    error: function(req, desc) {
			    	showMessage("error", type, desc);
			    },
				success: function(xml) {
					parseData(provider.parse(xml));
				}
			};
    		listeners.broadcast("submit", opts);
			lastAjax = jQuery.ajax(jQuery.extend(provider, opts));
		}
	};
	
	/**
	 * Function: queryRowCountInTab
	 * temporary filter just to answer question "how many rows will 
	 * be in particular tab if i will switch to it?"
	 * Does not change searchList contents!
	 * 
	 * Parameters:
	 * tab - tab to look in
	 * 
	 * Returns:
	 * number - count of rows
	 */
	this.queryRowCountInTab = function(tab) { // public
		// temporary filter just to answer question "how many rows will 
		// be in particular tab if i will switch to it?" => see search.queryRowCountInTab
		var tabsFilter = {
			possibleRows: 0,
			start: function() {
				this.possibleRows = 0;
			},
			filter: function(obj, num) {
				var tabAccepts = cfg.tabFilter(tab, obj, num);
				if (tabAccepts || (typeof(tabAccepts) == "undefined")) {
					this.possibleRows++;
				}
				return tabAccepts;
			},
			finish: function() {
			}
		}
		
		list.execute(tabsFilter, function() {});
		
		return tabsFilter.possibleRows;
	};
	
	function clear() {
		lastQuery = undefined;
		lastProvider = undefined;
		
		list.clear() 
		listeners.broadcast("items", []);
		showMessage("clear");
	}
	
	/**
	 * Function: clear
	 * Clear search results list.
	 */
	this.clear = function() {
		clear()
	}; // public
	
	/**
	 * Function: refresh
	 * See <Regio.ListView.refresh>
	 */
	this.refresh = function(tab) { // public
		if ((typeof(tab) == "undefined") || (tab == tabs.curTab)) {
			list.refresh();
		}
	};
	
	/**
	 * Function: nextPage
	 * Go to next page of search results.
	 */
	this.nextPage = function() { paging.nextPage() };
	/**
	 * Function: prevPage
	 * Go to previous page of search results.
	 */
	this.prevPage = function() { paging.prevPage() };
	
	/**
	 * Function: setTab
	 * Set tab. For this to work, _tabFilter_ must be provided in configuration.
	 */
	this.setTab = function() { return tabs.setTab.apply(tabs, arguments) }; // public
	
	/**
	 * Function: getDisplayedData
	 * Returns array of objects (see <Regio.ListView.getData>) of currently displayed data.
	 */
	this.getDisplayedData = function() {
		var rows = [];
		list.execute('current', function(row) {
			rows.push(row);
		});
		return rows;
	}
	
	/**
	 * Function: getCurPage
	 * Returns current page number.
	 */
	this.getCurPage = function() { return paging.curPage > paging.pageCount-1 ? paging.pageCount-1 : paging.curPage+1 };
	/**
	 * Function: getPageCount
	 * Returns current page count.
	 */
	this.getPageCount = function() { return paging.pageCount };
	/**
	 * Function: getCurPage
	 * Returns current tab (see <setTab>).
	 */
	this.getCurTab = function() { return tabs.curTab };
	
	
	/**
	 * Function: getState
	 * Returns state of component. Only for use with <setState>. Internal structure returned by this function can change in future!
	 */
	this.getState = function() {
		var state = {
			data: list.getData(),
			tab: tabs.curTab,
			page: paging.curPage,
			lastQuery: lastQuery,
			lastProvider: lastProvider
		}
		// maybe somone wants to modify state record?
		listeners.call("saveState", state);
		return state;
	}
	
	/**
	 * Function: setState
	 * Restore state of component (see <getState>)
	 * 
	 * Parameters:
	 * state - state returned by <getState>
	 */
	this.setState = function(state) {
		if(state.data.length == 0) {
			// show default message by default
			clear();
		} else {
			setListData(state.data);
		}
		tabs.setTab(state.tab);
		paging.setPage(state.page);
		lastQuery = state.lastQuery;
		lastProvider = state.lastProvider;
		// searchList itself does not handle query input and provider select...so we just broadcast them to siblings
		listeners.broadcast("restoreState", state);
	}
	
	this.listeners = listeners; // public
}


/**
 * Component: Regio.SearchList.Extensions
 * Provides usfull functions extending SearchList functionality (ie. Regio.SearchList.createFromMarkup).
 * 
 * Group: Search
 * 
 * Requires: Regio.SearchList
 * 
 * Optional: Regio.Autocomplete
 *
 * Version 2.20
 *
 * Author:
 * Alexandr Smirnov (alex@regio.ee)
 */



/**
 * Function: Regio.SearchList.createFromMarkup
 * Create and initialize Regio.SearchList for specific DOM elements within specified parent (div). 
 * This function looks for predefined HTML class names and match them with specific functionality.
 * 
 * Parameters:
 * div - optional, parent DOM element where to look for markup (defaults to document)
 * cfg - <Regio.SearchList> configuration
 * cfg.autosubmit -  can be set to false to make available to use diffrent search providers
 * 
 * Looks for following HTML/CSS selectors within specified parent:
 * .search_container - DOM element that will be filled with search results (see <Regio.SearchList> div)
 * .search_form - FORM with INPUT[name=q] which will be used to submit search
 * .search_next_page - anchor/button that will be used for going to next page (this element will be hidden if current page is last)
 * .search_prev_page - anchor/button that will be used for going to previous page (this element will be hidden if current page is first)
 * .search_clear
 * .search_message - all elements with this class will be hidden before new message is displayed
 * .search_message_empty, .search_message_wait, .search_message_error, .search_message_clear - element with on of this classes will be displayed for specific message type
 * .search_current_page - contents of this element will be replaced with current page number
 * .search_page_count - contents of this element will be replaced with page count
 */
// div - is a top div in which this function will operate (its not ListView div!) By default div = document
Regio.SearchList.createFromMarkup = function (div, cfg) {
	if (!div) div = document;
	
	var j = $('.search_container', div);
	if (j.length <= 0) {
		throw new Error('createSearchFromMarkup, element with class = "search_container" must exists within provided div');
	}
	var searchDiv = j[0];
	
	var search = new Regio.SearchList(searchDiv, cfg);
	
	var lastClickedRowData;
	
	// search results bindings
	search.listeners.add("row", function(arr, childs) {
		$('.search_clickable_title', childs).click(function() {
			lastClickedRowData = arr;
			search.listeners.broadcast("rowClick", arr, childs);
			return false;
		});
	});
	
	// search events
	search.listeners
	.add("saveState", function(state) {
		state.lastClickedRowData = lastClickedRowData;
	})
	.add("restoreState", function(state) {
		$('.search_form input[name=q]', div).val(typeof(state.lastQuery) == "object" ? state.lastQuery.query : state.lastQuery);
		lastClickedRowData = state.lastClickedRowData;
	})
	.add("query", function(q, type) {
		$('.search_form input[name=q]', div).val(typeof(q) == "object" ? q.query : q);
	})
	.add("message", function(type) {
		$('.search_message', div).hide();
		$('.search_message_'+type, div).show();
	})
	.add("items", function(arr) {
		lastClickedRowData = undefined;
		$('.search_message', div).hide();
		$('.search_results_count', div).text(arr.length);
	})
	.add("page", function(cur, total) {
		$('.search_current_page', div).text(cur);
		$('.search_page_count', div).text(total);
		$('.search_prev_page', div)[cur > 1 ? "show" : "hide"]();
		$('.search_next_page', div)[cur < total ? "show" : "hide"]();
	});
	
	// update state of pager
	search.listeners.broadcast("page", 0, 0);
	
	if (!cfg.noSubmitBinding) {
		// static template bindings
		$('.search_form', div).submit(function(e){
			if(!e.cancel) {
				search.submit(this.q.value, e.providerName);
			}
			return false;
		});
	}
	$('.search_form', div).bind("reset", function() {
		search.clear();
		return true;
	});
	$('.search_clear', div).click(function()      { search.clear(); return false; });
	$('.search_prev_page', div).click(function()  { search.prevPage(); return false; });
	$('.search_next_page', div).click(function()  { search.nextPage(); return false; });
	
	// autocomplete
	if (cfg.autocomplete) {
		if (!Regio.Autocomplete) {
			throw "Regio.SearchList.Extensions, for autocomplete to work, you have to include Regio.Autocomplete";
		}
		if (typeof(cfg.autocomplete) == "boolean") { // true
			cfg.autocomplete = {};
		}
		Regio.Utils.applyDefaults(cfg.autocomplete, {
			uri: "geoCodeBridge.php?q={query}&matchOptions=AUTOCOMPLETE&max=10&output=ac"
		});
		var $input = $('.search_form input[name=q]', div);
		Regio.Autocomplete($input, cfg.autocomplete.uri, 
			jQuery.extend(cfg.autocomplete, {
				onItemSelect: function() {
					$('.search_form', div).submit();
				}
			})
		);
		search.listeners.add("submit", function() {
			if (($input.length > 0) && $input[0].autocompleter && $input[0].autocompleter.hideResultsNow) {
				$input[0].autocompleter.hideResultsNow();
				$input[0].value = $input[0].value; // deselect
			}
		});
	}
	
	return search;
}

/**
 * Const: Regio.SearchList.AutocompleteJGCRemote
 * Autocomplete comfiguration for Regio.SearchList (Regio JGC by use of JSONP protocol)
 * 
 * Usage:
 * Search = Regio.SearchList.createFromMarkup(document, {
 *   rowsPerPage: 15,
 *   providers: {
 *     RegioJGC: Regio.SearchList.createProviderRegioJGC()
 *   },
 *   animation: true,
 *   autocomplete: Regio.SearchList.AutocompleteJGCRemote
 * });
 */

Regio.SearchList.createAutocompleteRegioJGCRemote = function(cfg) {
	if(!cfg) cfg = {}; //Jevgeni: Made uri confitable
	if(!cfg.uri) cfg.uri = 'http://demo.regio.ee/jsonp/geoCodeBridge.php?user={user}&q={query}&matchOptions=AUTOCOMPLETE&max=10' //Jevgeni
	return {
		uri: Regio.Utils.format(cfg.uri, cfg),
		dataType: 'jsonp',
		parse: function(data) {
			var res = [];
			for(var i in data) {
				res.push([data[i].address]);
			}
			return res;
		}
	}
}


/**
 * Component: Regio.GCAdmin.SearchListAdapter
 * Adapter for GCAdmin component.
 * 
 * Group: GCAdmin
 * 
 * Requires: Regio.GCAdmin, Regio.SearchList, Regio.SearchList.Extensions
 * 
 * See also:
 * <Regio.SearchList>
 * 
 * Author: 
 * Alexandr Smirnov (alex@regio.ee)
 */



/*
 * Function: Regio.GCAdmin.addSearchAdapter
 * Creates and assigns new SearchAdapter (see <Regio.SearchList>) to <Regio.GCAdmin> component.
 * If user clicks one of search results, then "result" event fired (see <Regio.GCAdmin>) with type = "ADDRESS".
 * 
 * Parameters:
 * gca - gcAdmin component
 * cfg - Configuration
 * 
 * Configuration:
 * div - string, element id within <Regio.GCAdmin> container to be used by <Regio.SearchList> (default see <Regio.SearchList>)
 * ... - all other possible configuration parameters that <Regio.SearchList> accepts
 * 
 * See also:
 * <Regio.GCAdmin>, <Regio.SearchList>
 * 
 * Uses following HTML elements/CSS selectors (if available) in GCAdmin container:
 * see <Regio.SearchList.Extensions>
 */
Regio.GCAdmin.addSearchAdapter = function(gca, cfg) {
	var gcaDiv = cfg.div ? cfg.div : gca.getDiv();
	var gcaCfg = gca.getConfig();
	var gcaListeners = gca.listeners;
	
	if ($('.search_container', gcaDiv).length <= 0) {
		throw 'Regio.GCAdmin, element with selector "search_container" missing (need it for embedding SearchAdapter)';
	}
	
	var search = Regio.SearchList.createFromMarkup(gcaDiv, cfg);
	gca.search = search;
	
	// search results bindings
	search.listeners
	.add("row", function(arr, childs) {
		$('.search_clickable_title', childs).click(function() {
			gcaListeners.broadcast("result", "ADDRESS", arr);
			return false;
		});
	})
	.add("items", function(arr) {
		// center to first result
		if (arr.length > 0) {
			gcaListeners.broadcast("result", "ADDRESS", arr[0]);
		}
	})
	.add("query", function() {
		if (gcaCfg.hideShow && !gca.isOpened()) {
			gca.open();
		}
	});
	
	// add gcAdmin component callbacks
	gcaListeners
	.add("clear", function() {
		search.clear();
	})
	.add("submit", function(q /*...*/) {
		search.submit.apply(search, arguments);
	});
	
	return search;
}


/**
 * Component: Regio.GCAdmin.ReverseGeoCode
 * ReverseGeoCoder for GCAdmin component
 * 
 * Group: GCAdmin
 * 
 * Requires: Regio.Utils, Regio.GCAdmin, MapCat.Listeners, jQuery
 * 
 * Version: 1.4
 * 
 * Author: 
 * Alexandr Smirnov (alex@regio.ee)
 */



/*
 * Function: Regio.GCAdmin.addReverseGeoCode
 * Creates ReverseGeoCoding object and assigns it to GCAdmin component. This object intercepts "done" event (see <Regio.GCAdmin>), 
 * does ReverseGeoCode lookup and fires "success" event. See Events, for details. 
 * 
 * Can intercept "done" events with types: "ADDRESS", "POINT", "LINESTRING" or "POLYGON" (see <Regio.GCAdmin.addSearchAdapter>, <Regio.GCAdmin.addFlashTileAdapter>)
 * 
 * For "LINESTRING" and "POLYGON" result types, first point is taken to RGC (!). 
 * 
 * Parameters:
 * gca - gcAdmin component
 * cfg - Configuration
 * 
 * Configuration:
 * url - url template of RGC bridge (default "rGeoCodeBridge.php?x={x}&y={y}")
 * 
 * See also:
 * <Regio.GCAdmin>, <Regio.GCAdmin.addSearchAdapter>, <Regio.GCAdmin.addFlashTileAdapter>
 * 
 * Uses following HTML elements/CSS selectors (if available) in GCAdmin container:
 * none
 */
Regio.GCAdmin.addReverseGeoCode = function(gca, cfg) {
	var gcaListeners = gca.listeners;
	var listeners = new MapCat.Listeners();
	if (!cfg) cfg = {};
	
	Regio.Utils.applyDefaults(cfg, {
		requestType: 'GET',
		dataType: 'json',
		url: 'rGeoCodeBridge.php?x={x}&y={y}',
		ignoreTypes: []
	});
	
	gcaListeners.add("done", function(type, data) {
		var x, y;
		if($(cfg.ignoreTypes).index(type) == -1) {
			switch(type) {
				case "ADDRESS":
					x = data.e, y = data.n;
					break;
				case "POINT":
				case "LINESTRING":
					x = data.geo[0].e, y = data.geo[0].n;
					break;
				case "POLYGON":
					x = data.geo[0][0].e, y = data.geo[0][0].n;
					break;
			}
			listeners.broadcast("resolve", { x: x, y: y });
		}
	});
	
	function resolve(data) {
		/*
		 * Event: resolveBegin
		 * Indicates that ajax request has started
		 * 
		 * Parameters:
		 * data - data object passed with <resolve> event
		 */
		listeners.broadcast("resolveBegin", data);
		
		var opts = {
			type: cfg.requestType,
			dataType: cfg.dataType,
			url: Regio.Utils.format(cfg.url, jQuery.extend(data, cfg.params)),
			error: function(req, desc) {
				// just do nothing
				/*
				 * Event: resolveEnd
				 * Indicates that ajax request has completed (either success OR error)
				 * 
				 * Parameters:
				 * status - "success" or "error"
				 * data - either data returned by RGC service (in case of success) OR string describing error
				 */
				listeners.broadcast("resolveEnd", "error", desc);
				/*
				 * Event: error
				 * Indicates that RGC request has failed
				 * 
				 * Parameters:
				 * desc - string, describing error
				 */
				listeners.broadcast("error", desc);
			},
			success: function(json) {
				listeners.broadcast("resolveEnd", "success", json);
				/*
				 * Event: success
				 * Indicates that RGC request was successfull
				 * 
				 * Parameters:
				 * json - object, data returned
				 * data - object passed to <resolve> event
				 */
				listeners.broadcast("success", json, data);
			}
		}
		
		jQuery.ajax(opts);
	}
	
	/*
	 * Event: resolve
	 * Listens for this event to start reverseGeoCode request. 
	 * 
	 * Parameters:
	 * data - data object to be passed to RGC service (usually contains "e" and "n" properties: {e: 111, n: 222})
	 */
	listeners.add("resolve", resolve);
	
	return {
		listeners: listeners
	};
}


/**
 * Component: jQuery.colorDialog
 * Combination of jQuery.ui.dialog and farbtastic. Binds to INPUT control. Created primarly for controls.digitizing
 * 
 * Requires: jQuery, ui.core.js, ui.dialog.js, farbtastic.js
 *
 * Optional: ui.draggable.js
 *
 * Group: jQuery
 * 
 * Author:
 * Alexandr Smirnov (alex@regio.ee)
 * 
 * Usage: 
 * $('INPUT.color_dialog').colorDialog([cfg]);
 * 
 * Configuration:
 * width - width of dialog (default "225px")
 * height - height of dialog (default "255px")
 * position - position of dialog. see jquery.ui.dialog docs. default right below input element.
 * showInput - do not hide input (default false)
 */

;(function($) {

$.fn.colorDialog = function(cfg) {
	cfg = cfg || {};
	var args = arguments;
	return this.each(function() {
		var $this = $(this);
		
		// some command issued _after_ creation ("close", "destroy", etc..)?
		var d = $this.data("colorDialog");
		if(d) {
			d.dialog.apply(d, args);
			return;
		}
		
		var $box;
		var changing = false;
		
		var $wheel = $('<div/>');
		var $div = $('<div class="color_dialog"/>').append($wheel);
		
		var farb = $.farbtastic($wheel, function(color) {
			$box.css("background", color);
			$this.css("background", color);
			changing = true;
			$this.val(color).change();
			changing = false;
		});
		
		$div.dialog({
			autoOpen: false,
			width: cfg.width || "225px",
			height: cfg.height || "255px",
			title: $this.attr("title"),
			open: function() {
				$box.addClass("selecting_color");
			},
			close: function() {
				$box.removeClass("selecting_color");
			}
		});
		
		$this.data("colorDialog", $div);
		
		$box = $('<span class="color_box" />').insertAfter(this)/*.html("&nbsp;")*/.append($this);
		
		$box.add($this).click(function() {
			if($box.is(".selecting_color")) {
				$div.dialog("close");
			} else {
				$div.data("position.dialog", cfg.position || calcPosition($this[0]));
				$div.dialog("open");
			}
		});
		
		$this.change(function() {
			if(!changing) {
				farb.setColor(normalizeColorString($this.val()));
			}
		});
		
		var initialColor = normalizeColorString($this.val());
		farb.setColor(initialColor);
		
		if(!cfg.showInput) {
			$this.css("visibility", "hidden");
		}
		
		function calcPosition(obj) {
			var curleft = 0;
			var curtop = 0;
			if(obj.offsetParent)
				while(1) {
					curleft += obj.offsetLeft;
					curtop += obj.offsetTop;
					if(!obj.offsetParent)
					break;
					obj = obj.offsetParent;
				}
			else if(obj.x && obj.y) {
				curleft += obj.x;
				curtop += obj.y;
			}
			return [curleft, curtop+$this.height()+5];
		}
		
		function normalizeColorString(color) {
			color = (''+color);
			if(((color.length == 3) || (color.length == 6)) && (color.substr(0,1) != "#")) {
				color = "#"+color;
			};
			return color;
		}
	});
};

})(jQuery);


/**
 * Component: jQuery.colorPicker
 * ColorPicker component. Requires jQuery and jQuery Farbtastic
 * 
 * Requires: jQuery, jQuery.farbtastic
 * 
 * Group: jQuery
 * Version: 1.5
 * 
 * Author:
 * Jevgeni Kiski (jevgeni@regio.ee)
 * 
 * Usage: 
 * $('#picker').colorPicker('.colorwell',[cfg]);
 * or
 * $.colorPicker('#picker', '.colorwell', [cfg]);
 * 
 * Works with <div>, <span>, <a>, <td>, <input>, <select>(limited), <textarea>
 * 
 * Configuration:
 * showHide - do show or hide itself (default true)
 * colorize - apply color to containers background (default true)
 * text - print text inside the container(default false)
 * textSameColor - apply same text and background colors, used to hide text in fields (default false)
 * callback - callback function (default null)
 * defaultColor - default color for fields if none is preconfigured (default '')
*/



jQuery.fn.colorPicker = function(linkTo, cfg){
    return jQuery.colorPicker(this, linkTo, cfg);
}

jQuery.colorPicker = function(container, linkTo, userConf){
    userConf = userConf ||
    {};
    
    var _htmlElements = 'div,span,a,td';
    var _valueElements = 'input, select, textarea';
    
    var cfg = $.extend({}, jQuery.colorPicker.defaultConf, userConf);
    
    var f = $.farbtastic(container);
    var p = $(container);
    var me = this;
	var callbacking = false;
    
    //Object functions >
    this.show = function(){
        if (typeof cfg.showHide == 'object') {
            cfg.showHide.show();
        }
        else 
            if (typeof cfg.showHide == 'boolean') {
                p.show();
            }
    }
    this.hide = function(){
        if (typeof cfg.showHide == 'object') {
            cfg.showHide.hide();
        }
        else 
            if (typeof cfg.showHide == 'boolean') {
                p.hide();
            }
    }
    this.setColor = f.setColor;
    // < Object functions
    
    if (cfg.showHide) 
        this.hide();
    $(linkTo).each(function(){
        this.color = getInitColor(this) || cfg.defaultColor; //default colors
        if (this.color) {
            updateColor(this, this.color);
        }
    }).click(function(){
        var elem = this;
		f.linkTo(function(color){
			if(elem.color == color) return;
            updateColor(elem, color);
			callbacking = true;
			doCallback(elem, color);
			callbacking = false;
        });
		
        f.setColor(this.color);
		
        if (cfg.showHide) 
            me.show();
    }).change(function(){
			if(callbacking) return;
			if(this.color == color) return;
			
			var color = getInitColor(this);
			if (checkHexColor(color)) {
				this.color = color;
				f.setColor(color);
				if (cfg.colorize) 
					applyColor(this, color);
				if (cfg.text) {
					$(this).css('color', cfg.textSameColor ? color : f.hsl[2] > 0.5 ? '#000' : '#fff');
				}
			}
    });
    
    function fixColor(color){
        if (color) {
            color = color.charAt(0) == '#' ? color : color = '#' + color;
        }
        return color;
    }
    
    function updateColor(elem, color){
        elem.color = color;
        if (cfg.colorize) 
            applyColor(elem, color);
        if (cfg.text) {
            addText(elem, color);
            $(elem).css('color', cfg.textSameColor ? color : f.hsl[2] > 0.5 ? '#000' : '#fff');
        }
    }
	
	function doCallback(elem, color){
		changing = true;
		$(elem).change();
        if (typeof cfg.callback == 'function') 
            cfg.callback(elem, color);
		changing = false;
	}
    
    function applyColor(elem, color){
        $(elem).css('background-color', color);
    }
    function addText(elem, color){
        if ($(elem).is(_htmlElements)) {
			if($(elem).html()!=color) $(elem).html(color);
        }
        else 
            if ($(elem).is(_valueElements)) {
                if($(elem).val()!=color) $(elem).val(color);
            }
    }
    function getInitColor(elem){
        var color = null;
        if ($(elem).is(_htmlElements)) {
            color = $(elem).html();
        }
        else 
            if ($(elem).is(_valueElements)) {
                color = $(elem).val();
            }
		if (!checkHexColor(color)) {
			try {
				color = convertColor(elem.style.backgroundColor);
			} 
			catch (e) {
			}
		}
        return fixColor(checkHexColor(color) ? color : null);
    }
    function checkHexColor(color){
        var reg = new RegExp('^#?([a-f]|[A-F]|[0-9]){3}(([a-f]|[A-F]|[0-9]){3})?$');
        return reg.test(color);
    }
    function convertColor(color){
        if (checkHexColor(color)) 
            return color;
        var reg = new RegExp('^rgb\\(([0-9]+), ([0-9]+), ([0-9]+)\\)$');
        var rgb = reg.exec(color);
        if (rgb) {
            var r = Math.round(rgb[1]);
            var g = Math.round(rgb[2]);
            var b = Math.round(rgb[3]);
            return '#' + (r < 16 ? '0' : '') + r.toString(16) +
            (g < 16 ? '0' : '') +
            g.toString(16) +
            (b < 16 ? '0' : '') +
            b.toString(16);
        }
        return null;
    }
    
    return this;
}

jQuery.colorPicker.defaultConf = {
    showHide: true,
    colorize: true,
    text: false,
    textSameColor: false,
    callback: null,
    defaultColor: ''
}



/**
 * Component: jQuery.iconsList
 * Creates list of icons for digitizing controls (myMap). Must be inited BEFORE Regio.Controls.Digitizing!
 * 
 * Group: Controls
 * 
 * Requires: jQuery
 * 
 * Version: 1.0
 * 
 * Group: jQuery
 * 
 * Author: 
 * Alexandr Smirnov (alex@regio.ee)
 */

;(function($) {

$.fn.iconList = function(colorField) {
	if(!colorField) {
		colorField = $(".feature_style[feature=point][styleName=pointColor]");
	}
	return this.each(function() {
		var $this = $(this);
		
		$this.addClass("iconsList");
		
		var $images = $('img', $this);
		var $anchors = $.map($images, function(img) {
			var $img = $(img);
			return $('<a href="#" class="feature_style" feature="point" stylename="pointIcon" stylevalue="'+$img.attr("symbol")+'" />').click(function() {
				$(colorField).val($img.attr("symbolColor"));
				return false;
			}).append($img)[0];
		});
		$this.empty().append($anchors);
	});
};

})(jQuery);


/**
 * Component: jQuery.rangeSlider
 * Can be used in replacement for INPUT. Created primarly for controls.digitizing
 * 
 * Requires: jQuery, ui.core.js, ui.slider.js
 *
 * Author:
 * Alexandr Smirnov (alex@regio.ee)
 * 
 * Group: jQuery
 * 
 * Usage: 
 * (code)
 * <input step="1" max="100" min="0" value="50" />
 * ...
 * $('INPUT').rangeSlider();
 * (end)
 */
 
$.fn.rangeSlider = function() {
	return this.each(function() {
		var changing = false;
		var $this = $(this);
		var $slider = $("<div><div class='ui-slider-handle'></div></div>").insertAfter(this).slider({
			minValue: $this.attr("min"),
			maxValue: $this.attr("max"),
			stepping: $this.attr("step"),
			startValue: $this.attr("value"),
			slide: function(e, ui) {
				$this.attr("value", Math.round(ui.value));
				changing = true;
				$this.change();
				changing = false;
			}
		});
		$this.change(function() {
			if(!changing) {
				$slider.data("slider").moveTo($this.val());
			}
		});
		$this.hide();
	});
};



/**
 * Component: Regio.Autocomplete
 * Adds autocomplete fuctionality to any field
 * 
 * Requires: Regio.Utils, jQuery
 * 
 * Version: 1.2
 * 
 * Authors:
 * - modified by Alexandr Smirnov (alex@regio.ee)
 * - http://www.pengoworks.com/workshop/jquery/autocomplete.htm
 */

if(1); // dummy



(function () {

var autocomplete = function(input, options) {
	// Create a link to self
	var me = this;

	// Create jQuery object for input element
	var $input = $(input).attr("autocomplete", "off");

	// Apply inputClass if necessary
	if (options.inputClass) {
		$input.addClass(options.inputClass);
	}

	// Create results
	var results = document.createElement("div");

	// Create jQuery object for results
	// var $results = $(results);
	var $results = $(results).hide().addClass(options.resultsClass).css("position", "absolute");
	if( options.width > 0 ) {
		$results.css("width", options.width);
	}

	// Add to body element
	$("body").append(results);

	input.autocompleter = me;

	var timeout = null;
	var prev = "";
	var active = -1;
	var cache = {};
	var keyb = false;
	var hasFocus = false;
	var lastKeyPressCode = null;
	var mouseDownOnSelect = false;
	var hidingResults = false;

	// flush cache
	function flushCache(){
		cache = {};
		cache.data = {};
		cache.length = 0;
	};

	// flush cache
	flushCache();

	// if there is a data array supplied
	if( options.data != null ){
		var sFirstChar = "", stMatchSets = {}, row = [];

		// no url was specified, we need to adjust the cache length to make sure it fits the local data store
		if( typeof options.uri != "string" ) {
			options.cacheLength = 1;
		}

		// loop through the array and create a lookup structure
		for( var i=0; i < options.data.length; i++ ){
			// if row is a string, make an array otherwise just reference the array
			row = ((typeof options.data[i] == "string") ? [options.data[i]] : options.data[i]);

			// if the length is zero, don't add to list
			if( row[0].length > 0 ){
				// get the first character
				sFirstChar = row[0].substring(0, 1).toLowerCase();
				// if no lookup array for this character exists, look it up now
				if( !stMatchSets[sFirstChar] ) stMatchSets[sFirstChar] = [];
				// if the match is a string
				stMatchSets[sFirstChar].push(row);
			}
		}

		// add the data items to the cache
		for( var k in stMatchSets ) {
			// increase the cache size
			options.cacheLength++;
			// add to the cache
			addToCache(k, stMatchSets[k]);
		}
	}

	$input
	.keydown(function(e) {
		// track last key pressed
		lastKeyPressCode = e.keyCode;
		switch(e.keyCode) {
			case 38: // up
				e.preventDefault();
				moveSelect(-1);
				break;
			case 40: // down
				e.preventDefault();
				moveSelect(1);
				break;
			case 9:  // tab
			case 13: // return
				if( selectCurrent() ){
					// make sure to blur off the current field
					$input.get(0).blur();
					e.preventDefault();
				}
				break;
			default:
				active = -1;
				if (timeout) clearTimeout(timeout);
				timeout = setTimeout(function(){onChange();}, options.delay);
				break;
		}
	})
	.focus(function(){
		// track whether the field has focus, we shouldn't process any results if the field no longer has focus
		hasFocus = true;
	})
	.blur(function() {
		// track whether the field has focus
		hasFocus = false;
		if (!mouseDownOnSelect) {
			hideResults();
		}
	});

	hideResultsNow();

	function onChange() {
		// ignore if the following keys are pressed: [del] [shift] [capslock]
		if( lastKeyPressCode == 46 || (lastKeyPressCode > 8 && lastKeyPressCode < 32) ) return $results.hide();
		var v = $input.val();
		if (v == prev) return;
		prev = v;
		if (v.length >= options.minChars) {
			$input.addClass(options.loadingClass);
			requestData(v);
		} else {
			$input.removeClass(options.loadingClass);
			$results.hide();
		}
	};

 	function moveSelect(step) {

		var lis = $("li", results);
		if (!lis) return;

		active += step;

		if (active < 0) {
			active = 0;
		} else if (active >= lis.size()) {
			active = lis.size() - 1;
		}

		lis.removeClass("ac_over");

		$(lis[active]).addClass("ac_over");

		// Weird behaviour in IE
		// if (lis[active] && lis[active].scrollIntoView) {
		// 	lis[active].scrollIntoView(false);
		// }

	};

	function selectCurrent() {
		var li = $("li.ac_over", results)[0];
		if (!li) {
			var $li = $("li", results);
			if (options.selectOnly) {
				if ($li.length == 1) li = $li[0];
			} else if (options.selectFirst) {
				li = $li[0];
			}
		}
		if (li) {
			selectItem(li);
			return true;
		} else {
			return false;
		}
	};

	function selectItem(li) {
		if (!li) {
			li = document.createElement("li");
			li.extra = [];
			li.selectValue = "";
		}
		var v = $.trim(li.selectValue ? li.selectValue : li.innerHTML);
		input.lastSelected = v;
		prev = v;
		$results.html("");
		$input.val(v);
		hideResultsNow();
		if (options.onItemSelect) {
			setTimeout(function() { options.onItemSelect(li) }, 1);
		}
	};

	// selects a portion of the input string
	function createSelection(start, end){
		// get a reference to the input element
		var field = $input.get(0);
		if( field.createTextRange ){
			var selRange = field.createTextRange();
			selRange.collapse(true);
			selRange.moveStart("character", start);
			selRange.moveEnd("character", end);
			selRange.select();
		} else if( field.setSelectionRange ){
			field.setSelectionRange(start, end);
		} else {
			if( field.selectionStart ){
				field.selectionStart = start;
				field.selectionEnd = end;
			}
		}
		field.focus();
	};

	// fills in the input box w/the first match (assumed to be the best match)
	function autoFill(sValue){
		// if the last user key pressed was backspace, don't autofill
		if( lastKeyPressCode != 8 ){
			// fill in the value (keep the case the user has typed)
			$input.val($input.val() + sValue.substring(prev.length));
			// select the portion of the value not typed by the user (so the next character will erase)
			createSelection(prev.length, sValue.length);
		}
	};

	function showResults() {
		// get the position of the input field right now (in case the DOM is shifted)
		var pos = findPos(input);
		// either use the specified width, or autocalculate based on form element
		var iWidth = (options.width > 0) ? options.width : $input.width();
		// reposition
		$results.css({
			width: parseInt(iWidth) + "px",
			top: (pos.y + input.offsetHeight) + "px",
			left: pos.x + "px"
		}).show();
	};

	function hideResults() {
		if (timeout) clearTimeout(timeout);
		timeout = setTimeout(hideResultsNow, 200);
	};

	function hideResultsNow() {
		if (hidingResults) {
			return;
		}
		hidingResults = true;
	
		if (timeout) {
			clearTimeout(timeout);
		}
		
		var v = $input.removeClass(options.loadingClass).val();
		
		if ($results.is(":visible")) {
			$results.hide();
		}
		
		if (options.mustMatch) {
			if (!input.lastSelected || input.lastSelected != v) {
				selectItem(null);
			}
		}

		hidingResults = false;
	};

	function receiveData(q, data) {
		if (data) {
			$input.removeClass(options.loadingClass);
			results.innerHTML = "";

			// if the field no longer has focus or if there are no matches, do not display the drop down
			if( !hasFocus || data.length == 0 ) return hideResultsNow();

			if ($.browser.msie) {
				// we put a styled iframe behind the calendar so HTML SELECT elements don't show through
				$results.append(document.createElement('iframe'));
			}
			results.appendChild(dataToDom(data));
			// autofill in the complete box w/the first match as long as the user hasn't entered in more data
			if( options.autoFill && ($input.val().toLowerCase() == q.toLowerCase()) ) autoFill(data[0][0]);
			showResults();
		} else {
			hideResultsNow();
		}
	};
	
	function parseData(data) {
		if (!data) return null;
		if (options.parse) {
			return options.parse(data);
		}
		
		var parsed = [];
		var rows = data.split(options.lineSeparator);
		for (var i=0; i < rows.length; i++) {
			var row = $.trim(rows[i]);
			if (row) {
				parsed[parsed.length] = row.split(options.cellSeparator);
			}
		}
		return parsed;
	};

	function dataToDom(data) {
		var ul = document.createElement("ul");
		var num = data.length;

		// limited results to a max number
		if( (options.maxItemsToShow > 0) && (options.maxItemsToShow < num) ) num = options.maxItemsToShow;

		for (var i=0; i < num; i++) {
			var row = data[i];
			if (!row) continue;
			var li = document.createElement("li");
			if (options.formatItem) {
				li.innerHTML = options.formatItem(row, i, num);
				li.selectValue = row[0];
			} else {
				li.innerHTML = row[0];
				li.selectValue = row[0];
			}
			var extra = null;
			if (row.length > 1) {
				extra = [];
				for (var j=1; j < row.length; j++) {
					extra[extra.length] = row[j];
				}
			}
			li.extra = extra;
			ul.appendChild(li);
			
			$(li).hover(
				function() { $("li", ul).removeClass("ac_over"); $(this).addClass("ac_over"); active = $("li", ul).indexOf($(this).get(0)); },
				function() { $(this).removeClass("ac_over"); }
			).click(function(e) { 
				e.preventDefault();
				e.stopPropagation();
				selectItem(this)
			});
			
		}
		$(ul).mousedown(function() {
			mouseDownOnSelect = true;
		}).mouseup(function() {
			mouseDownOnSelect = false;
		});
		return ul;
	};

	function requestData(q) {
		if (!options.matchCase) q = q.toLowerCase();
		var data = options.cacheLength ? loadFromCache(q) : null;
		// recieve the cached data
		if (data) {
			receiveData(q, data);
		// if an AJAX url has been supplied, try loading the data now
		} else if( (typeof options.uri == "string") && (options.uri.length > 0) ){
			var success = function(data) {
				data = parseData(data);
				addToCache(q, data);
				receiveData(q, data);
			}
			$.ajax(jQuery.extend({
				url: makeUrl(q),
				type: 'GET',
				success: success
			}, options));
		// if there's been no data found, remove the loading class
		} else {
			$input.removeClass(options.loadingClass);
		}
	};

	function makeUrl(q) {
		var url = Regio.Utils.format(options.uri, { query: encodeURIComponent(q) });
		for (var i in options.extraParams) {
			url += "&" + i + "=" + encodeURIComponent(options.extraParams[i]);
		}
		return url;
	};

	function loadFromCache(q) {
		if (!q) return null;
		if (cache.data[q]) return cache.data[q];
		if (options.matchSubset) {
			for (var i = q.length - 1; i >= options.minChars; i--) {
				var qs = q.substr(0, i);
				var c = cache.data[qs];
				if (c) {
					var csub = [];
					for (var j = 0; j < c.length; j++) {
						var x = c[j];
						var x0 = x[0];
						if (matchSubset(x0, q)) {
							csub[csub.length] = x;
						}
					}
					return csub;
				}
			}
		}
		return null;
	};

	function matchSubset(s, sub) {
		if (!options.matchCase) s = s.toLowerCase();
		var i = s.indexOf(sub);
		if (i == -1) return false;
		return i == 0 || options.matchContains;
	};

	this.flushCache = function() {
		flushCache();
	};

	this.hideResultsNow = function() {
		hideResultsNow();
	};

	this.setExtraParams = function(p) {
		options.extraParams = p;
	};

	function addToCache(q, data) {
		if (!data || !q || !options.cacheLength) return;
		if (!cache.length || cache.length > options.cacheLength) {
			flushCache();
			cache.length++;
		} else if (!cache[q]) {
			cache.length++;
		}
		cache.data[q] = data;
	};

	function findPos(obj) {
		var curleft = obj.offsetLeft || 0;
		var curtop = obj.offsetTop || 0;
		while (obj = obj.offsetParent) {
			curleft += obj.offsetLeft
			curtop += obj.offsetTop
		}
		return {x:curleft,y:curtop};
	}
}

/*
 Function: Regio.Autocomplete
 Add auto complete to given div
 
 Parameters:
 div - HTML element (can be used with jQuery as $('css selector'))
 uri - url template for autocomplete source ("{query}" substring will be substituted with input string)
 options - configuration
 
 Configuration:
 type, dataType, etc. - see docs for jquery.ajax function
 formatItem, matchContains, maxItemsToShow, etc. - see docs for jquery.autocomplete
 parse - function, that will be called to parse results from autocomplete source. 
 This function should return array of arrays (!). ie. [['row1-field1', 'row1-field2'], ['row2-field1', 'row2-field2']]. 
 By default, only first field from each row will be shown, but you can define formatItem function and customize output.
*/
Regio.Autocomplete = function(div, uri, options, data) {
	// Make sure options exists
	options = options || {};
	// Set url as option
	options.uri = uri;
	// set some bulk local data
	options.data = ((typeof data == "object") && (data.constructor == Array)) ? data : null;

	// Set default values for required options
	options = $.extend({
		inputClass: "ac_input",
		resultsClass: "ac_results",
		lineSeparator: "\n",
		cellSeparator: "|",
		minChars: 1,
		delay: 400,
		matchCase: 0,
		matchSubset: false,
		matchContains: false,
		cacheLength: 25,
		mustMatch: 0,
		extraParams: {},
		loadingClass: "ac_loading",
		selectFirst: false,
		selectOnly: false,
		maxItemsToShow: -1,
		autoFill: false,
		width: 0
	}, options);
	options.width = parseInt(options.width, 10);

	$(div).each(function() {
		var input = this;
		new autocomplete(input, options);
	});
}

/*jQuery.fn.autocompleteArray = function(data, options) {
	return this.autocomplete(null, options, data);
}*/

jQuery.fn.indexOf = function(e){
	for( var i=0; i<this.length; i++ ){
		if( this[i] == e ) return i;
	}
	return -1;
};

})();


/**
 * Component: Regio.Cookies
 * Independant cookies library. Provides functionality to create and delete browser cookies.
 * 
 * Version: 09.04.08
 * 
 * Author: 
 * Alexandr Smirnov (alex@regio.ee)
 */

if (typeof(Regio) == "undefined") {
	Regio = {};
}

Regio.Cookies = {
	/**
	 * Function: Regio.Cookies.get
	 * Get cookie value
	 * 
	 * Parameters:
	 * name - string, cookie name
	 */
	get: function(c_name) {
		if (document.cookie.length>0) {
		  c_start=document.cookie.indexOf(c_name + "=")
		  if (c_start!=-1) { 
		    c_start=c_start + c_name.length+1;
		    c_end=document.cookie.indexOf(";",c_start);
		    if (c_end==-1) c_end=document.cookie.length;
		    return unescape(document.cookie.substring(c_start,c_end));
		    }
		  }
		return "";
	},
	/**
	 * Function: Regio.Cookies.put
	 * Create new cookie
	 * 
	 * Parameters:
	 * name - string, cookie name
	 * value - string, cookie value
	 * expireDays - number, count of days when cookie will expire
	 */
	put: function(c_name,value,expiredays) {
		var exdate = new Date();
		exdate.setDate(exdate.getDate()+expiredays);
		document.cookie=c_name+"="+escape(value)+((expiredays==null) ? "" : ";expires="+exdate.toGMTString());
	}
};


/**
 * Component: Regio.Coords.Convert
 * EXPERIMENT component for converting coordinates.
 * 
 * Author:
 * Alexandr Sminrov (alex@regio.ee)
 */

if(true);

(function() {

if(!window.Regio) {
	Regio = {};
}

if(!window.Regio.Coords) {
	Regio.Coords = {};
}

Regio.Coords.Convert = function(x, y, from, to) {
	var est = ['EST'];
	var wgs = ['WGS', 'WGS84'];
	
	function is(v, a) {
		for (var i=0; i < a.length; i++) {
		    if(a[i] == v) return true;
		}
		return false;
	}
	
	from = from.toUpperCase();
	to = to.toUpperCase();
	var res = false;
	
	if((is(from, est) && is(to, est)) || (is(from, wgs) && is(to, wgs))) {
		res = [x, y];
	} else
	if(is(from, est) && is(to, wgs)) {
		res = Est2Wgs(x, y);
	} else
	if(is(to, est) && is(from, wgs)) {
		res = Wgs2Est(x, y);
	} else {
		throw 'Regio.Coords.ConvertCoordinates unsupported coordinate system!';
	}
	
	res = {
		x: res[0], y: res[1],
		e: res[0], n: res[1],
		lng: res[0], lat: res[1],
		lon: res[0]
	};
	
	return res;
};

var _PI = 3.14159265358979323846;
var _n = 0.85417585805;
var _x0 = 6375000.0000000;
var _y0 = 500000.0000000;
var _a = 6378137.0000000;
var _F = 1.7988478514;
var _e = 0.0818191910428158;
var _p0 = 4020205.4790000;

function Est2Wgs(X, Y) {
	function sqr(a) { return a*a };
	
	var theta, p, t, u;
	var Lo = 24 * Math.PI / 180;

	var ux = X - _y0;
	var uy = Y - _x0;

	var sx = ux;
	ux = uy;
	uy = sx;

	theta = Math.atan(uy / (_p0 - ux));
	var tmpL = theta / _n + Lo;

	p = sqr(_p0 - ux);
	p += sqr(uy);
	p = Math.sqrt(p);

	t = Math.pow((p / (_a * _F)),1/_n);

	u = Math.PI / 2 - 2 * Math.atan(t);

	var tmpB = u + (sqr(_e)/2 + 5*sqr(_e)*sqr(_e)/24 + Math.pow(_e,6)/12 + 13*Math.pow(_e, 8)/360) * Math.sin(2*u) + 
		(7*Math.pow(_e,4)/48 + 29*Math.pow(_e, 6)/240 + 811*Math.pow(_e, 8)/11520)*Math.sin(4*u) + 
		(7*Math.pow(_e, 6)/120 + 81*Math.pow(_e, 8)/1120)*Math.sin(6*u);
	X=Rad2Deg(tmpL);
	Y=Rad2Deg(tmpB);
	
	return [X, Y];
}

function Wgs2Est(X, Y) {
	var L = Deg2Rad(X);
	var B = Deg2Rad(Y);

	var Lo = 24 * Math.PI / 180;
	var theta;
	var p;
	var t;

	t = Math.sqrt( (1 - Math.sin(B)) / (1 + Math.sin(B)) * Math.pow( (1+_e*Math.sin(B))/(1-_e*Math.sin(B)), _e) );

	theta = _n * (L - Lo);
	p = _a * _F * Math.pow(t, _n);

	Y = _p0 - p * Math.cos(theta) + _x0;
	X = p * Math.sin(theta) + _y0;
	
	return [X, Y];
}
	
function Rad2Deg(phi) {
	phi = phi * 180 / Math.PI;
	return phi;
}

function Deg2Rad(deg) {
	return deg * (Math.PI / 180);
}

function Wgs2MercWgs(X, Y) {
	if(!Y) { Y = X[1]; X = X[0] };
	
	var e_wgs84 = 0.0818191908426215;
	var a_wgs84 = 6378137.000;
	var tmpN = Deg2Rad(X);
	var tmpE = Deg2Rad(Y);

	var tn = (Math.PI/4.0) + (tmpE/2.0);
	var vh = (1.0-(e_wgs84*Math.sin(tmpE))) / (1.0+(e_wgs84*Math.sin(tmpE)));
	var aste = e_wgs84/2.0;
	X = a_wgs84*tmpN;
	Y = a_wgs84*Math.log(Math.tan(tn) * Math.pow(vh,aste));
	
	return [X, Y];
}

})();


/**
 * Component: Debug
 * Routines to debug MapCat API2.
 * 
 * Requires: MapCat.Log
 * 
 * Version: 2.4.5
 * 
 * Usage: 
 * Include in html and call 
 * | InitializeAPIDebug(mapcat_component_object, {
 * |     'component1 name': component1,
 * |     'component2 name': component2,
 * |     'componentN name': componentN
 * | }), 
 * that will output all MapCat events with all arguments.
 * If _logCallback_ is not specified, then output will be done using <MapCat.Log.add>
 * 
 * NB:
 * Creates global window.JSON 
 * 
 * Author: 
 * Alexandr Smirnov (alex@regio.ee)
 */

if(!this.JSON){
	JSON=function(){function f(n){return n<10?'0'+n:n;}
	Date.prototype.toJSON=function(){return this.getUTCFullYear()+'-'+
	f(this.getUTCMonth()+1)+'-'+
	f(this.getUTCDate())+'T'+
	f(this.getUTCHours())+':'+
	f(this.getUTCMinutes())+':'+
	f(this.getUTCSeconds())+'Z';};var m={'\b':'\\b','\t':'\\t','\n':'\\n','\f':'\\f','\r':'\\r','"':'\\"','\\':'\\\\'};function stringify(value,whitelist){var a,i,k,l,r=/["\\\x00-\x1f\x7f-\x9f]/g,v;switch(typeof value){case'string':return r.test(value)?'"'+value.replace(r,function(a){var c=m[a];if(c){return c;}
	c=a.charCodeAt();return'\\u00'+Math.floor(c/16).toString(16)+
	(c%16).toString(16);})+'"':'"'+value+'"';case'number':return isFinite(value)?String(value):'null';case'boolean':case'null':return String(value);case'object':if(!value){return'null';}
	if(typeof value.toJSON==='function'){return stringify(value.toJSON());}
	a=[];if(typeof value.length==='number'&&!(value.propertyIsEnumerable('length'))){l=value.length;for(i=0;i<l;i+=1){a.push(stringify(value[i],whitelist)||'null');}
	return'['+a.join(',')+']';}
	if(whitelist){l=whitelist.length;for(i=0;i<l;i+=1){k=whitelist[i];if(typeof k==='string'){v=stringify(value[k],whitelist);if(v){a.push(stringify(k)+':'+v);}}}}else{for(k in value){if(typeof k==='string'){v=stringify(value[k],whitelist);if(v){a.push(stringify(k)+':'+v);}}}}
	return'{'+a.join(',')+'}';}}
	return{stringify:stringify,parse:function(text,filter){var j;function walk(k,v){var i,n;if(v&&typeof v==='object'){for(i in v){if(Object.prototype.hasOwnProperty.apply(v,[i])){n=walk(i,v[i]);if(n!==undefined){v[i]=n;}}}}
	return filter(k,v);}
	if(/^[\],:{}\s]*$/.test(text.replace(/\\./g,'@').replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(:?[eE][+\-]?\d+)?/g,']').replace(/(?:^|:|,)(?:\s*\[)+/g,''))){j=eval('('+text+')');return typeof filter==='function'?walk('',j):j;}
	throw new SyntaxError('parseJSON');}};}();
}

/**
 * Function: InitializeAPIDebug
 * Starts outputing all events (with names and parameters) to Firebug console or into provided logCallback
 * 
 * Parameters:
 * listeners - any component that provides listeners property (events dispatcher) (see <MapCat.Component>, <Regio.SearchList>, etc...)
 * components - optional, object containing names of components as Keys and components objects as values ({ "comp1 name": comp1, ... }). Use this, if you have several components on one page and need to see events flow from all of them at the same time. Log will look like: 
 * | Comp1 Name: eventName (eventParam1, ...)
 * | Comp1 Name: eventName (eventParam1, ...)
 * | Comp2 Name: eventName (eventParam1, ...)
 * logCallback - optional, function, if you need to test  components in some other browser (that does not contain FireBug) or you just have implemented some other debugging console and want to see output there, you can use this function. If specified, then this callback will be called for every output line. Function should accept one parameter - string (line of output). 
 */
function InitializeAPIDebug(listeners/*or MapCat.Component*/, components, logCallback) {
	if (listeners && listeners.listeners) {
		listeners = listeners.listeners;
	}
	var log = logCallback ? logCallback : MapCat.Log.add;
	
	
	function stringifyArgument(a) {
		var ret;
		if (a && typeof(a) == 'object' && a.length && a.item) {
			ret = '<DOM Objects Array?>';
		} else {
			try {
				ret = JSON.stringify(a);
			} catch(e) {
				ret = "<" + e + ">";
			}
		}
		return ret;
	}
	
	function createLogger(componentName) {
		return function(filter, eventName) {
			var a = [];
			for(var i=2;i<arguments.length;i++) {
				var s = stringifyArgument(arguments[i]);
				a.push(s);
			}
			var argsStr = a.join(', ');
			log(componentName+': "'+eventName+'"'+(argsStr ? ', '+argsStr : ''));
		}
	}
	
	function installLoggingCode(key, listeners) {
		var logger = createLogger(key);
		
		var old = listeners.broadcastEx;
		
		listeners.broadcastEx = function(filter, componentName) {
			logger.apply(undefined, arguments);
			
			var ret = old.apply(listeners, arguments);
			
			if(typeof ret != "undefined") {
				log(key+": \""+componentName+"\", ...returning: "+stringifyArgument(ret));
			}
			
			return ret;
		};
	}
	
	if (listeners && (typeof(listeners) == "object")) {
		installLoggingCode('MapCat FlashTile', listeners);
	};
	if (typeof(components) == "object") {
		for(var key in components) {
			var c = components[key];
			installLoggingCode(key, c.listeners);
		}
	}
}



/**
 * Component: Regio.DelayedExecutor
 * Exports function "ready" for anything (any components, etc).
 * 
 * Version: 1.0
 * 
 * Usage:
 * see test/DelayedExecutor.html
 * 
 * Author:
 * Alexandr Smirnov (alex@regio.ee)
 */

Regio.DelayedExecutor = function(listeners, loadedFlag, isLoadedFlag) {
	var loadedFlag = loadedFlag || ".loaded";
	var isLoadedflag = isLoadedflag || ".isLoaded";
	
	var loaded = {};
	var queues = {};
	
	var Item = function(callback, contest) {
		this.call = function() {
			callback.apply(contest);
		}
	}
	
	function isLoaded(n) {
		var yes = loaded[n];
		if(!yes) {
			yes = listeners.call(n+isLoadedflag);
			if(yes) {
				loaded[n] = true;
				executeQueue(n);
			}
		}
		return yes;
	}
	
	function executeQueue(n) {
		var q = queues[n];
		if(q && q.length) {
			for(var i=0; i<q.length; i++) {
				q[i].call();
			}
			delete queues[n];
		}
	}
	
	listeners.add("*", function(n) {
		if(n.substr(n.length-loadedFlag.length) == loadedFlag) {
			var cmpName = n.substr(0, n.length-loadedFlag.length);
			loaded[cmpName] = true;
			executeQueue(cmpName);
		}
	});
	
	this.ready = function(componentName, callback, contest) {
		if(isLoaded(componentName)) {
			callback.apply(contest);
		} else {
			var i = new Item(callback, contest);
			if(queues[componentName]) {
				queues[componentName].push(i);
			} else {
				queues[componentName] = [i];
			}
		}
	}
}


/**
 * Component: Regio.TreeView
 * Old TreeView component. Still here only because Regio.LayersTree requires it.
 * 
 * Optional: jQuery
 * 
 * Version: 20.11.07
 * 
 * Author:
 * Alexandr Smirnov (alex@regio.ee)
 */

// @author: Aleksandr (Regio)
// Independant TreeView component.
// ver 20.11.07

if (typeof(Regio) == 'undefined') {
	Regio = {}; // namespace
}

Regio.TreeView = function(div) {
	if (typeof div == "string") {
		div = document.getElementById(div);
	}
	this.div = div;
}

Regio.TreeView.prototype = {
	// public
	setData: function(obj) { // json
		this.data = obj;
		this._formatTree(this.data);
	},
	collapseAllButFirstByDefault: true,
	wrapTree: true,
	// public events
	onFormatObjectItem: undefined,
	onBranchHeaderClick: undefined,
	onBeforeBranchProcess: undefined,
	onFormatBranchHeader: undefined,
	onGetBranchClass: undefined,
	onFormatBranch: undefined,
	// private
	_formatTree: function(data) {
		var html = '';
		if (this.wrapTree) {
			html += '<div class="tree" style="overflow: auto; height: 100%;">';
			html += this._formatBranch(data, 0, true);
			html += '</div>';
		} else {
			html += this._formatBranch(data, 0, true);
		}
		this.div.innerHTML = html;
	},
	_formatBranch: function(branch, level, firstBranch) {
		var html = '';
		if (!this.onFormatBranch) {
			var branchClass = this.onGetBranchClass ? this.onGetBranchClass(level, branch) : (level > 0 ? 'branch' : 'firstbranch');
			html += '<div class="' + branchClass + '"' +
				' style="display: '+ (!firstBranch && this.collapseAllButFirstByDefault ? 'none' : '') +'">';
		}
		
		for(var b in branch) {
			branch[b]._parent_ = branch;
			
			if (b != '_data_' && b != '_parent_') { // reserved for additoinal data
				switch(typeof branch[b]) {
					case 'object':
						if (!branch[b].isItem) {
							// process as branch
							if (this.onFormatBranch) {
								html += new String(this.onFormatBranch(b, branch[b]))
									.replace(/{childs}/gmi, 
										this._formatBranch(branch[b], level+1, firstBranch)
									);
							} else {
								if (this.onBeforeBranchProcess)
									this.onBeforeBranchProcess(b, branch[b]);
								html += '<div class="branch_wrapper">';
								if (!this.noExpanders) {
									html += '<a href="#" onClick="Regio.TreeView.__branchClick(this.nextSibling.nextSibling, this); '+
										'return false" class="'+
										(!firstBranch && this.collapseAllButFirstByDefault ? 'collapser' : 'expander')+'">';
									html += '&nbsp;';
									html += '</a>';
								}
								html += '<span class="header">';
								if (this.onFormatBranchHeader) {
									html += this.onFormatBranchHeader(b, branch[b], level);
								} else {
									html += b;
									html += '<br />'
								}
								html += '</span>';
								html += this._formatBranch(branch[b], level+1, firstBranch);
								html += '</div>';
							}
						} else {
							// "object item"
							if (this.onFormatObjectItem) {
								html += this.onFormatObjectItem(b, branch[b], level);
							} else {
								html += '<div class="item">'+b+'</div>';
							}
						}
						break;
					case 'string':
					case 'number':
					case 'boolean':
						// "text item"
						html += '<div class="item '+b+'">'+branch[b]+'</div>';
						break;
				}
				firstBranch = false;
			}
		}

		if (!this.onFormatBranch) {
			html += "</div>";
		}
		return html;
	}
}
// static (!) private functions
Regio.TreeView.__branchClick = function(div, link) {
	if (this.onBranchHeaderClick) {
		this.onBranchHeaderClick(div, link);
	}
	link.className = (link.className == 'expander' ? 'collapser' : 'expander');
	if (typeof(jQuery) != "undefined") {
		$(div).animate({
			"opacity": "toggle",
			"height": "toggle"
		}, 200);
	} else {
		div.style.display = (div.style.display ? "" : "none")
	}
	return false;
}

// Example of use:
/*
$(function() {
function NavTo(x, y) {
	alert('NavTo('+y+'+", "+'+y+')');
}
var Tree = new Regio.TreeView('allResultsList');
Tree.onFormatObjectItem = function(name, obj) {
	return '<div class="item"><a href="javascript:NavTo('+obj.x+', '+obj.y+')">'+b+'</a></div>';
}
Tree.onFormatBranchHeader = function(name, obj) {
	html = '';
	html += '<a href="#" onClick="alert(\'clicked\'); return false">';
	html += name;
	html += '</a>';
	html += '<br />'
	return html;
}

Tree.setData({
	branch1: {
		b11: 'hoho',
		b12: 'hehe',
		branch_1: {
			b11: 'hoho',
			b12: 'hehe'
		},
		branch_2: {
			b11: 'hoho',
			b12: 'hehe',
			object_item: {
				isItem: true,
				x: 123,
				y: 321
			}
		}
	},
	branch2: {
		b21: 'second1',
		b22: 'second2'
	},
	error: 'Some text'
});
});/**/


/**
 * Component: Regio.LayersTree
 * Layers menu component. Requires Regio.TreeView version 1 (treeview.js) !!!
 * 
 * Requires: Regio.TreeView, Regio.Cookies, Regio.Utils, jQuery, jQuery.json, ui.sortable
 * 
 * Version: 1.66
 * 
 * Author:
 * Alexandr Smirnov (alex@regio.ee)
 */

// @author: Aleksandr (Regio)
// LayersTree component.
// requires TreeView component and jQuery!
if ((typeof(Regio) == 'undefined') || (typeof(Regio.TreeView) == 'undefined')) {
	alert('LayersTree requires Regio.TreeView!');
}
if ((typeof(Regio) == 'undefined') || (typeof(Regio.Cookies) == 'undefined')) {
	alert('LayersTree requires Regio.Cookies!');
}
if (((typeof(Regio) == 'undefined') || !(Regio.Utils && Regio.Utils.format) && (typeof(Regio.formatStr) == 'undefined')) && (typeof(String.prototype.format) == 'undefined')) {
	alert('LayersTree requires utils.js!');
}
if (typeof(jQuery) == 'undefined') {
	alert('LayersTree requires jQuery!');
}
if ((typeof(jQuery) == 'undefined') || (!Object.toJSON)) {
	alert('LayersTree requires jQuery.json extension (json.js)!');
}

// old version compatibility (String.format)
if (typeof(Regio.formatStr) == 'undefined') {
	if (Regio.Utils && Regio.Utils.format) {
		Regio.formatStr = Regio.Utils.format;
	} else {
		Regio.formatStr = function(str) {
			var args = [];
			for(var i=1;i<arguments.length;i++) {
				args.push(arguments[i]);
			}
			return str.format.apply(str, args);
			return
		}
	}
}

Regio.LayersTree = function(div, cfg, mapcat, id /*optional*/) {
	var div = Regio.Utils.getElement(div);
	this.mapcat = mapcat;
	
	var state = null;
	id = id ? id : div.id ? div.id : ''; // if no id then id = div.id or ''
	if (cfg.saveStateInCookies) {
		var key = 'Regio.LayersTree.'+id+'.state';
		state = Regio.Cookies.get(key);
		if (state) {
			state = state.parseJSON();
			// NB: actual restoration of state is done in several parts: groups collpase state; groups and layers order
		}
	}
	
	cfg.sortable = typeof(cfg.sortable) == 'undefined' ? true : cfg.sortable;
	cfg.collapseClasses = (cfg.collapseClasses && cfg.collapseClasses.length && (cfg.collapseClasses.length >= 2)) ? cfg.collapseClasses : ['collapser', 'expander'];
	
	var tree = new Regio.TreeView(div);
	tree.noExpanders = true;
	tree.collapseAllButFirstByDefault = false;
	tree.wrapTree = false;
	
	tree.onFormatObjectItem = onFormatItem;
	tree.onFormatBranch = onFormatBranch;
	
	var myId = 'LayersTree_'+Math.random().toString().split('.')[1];
	window[myId] = this;
	this.myId = myId;
	
	var self = this;
	
	this.mapcat.addCallback(cfg.loadedEventName ? cfg.loadedEventName : "mapcat.layersData.loaded", onLayersListLoaded, this);
	this.mapcat.addCallback("layer.statusChanged", onLayerStatusChanged, this);
	
	var last_ft_data = undefined;
	
	var events = {
		list: {},
		init: function(obj) {
			if (obj) {
				var me = this;
				for(evs in obj) {
					$(evs.split(',')).each(function() {
						me.list[this] = obj[evs];
					})
				}
			}
		},
		execUserEvent: function(name) {
			this.applyRule(this.list[name]);
		},
		applyRule: function(selectors) {
			for(j in selectors) {
				if (j == 'code') {
					eval(selectors[j]);
				} else {
					// jQuery selectors
					var props = selectors[j];
					var elem = $(j, div);
					// elem is jQuery element (array)
					var me = this;
					elem.each(function() { 
						me.applyPropertyes(this, props);
					});
				}
			}
		},
		applyPropertyes: function(elem, obj) {
			for(key in obj) {
				var value = obj[key];
				if (typeof(value) == 'object') {
					this.applyPropertyes(elem[key], value);
				} else {
					elem[key] = value;
				}
			}
		}
	}
	// do init
	events.init(cfg.events);
	
	function findGroupIdByLayerId(lid) {
		// based on last_ft_data
		for(g in last_ft_data) {
			for(l in last_ft_data[g].layers) {
				if (last_ft_data[g].layers[l].id == lid) {
					return last_ft_data[g].id;
				}
			}
		};
		return false;
	}
	
	function onLayerStatusChanged(gid, lid, enabled) {
		$('#layer_check_'+lid, div).attr('disabled', !enabled);
		// make item grayed
		$('#layer_'+lid, div)[enabled ? 'removeClass' : 'addClass']('disabled');
		
		events.execUserEvent(enabled ? 'itemenabled' : 'itemdisabled');
		
		checkGroupDisabledState(gid);
		checkLayerGroupCheck(gid);
	}
	
	function onLayersListLoaded(list) {
		// restore order only...collapse state is restored elsewhere
		if (state) {
			reorderGroups(state.groupsOrder);
			var groups = $(state.groupLayersOrder);
			for(var i=0;i<groups.length;i++) {
				reorderLayers(state.groupsOrder[i], groups[i]);
			}
			var list = mapcat.call("mapcat.layersData.get");
		}
		this.setFTGroupsData(list);
	}
	
	function convertListDataToTreeData(menu) {
		var res = {};
		for(var g=0;g<menu.length;g++) {
			// data for headers
			var headerSymbol = ''; // default symbol here
			if (menu[g].layers.length > 0) {
				headerSymbol = menu[g].layers[0].symbol;
			}
			res[menu[g].name] = {
				_data_: {
					id: menu[g].id,
					symbol: headerSymbol
				}
			}
			// childs
			var layers = menu[g].layers;
			for(var i=0;i<layers.length;i++) {
				res[menu[g].name][layers[i].name] = {
					isItem: true,
					data: layers[i]
				}
			}
		}
		return res;
	}
	
	// function to call jquery registered plugin extensions (ie. from ui.draggable.ext.js)
	function callJQPluginExts(plugin, extname, e, ui) {
		if ($.ui[plugin] && $.ui[plugin].prototype.plugins[extname]) {
			var o = ui.options;
			var c = $.ui[plugin].prototype.plugins[extname];
			for (var i = 0; i < c.length; i++) {
				if (o[c[i][0]]) c[i][1].apply(e, [e, ui]);
			}
		}
	}
	
	function makeTreeSortable() {
		$('.layer_group', div).sortable({
			items: '.layer_name',
			hoverClass: 'sortHelper',
			handle: cfg.itemDragHandler,
			update: function(a, b) {
				var gid = (b.current ? b.current.element.id : b.element.attr("id")).substring('layer_group_'.length);
				reorderLayers(gid, getLayers(gid));
				saveTreeState(true);
			},
			// jquery ui.sortable does not (ver 1.2.1) use draggable with its extensions,
			// so we force here to use them
			start: function(e,ui) {
				callJQPluginExts('draggable', 'start', e, ui);
			},
			sort: function(e,ui) {
				callJQPluginExts('draggable', 'drag', e, ui);
			},
			stop: function(e,ui) {
				callJQPluginExts('draggable', 'stop', e, ui);
			},
			// now this works also:
			//opacity: 0.40, - slows down too much on FF
			axis: 'y'
		});
		$(div).sortable({
			items: '.layer_group',
			hoverClass: 'sortHelper',
			handle: cfg.headerDragHandler,
			update: function() {
				reorderGroups(getGroups());
				saveTreeState(true);
			},
			helper: 'original',
			// jquery ui.sortable does not (ver 1.2.1) use draggable with its extensions,
			// so we force here to use them
			start: function(e,ui) {
				callJQPluginExts('draggable', 'start', e, ui);
			},
			sort: function(e,ui) {
				callJQPluginExts('draggable', 'drag', e, ui);
			},
			stop: function(e,ui) {
				callJQPluginExts('draggable', 'stop', e, ui);
			},
			// now this works also:
			//opacity: 0.40, - slows down too much on FF
			axis: 'y'
		});
	}
	
	function saveTreeState(delayed) {
		if (cfg.saveStateInCookies) {
			if (delayed) {
				setTimeout(Regio.formatStr('window["{0}"].saveTreeState(false)', myId), 1000); // avoid delay in IE
			} else {
				var json = Object.toJSON(getState());
				Regio.Cookies.put('Regio.LayersTree.'+id+'.state', json);
			}
		}
	}
	
	function getState() {
		var groups = getGroups();
		
		var layersInGroups = [];
		$(groups).each(function() {
			layersInGroups.push(getLayers(this));
		});
		
		var state = {
			groupsOrder: groups,
			groupLayersOrder: layersInGroups, // array of array
			expandedGroups: getExpandedGroups()
		}
		
		return state;
	}
	
	function reorderGroups(idList) {
		mapcat.broadcast("layers.reorderGroups", idList);
	}
	
	function reorderLayers(gid, idList) {
		mapcat.broadcast("layers.reorderLayers", idList);
	}
	
	function getGroups() {
		var res = [];
		$('.layer_group', div).each(function() {
			res.push(this.id.substring('layer_group_'.length));
		});
		return res;
	}
	
	function getLayers(gid) {
		var res = [];
		if (gid) {
			$('#layer_group_'+gid, div).each(function() {
				$('.layer_name', this).each(function() {
					res.push(this.id.substring('layer_'.length));
				});
			});
		} else {
			// all layers in one-dimensional array
			$('.layer_name', div).each(function() {
				res.push(this.id.substring('layer_'.length));
			});
		}
		return res;
	}
	
	function getCheckedLayers() {
		var res = [];
		var checks = $('input:checkbox', div).not('.header_check').not(':disabled').filter(':checked').each(function() {
			res.push(this.id.substring('layer_check_'.length));
		});
		return res;
	}
	
	function getExpandedGroups() {
		var res = [];
		$('.layer_group:has(.'+cfg.collapseClasses[0]+')', div).each(function() {
			res.push(this.id.substring('layer_group_'.length));
		});
		return res;
	}
	
	function onFormatItem(name, obj) {
		var privateLayer = false;
		if (cfg.privateLayersPrefix) {
			privateLayer = (name.length > 0) && (name.substring(0,1) == cfg.privateLayersPrefix);
			if (privateLayer) name = name.substring(1);
		}
		
		var tmpl = cfg.itemFormat;
		if (!tmpl) {
			tmpl = '<div class="{layer_class}" id="{layer_div_id}">{layer_name}</div>';
		}
		var fmt = $.extend({}, obj.data, 
		{
				layer_name: name, 
				layer_id: obj.data.id,
				layer_check_id: 'layer_check_'+obj.data.id,
				checkbox_checked_attr: obj.data.checked ? 'checked="true"' : '',
				checkbox_disabled_attr: obj.data.enabled ? '' : 'disabled="true"',
				draggable_class: 'draggable',
				layer_div_id: 'layer_'+obj.data.id,
				layer_class: 'layer_name'+(obj.data.enabled ? '' : ' disabled')+(privateLayer ? ' private' : ''),
				symbol: obj.data.symbol,
				checkbox_onclick: Regio.formatStr('window[\'{0}\'].checkLayer(this.checked, \'{1}\', \'{2}\', false)', myId, obj.data.id, obj._parent_._data_.id)
		});
		var t = Regio.formatStr(new String(tmpl), fmt);
		return t;
	}
	
	function onFormatBranch(name, obj) {
		var privateGroup = false;
		if (cfg.privateGroupsPrefix) {
			privateGroup = (name.length > 0) && (name.substring(0,1) == cfg.privateGroupsPrefix);
			if (privateGroup) name = name.substring(1);
		}
		
		var tmpl = cfg.branchFormat;
		if (!tmpl) {
			tmpl = '<div class="{group_class}" id="{group_div_id}"><div class="group_header"><span class="{draggable_class}">X</span>{group_name} <a href="{collapse_link_href}">x</a></div><div class="{collapse_class}">{childs}</div></div>';
		}
		
		var fmt = $.extend({}, obj._data_, {
			group_name: name, 
			group_id: obj._data_.id,
			draggable_class: 'draggable_header',
			group_class: 'layer_group'+(privateGroup ? ' private' : ''),
			group_div_id: 'layer_group_'+obj._data_.id,
			collapse_link_id: 'collapse_link_'+obj._data_.id,
			collapse_link_href: Regio.formatStr('javascript:window[\'{0}\'].onCollapseLinkClick(\'{1}\')', myId, obj._data_.id),
			checkbox_onclick: Regio.formatStr('window[\'{0}\'].onLayerGroupChecked(this, \'{1}\')', myId, obj._data_.id),
			check_class: 'header_check',
			symbol: obj._data_.symbol
		});
		
		if (!state) {
			fmt.collapse_link_class = cfg.collapseClasses[(formatting_first_group || !cfg.collapseAllButCurrentByDefault) ? 0 : 1];
			fmt.collapse_class = 'collapse_class_'+obj._data_.id+((formatting_first_group || !cfg.collapseAllButCurrentByDefault) ? '' : ' hidden');
		} else {
			// restore collapsed state from cookies
			var expanded = $(state.expandedGroups).index(obj._data_.id) != -1;
			
			fmt.collapse_link_class = cfg.collapseClasses[expanded ? 0 : 1];
			fmt.collapse_class = 'collapse_class collapse_class_'+obj._data_.id+(expanded ? '' : ' hidden');
		}
		
		formatting_first_group = false;
		
		var t = Regio.formatStr(new String(tmpl), fmt);
		
		return t;
	}
	
	function onAfterTreeFormatted(treeData, ft_data) {
		$(ft_data).each(function() {
			checkGroupDisabledState(this.id);
		});
		if (cfg.sortable) {
			makeTreeSortable();
		}
		$('.collapse_class', div).filter('.hidden').show().hide(); // is it slow? without it, animation of initially closed groups will not run
		
		events.execUserEvent('privatehide');
		events.execUserEvent('loaded');
	}
	
	var formatting_first_group = true;
	
	function onBeforeTreeFormatted(ft_data) {
		formatting_first_group = true;
	}
	
	function checkGroupDisabledState(gid) {
		var group_check = $('#layer_group_'+gid+' .header_check', div);
		var checks = $('#layer_group_'+gid+' input:checkbox', div).not('.header_check');
		var enabled_cnt = $(checks).not(':disabled').length;
		group_check.attr('disabled', enabled_cnt == 0 ? 'true' : '');
	}
	
	function checkLayerGroupCheck(gid) {
		var group_check = $('#layer_group_'+gid+' .header_check', div);
		var checks = $('#layer_group_'+gid+' input:checkbox', div).not('.header_check').not(':disabled');
		var not_checked_cnt = $(checks).not(':checked').length;
		var checked = (not_checked_cnt == 0);
		if (checks.length > 0) {
			group_check.attr('checked', checked ? 'true' : '');
		}
	}
	
	// emulate click on collapse link
	function toggleGroupCollapsedState(gid, animation) {
		var speed = 200;
		var cl = $('.collapse_class_'+gid, div);
		var lnk = $('#collapse_link_'+gid, div);
		
		var showing = lnk.hasClass(cfg.collapseClasses[1]);
		if (showing) {
			$('.collapse_class_'+gid, div).removeClass('hidden');
			cl.animate({
				"opacity": "show",
				"height": "show"
			}, animation ? speed : 0, function() {
				// in case user clickd collapse before animation ends
				$('.collapse_class_'+gid, div).removeClass('hidden');
				lnk.addClass(cfg.collapseClasses[0]).removeClass(cfg.collapseClasses[1]);
				//
				if (self.onCollapsedOrExpanded) {
					self.onCollapsedOrExpanded(self);
				}
			});
		} else {
			cl.animate({
				"opacity": "hide",
				"height": "hide"
			}, animation ? speed : 0, function() {
				$('.collapse_class_'+gid, div).addClass('hidden');
				lnk.removeClass(cfg.collapseClasses[0]).addClass(cfg.collapseClasses[1]);
				//
				if (self.onCollapsedOrExpanded) {
					self.onCollapsedOrExpanded(self);
				}
			});
		}
	}
	
	// private
	this.onCollapseLinkClick = function(gid) {
		toggleGroupCollapsedState(gid, true);
		saveTreeState(true);
	}
	
	this.checkLayer = function(toggle, lid, gid, modifyCheckbox) {
		if (!gid) {
			gid = findGroupIdByLayerId(lid);
		}
		var args = { Lid: lid+'', checked: toggle };
		this.mapcat.broadcast("flash.menu.checkLayer", args);
		
		checkLayerGroupCheck(gid);
		
		if (modifyCheckbox) {
			$('#layer_check_'+lid, div).attr('checked', toggle);
		}
	}
	
	this.onLayerGroupChecked = function(checkbox, id) {
		var checks = $('#layer_group_'+id+' input:checkbox', div).not('.header_check').not(':disabled');
		if (checkbox.checked) {
			checks.not(':checked').click();
		} else {
			checks.filter(':checked').click();
		}
		//checks.attr('checked', checkbox.checked ? 'true' : '');
		//checks.click();
	}
	
	// public
	this.getLayers = getLayers;
	this.getGroups = getGroups;
	this.getCheckedLayers = getCheckedLayers;
	this.getExpandedGroups = getExpandedGroups;
	this.toggleGroup = toggleGroupCollapsedState;
	this.saveTreeState = saveTreeState;
	this.sortable = true;
	this.checkLayer = this.checkLayer;
	this.showPrivateLayers = function(show) {
		events.execUserEvent(show ? 'privateshow' : 'privatehide');
	}
	this.setFTGroupsData = function(ft_data) {
		last_ft_data = ft_data;
		onBeforeTreeFormatted(ft_data);
		var treeData = convertListDataToTreeData(ft_data);
		tree.setData(treeData);
		onAfterTreeFormatted(treeData, ft_data);
	}
}



/**
 * Component: Regio.Preview
 * Preview component. Previews point, line, polygon styles in small flash window
 * 
 * Version: 2.1
 * 
 * Requires: MapCat.Component, jQuery
 * 
 * Author: 
 * Jevgeni Kiski (Regio)
 * 
 * Usage:
 * sft = new Regio.Preview('smallFT', {
 * 	src: "mapcat/small.swf",
 * 	width: "30px",
 * 	height: "24px",
 * 	vars: "symbolsDir=mapcat/symbols/",
 * 	wmode: 'opaque'
 * });
 * 
 * Configuration:
 * Same as for regular MapCat.Component
 */



Regio.Preview = function(div, userConf){
	userConf = userConf || {};
	var cfg = $.extend({}, Regio.Preview.defaultConf, userConf);

    if (!cfg.src) 
        throw new Error('Wrong src passed to Regio.SmallFTPreview constructor');
		
    this.sft = new MapCat.Component(div, cfg);
	var me = this;
	
    this.listeners = this.sft.listeners;
	
	this.drawPoint = function(symbol, color, alpha){
		me.sft.ready(function(){
			me.listeners.broadcast('addPoint', {
	            objectId: "preview",
	            points: {
	                "x": 0,
	                "y": 0
	            },
	            color: fixColor(color),
	            alpha: alpha,
	            symbolId: symbol
	        });
		});
	}
	
	this.drawLine = function(width, color, alpha){
		this.drawMultiLine([{width: width ,color: color}],alpha);
	}
	
	this.drawMultiLine = function (lines, alpha){
		var colors = [];
		var widths = [];
		for(var i in lines){
			colors.push(fixColor(lines[i].color));
			widths.push(lines[i].width);
		}
		me.sft.ready(function(){
			me.listeners.broadcast('addLine', {
	            objectId: 'preview',
	            points: [{
	                "x": -12,
	                "y": 8
	            }, {
	                "x": 12,
	                "y": -8
	            }],
	            colors: colors,
	            widths: widths,
	            alpha: alpha
	        });
		});
	}
	
	this.drawPolygon = function (lineWidth, lineColor, lineAlpha, fillColor, fillAlpha){
		me.sft.ready(function(){
			me.listeners.broadcast('addPolygon', {
	            objectId: 'preview',
	            points: [{
	                "x": -12,
	                "y": 8
	            }, {
	                "x": 10,
	                "y": 5
	            }, {
	                "x": 12,
	                "y": -8
	            }, {
	                "x": -10,
	                "y": -5
	            }],
	            lineColor: fixColor(lineColor),
	            lineWidth: lineWidth,
	            lineAlpha: lineAlpha,
	            fillColor: fixColor(fillColor),
	            fillAlpha: fillAlpha
	        });
		});
	}
	
	function fixColor(color){
		if (color) {
            color = color.charAt(0) == '#' ? color.substr(1,color.length) : color;
        }
        return color;
	}
    return this;
}

Regio.Preview.defaultConf = {
    width: '30px',
    height: '24px',
	vars: '',
	wmode: 'window'
}



/**
 * Component: Regio.SymbolList
 * SymbolList Component. Displays symbols for picking up. Requires Regio.Utils and jQuery !!!
 * 
 * Requires: Regio.Utils, jQuery
 * 
 * Version: 1.0
 * 
 * Author:
 * Jevgeni Kiski (jevgeni@regio.ee)
 * 
 * Usage:
 * Regio.SymbolList('templateDiv', [cfg], callback);//container div/template div
 * 
 * Configuration:
 * {symbols: [{id: 'here',pix: symbolsDir + 'here.png',title: 'Ico 1'}, {...}]}
 * 
 * Note:
 * symbolsDir is relative path to symbol from main html document
 * 
 * Callback:
 * function(attr, elem){}
*/
Regio.SymbolList = function(div, cfg, callback){
    var itemTemplate = Regio.Utils.initTemplate($(div).get(0), 'html');
	$(cfg.symbols).each(function(){
		var elem = document.createElement('span');
		elem.innerHTML = Regio.Utils.format(itemTemplate,this);
		var data = this;
		$(elem).children().click(function(){
			if(callback) callback(data,this);
			return false;
		});
		$(div).append($(elem).children());
	});
}


/**
 * Component: Regio.Route
 * Routing base component.
 * 
 * Requires: Regio.Utils, MapCat.Listeners, Regio.TreeView2
 * 
 * Group: Route
 * 
 * Author: 
 * Alexandr Smirnov (alex@regio.ee)
 */

if (typeof(Regio) == "undefined" || !Regio.Utils) {
	throw new Error('Regio.Route requires utils.js!');
}

if (typeof(MapCat) == "undefined") {
	throw new Error('Regio.Route requires MapCat API v2!');
}

if (!Regio.TreeView2) {
	throw new Error('Regio.Route requires treeView.2.0.js!');
}

(function() {

var RouteDescription = function(route, div, cfg) {
	div = Regio.Utils.getElement(div);
	
	if (div && !cfg.treeTemplate) {
		cfg.treeTemplate = Regio.Utils.initTemplate(div, "html");
	}
	
	var overview;
	
	var tree = new Regio.TreeView2(div, {
		treeTemplate: cfg.treeTemplate,
		branchFirstItemIsHeaderDescriptor: false,
		treeStartsWithHeader: false
	});
	
	tree.getHeaderDescriptor = function(arr, pos) {
		if (pos.length == 1) {
			var points = route.getPoints();
			var res = route.listeners.broadcast("header", arr, pos, points);
			return res ? res : points[pos[0]];
		}
	}
	
	tree.beforeDrawNode = function(obj, childNodes, drawingFirstBranch, position) {
		return route.listeners.broadcast("row", obj, position, childNodes);
	};
	
	var treeData = [];
	
	function setTreeData(data) {
		tree.setData(treeData = data);
	}
	
	this.clear = function() {
		var cancel = route.listeners.broadcast("clearDescription");
		if (!cancel) setTreeData([]);
	}
	
	this.setData = function(data, overview) {
		route.listeners.broadcast("drawDescription", data, overview);
		setTreeData(data);
	}
	
	this.getData = function() {
		return treeData;
	}
}

var RouteSegments = function(route) {
	var segments = {}; // hash of hashes
	var segmentsToBeDrawn = {}; // hash of hashes
	
	this.clear = function() {
		segments = {};
		segmentsToBeDrawn = {};
	}
	
	this.updateSegment = function(wayPointNum, segmentID, data) {
		var w = wayPointNum, id = segmentID;
		
		if (!segments[w]) {
			segments[w] = {};
		}
		segments[w][id] = data;
		
		// mark "to be drawn"
		if (!segmentsToBeDrawn[w]) {
			segmentsToBeDrawn[w] = {};
		}
		segmentsToBeDrawn[w][id] = data;
	}
	
	this.getUpdatedSegments = function() {
		for(var i in segments) break;
		if (!i) {
			// list is clear
			return false;
		} else {
			var tmp = segmentsToBeDrawn;
			segmentsToBeDrawn = {};
			return tmp;
		}
	}
}

Regio.Route = function(div, api, cfg) {
	div = Regio.Utils.getElement(div);
	
	var listeners = this.listeners = new MapCat.Listeners();
	var me = this;
	var state, started = false, currentProvider;
	var providerParams;
	var queryesCount = 0;
	
	var segments = new RouteSegments(me);
	
	if (!cfg.providers) {
		throw new Error('Regio.Route, no providers defined!');
	}
	
	var wayPoints = [];
	
	var desc = new RouteDescription(me, div, cfg);
	
	setState('idle');
	
	// init complete
	
	function clear() {
		desc.clear();
		segments.clear();
		renderPath();
	}
	
	function setState(s) {
		state = s;
		switch (s) {
			case 'started':
				started = true;
				break;
			case 'stopped':
				started = false;
				setState('idle');
				break;
		}
		listeners.broadcast("status", s, started);
	}
	
	function start(provider) {
		currentProvider = provider;
		setState('started');
		query(true, true);
	}
	
	function stop() {
		if (!started) return;
		if (currentProvider.abort) {
			//currentProvider.abort();
		}
		setState('stopped');
	}
	
	function query(queryDesc, queryPoints, bbox, generalization) {
		if (!currentProvider) {
			throw new Error('Regio.Route, query - no current provider defined!');
		}
		if (!currentProvider.getRoute) {
			throw new Error('Regio.Route, provider does not have "getRoute" function defined');
		}
		
		setState('wait');
		
		listeners.broadcast("requestStarted");
		
		var complete = function(isError, errorDesc) {
			if (isError) {
				listeners.broadcast("requestError", errorDesc);
			} else {
				if (queryPoints) {
					renderPath();
				}
				listeners.broadcast("requestComplete", queryesCount, queryDesc, queryPoints, bbox, generalization);
			}
			// query done, increase counter
			queryesCount++;
			setState('idle');
		}
		
		if(currentProvider.abort) {
		//	currentProvider.abort();
		}
		currentProvider.getRoute(me, api, queryDesc, queryPoints, bbox, generalization, complete);
	}
	
	function renderPath() {
		var updates = segments.getUpdatedSegments();
		// updates maybe = false if list was just cleared
		
		listeners.broadcast(updates === false ? "clearPath" : "drawPath", updates, queryesCount, me, api, segments);
		
		if (cfg.renderers) {
			for(var i=0; i<cfg.renderers.length; i++) {
				cfg.renderers[i](updates, queryesCount, me, api, segments);
			}
		}
	}
	
	this.addPoints = function(wayPointsArray) {
		stop();
		clear();
		for(var i=0; i<wayPointsArray.length; i++) {
			wayPoints.push(wayPointsArray[i]);
		}
		listeners.broadcast("points", wayPoints);
	}
	
	this.clearPoints = function() {
		stop();
		clear();
		wayPoints = [];
		listeners.broadcast("points", wayPoints);
	}
	
	this.startRouting = function(provider, params) {
		providerParams = params;
		
		if (!provider) {
			for(provider in cfg.providers) break;
			if (!provider) {
				throw new Error('Regio.Route, can not find default provider!');
			}
		}
		
		if (typeof(provider) == "string") {
			provider = cfg.providers[provider];
		}
		
		stop();
		clear();
		start(provider);
	}
	
	this.stopRouting = function() {
		stop();
	}
	
	this.updateForBBox = function(bbox, generalization) {
		if (!started) return;
		query(false, true, bbox, generalization);
	}
	
	this.clear = function() {
		clear();
	}
	
	this.getProviderParams = function() {
		return providerParams;
	}
	
	this.setDescription = function(treeData, overview) {
		desc.setData(treeData, overview);
	}
	
	this.getDescription = function() {
		return desc.getData();
	}
	
	this.getPoints = function() {
		return wayPoints;
	}
	
	this.updateSegment = function() {
		// redirect to RouteSegments object
		return segments.updateSegment.apply(segments, arguments);
	}
	
	this.updatePath = function() {
		query(false, true);
	}
	
	return this;
}

})();


/**
 * Component: Regio.Route.DelfiProvider
 * Provider for Regio.Route
 * 
 * Requires: Regio.Route, jQuery
 * 
 * Group: Route
 * 
 * Version: 1.3
 * 
 * Author: 
 * Alexandr Smirnov (alex@regio.ee)
 */



Regio.Route.CreateDelfiProvider = function(cfg) {
	cfg = cfg || {};
	
	Regio.Utils.applyDefaults(cfg, {
		bridgePath: 'regioeekaart/delfiRoutingBridge.php',
		idPrefix: ''
	});
	
	var provider = {
		abort: function() {
			if(this.lastAjax) {
				this.lastAjax.abort();
				this.lastAjax = null;
			}
		},
		getRoute: function(route, mapapi, queryDescription, querySegments, bbox, generalization, onCompleteCallback) {
			var me = this;
			var points = route.getPoints(), pointsArr = [];
			// convert way points to routing request format (x,y;x,y)
			for(var i=0; i<points.length; i++) {
				pointsArr.push([points[i].e, points[i].n].join(','));
			}
			
			// do request
			var opts = {
			    dataType: 'xml',
			    error: function(e, t) {
			    	// onCompleteCallback accept params: isError, errorDescription
			    	onCompleteCallback(true, t);
			    },
			    success: function(data) {
			    	if (queryDescription) {
			    		me.lastServerParams = $('route server', data).attr('params');
			    		
				    	var desc = $('route desc', data);
				    	var legs = $('route leg', data), arr = [];
				    	// make legs to objects
				    	for(var i=0; i<legs.length; i++) {
				    		arr.push({
				    			id: cfg.idPrefix+$(legs[i]).attr('id'),
				    			turn: (''+$(legs[i]).attr('turn')).toLowerCase(),
				    			text: decodeURIComponent($(legs[i]).attr('text'))
				    		})
				    	}
				    	// we have all we need for setting description
				    	// setDescription first parameter is array of arrays (one array for every way point), 
				    	// but that is not required (could be any data in format of Regio.TreeView2)
				    	route.setDescription([arr,[]], {
				    		length: desc.attr('length'),
				    		time: desc.attr('time')
				    	});
			    	}
			    	
			    	if (querySegments) {
			    		var wayPointNum = 0;
				    	var objs = $('route obj', data);
				    	
				    	objs.each(function() {
				    		var id = cfg.idPrefix+$(this).attr('id');
				    		
				    		var arr = [];
				    		$('crd', this).each(function() {
				    			arr.push({ e: $(this).attr('E'), n: $(this).attr('N')});
				    		});
				    		
			    			route.updateSegment(wayPointNum, id, {
			    				text: $(route.getDescription()[wayPointNum]).filter(function() { return this.id == id })[0].text,
			    				points: arr
			    			})
			    		});
			    	}
			    	
			    	if(queryDescription) {
			    		// do update request right away
			    		me.getRoute(route, mapapi, false, querySegments, bbox, generalization, onCompleteCallback);
			    		onCompleteCallback();
			    	} else {
			    		onCompleteCallback();
			    	}
			    }
			};
			
			if (queryDescription) {
				// url params
				opts.url = cfg.bridgePath,
			    opts.type = 'GET';
				opts.data = {
					request: 'description',
					points: pointsArr.join(';'),
					locale: 'est',
					srs: 'EPSG:3301'
				};
			} else if (querySegments) {
				/*if (!Regio.Utils.isArray(bbox) || (bbox.length != 4)) {
					throw new Error('RouteDelfiProvider, passed bbox for update must be array with length = 4');
				}*/
				if (!bbox) bbox = [0, 0, 99999999, 99999999];
				if (!generalization) generalization = 100;
				// url params
				opts.url = Regio.Utils.format(cfg.bridgePath+'?request=update&points={points}&locale=est&srs=EPSG:3301&gen={gen}&e1={e1}&n1={n1}&e2={e2}&n2={n2}', {
					points: pointsArr.join(';'),
					gen: generalization,
					e1: bbox[0],
					n1: bbox[1],
					e2: bbox[2],
					n2: bbox[3]
				});
			    opts.type = 'POST';
				opts.data = this.lastServerParams ? '<?xml version="1.0"?><data><params>'+decodeURIComponent(this.lastServerParams)+'</params></data>' : '';
				opts.contentType = "text/xml";
			}
			
			if(!queryDescription && !this.lastServerParams) {
				// if its update request and there are no lastServerParams then it means that 
				// description request have not completed... we can do nothing here, just ignore it 
				// (probably should put it into some queue in future)
			} else {
				this.abort();
				this.lastAjax = jQuery.ajax(opts);
			}
		}
	}
	
	return provider;
}



/**
 * Component: Regio.Route.FlashTileRenderer
 * Regio.Route renderer for MapCat FlashTile
 * 
 * Requires: Regio.Route, MapCat.Listeners
 * 
 * Group: Route
 * 
 * Author: 
 * Alexandr Smirnov (alex@regio.ee)
 */



Regio.Route.CreateFlashTileRenderer = function(mapcat/*, getSegmentTooltipCallback*/, cfg) {
	if(!cfg) cfg = {};
	
	var defaultStyle = { line: { alpha: 60, lines: [{ color: '000000', width: 5}, { color: 'FF0000', width: 3 }]}};
	var hiliteStyle = { line: { alpha: 60, lines: [{ color: 'FFFFFF', width: 7}, { color: '0000FF', width: 5 }]}};
	
	var layerName = 'FT_ROUTE_'+Math.random().toString().split('.')[1];
	
	var listeners = new MapCat.Listeners();
	
	mapcat.ready(function() {
		mapcat.broadcast("layers.addSystemLayer", layerName);
	});
	
	mapcat.addCallback("objects.onClick", function(args) {
		if (args.layerId == layerName) {
			mapcat.broadcast("map.centerToObject", {
				Lid: args.layerId,
				obId: args.objectId
			});
		}
	});
	
	mapcat.addCallback("objects.onMouseOver", function(args) {
		if (args.layerId == layerName) {
			mapcat.broadcast("layers.restyleObjectAtSystemLayer", args.layerId, args.objectId, hiliteStyle);
			var ids = getSegmentIDsFromObjectID(args.objectId);
			listeners.broadcast("overSegment", ids.wayPointNum, ids.segmentID);
		}
	});
	
	mapcat.addCallback("objects.onMouseOut", function(args) {
		if (args.layerId == layerName) {
			mapcat.broadcast("layers.restyleObjectAtSystemLayer", args.layerId, args.objectId, defaultStyle);
			var ids = getSegmentIDsFromObjectID(args.objectId);
			listeners.broadcast("outSegment", ids.wayPointNum, ids.segmentID);
		}
	});
	
	function getObjectID(wayPointNum, segmentID) {
		return wayPointNum+'_'+segmentID;
	}
	
	function getSegmentIDsFromObjectID(objectID) {
		var arr = (objectID+'').split('_');
		return {
			wayPointNum: arr[0],
			segmentID: arr[1]
		}
	}
	
	function drawSegment(id, segment) {
		mapcat.ready(function() {
			mapcat.broadcast("layers.addLineToSystemLayer", layerName, {
				objectId: id,
				geo: segment.points, // array of {e: x, n: y}
				colors: ['0x990000', '0x770000'],
				widths: [5, 3],
				alpha: 100,
				tooltip: segment.text
			});
			mapcat.broadcast("layers.restyleObjectAtSystemLayer", layerName, id, defaultStyle);
		});
	}
	
	var renderer = function(updates, queryesCount, route, api, segments) {
		if (!updates) {
			mapcat.ready(function() {
				mapcat.broadcast("layers.clearSystemLayer", layerName);
			});
		}
		for(var wayPointNum in updates) {
			var wayPointSegments = updates[wayPointNum];
			for(var segmentID in wayPointSegments) {
				var segment = wayPointSegments[segmentID];
				drawSegment(getObjectID(wayPointNum, segmentID), segment);
			}
		}
		if (queryesCount == 0) {
			if(!cfg.noCentering) {
				// first time rendering
				setTimeout(function() { // just to move it out of current contest
					mapcat.ready(function() {
						mapcat.broadcast("layers.centerToSystemLayer", layerName);
					});
				}, 100);
			}
		}
	}
	
	renderer.listeners = listeners;
	
	listeners.add("hiliteSegment", function(wNum, sID) {
		mapcat.ready(function() {
			mapcat.broadcast("layers.restyleObjectAtSystemLayer", layerName, getObjectID(wNum, sID), hiliteStyle);
		});
	});
	
	listeners.add("deHiliteSegment", function(wNum, sID) {
		mapcat.ready(function() {
			mapcat.broadcast("layers.restyleObjectAtSystemLayer", layerName, getObjectID(wNum, sID), defaultStyle);
		});
	});
	
	listeners.add("centerSegment", function(wNum, sID) {
		mapcat.ready(function() {
			mapcat.broadcast("map.centerToObject", {
				Lid: layerName, 
				obId: getObjectID(wNum, sID)
			});
		});
	});
	
	return renderer;
}



/**
 * Component: Regio.Route.GoogleMapsRenderer
 * Regio.Route renderer for Google Maps
 * 
 * Requires: Regio.Route, MapCat.Listeners, GoogleMaps, Regio.Coords.Convert
 * 
 * Group: Route
 * 
 * Author: 
 * Alexandr Smirnov (alex@regio.ee)
 */



Regio.Route.CreateGoogleMapsRenderer = function(map) {
	//var defaultStyle = { line: { alpha: 60, lines: [{ color: '000000', width: 5}, { color: 'FF0000', width: 3 }]}};
	//var hiliteStyle = { line: { alpha: 60, lines: [{ color: 'FFFFFF', width: 7}, { color: '0000FF', width: 5 }]}};
	
	var listeners = new MapCat.Listeners();
	
	/*mapcat.addCallback("objects.onClick", function(args) {
		if (args.layerId == layerName) {
			mapcat.broadcast("map.centerToObject", {
				Lid: args.layerId,
				obId: args.objectId
			});
		}
	});
	
	mapcat.addCallback("objects.onMouseOver", function(args) {
		if (args.layerId == layerName) {
			mapcat.broadcast("layers.restyleObjectAtSystemLayer", args.layerId, args.objectId, hiliteStyle);
			var ids = getSegmentIDsFromObjectID(args.objectId);
			listeners.broadcast("overSegment", ids.wayPointNum, ids.segmentID);
		}
	});
	
	mapcat.addCallback("objects.onMouseOut", function(args) {
		if (args.layerId == layerName) {
			mapcat.broadcast("layers.restyleObjectAtSystemLayer", args.layerId, args.objectId, defaultStyle);
			var ids = getSegmentIDsFromObjectID(args.objectId);
			listeners.broadcast("outSegment", ids.wayPointNum, ids.segmentID);
		}
	});
	
	function getObjectID(wayPointNum, segmentID) {
		return wayPointNum+'_'+segmentID;
	}
	
	function getSegmentIDsFromObjectID(objectID) {
		var arr = (objectID+'').split('_');
		return {
			wayPointNum: arr[0],
			segmentID: arr[1]
		}
	}
	*/
	
	var segments = {}, segmentsMustBeClearedOnNextDraw = false;
	
	function updateSegment(w, s, polyline) {
		if (!segments[w]) {
			segments[w] = {};
		}
		var old = segments[w][s];
		if (old) {
			map.removeOverlay(old);
		}
		map.addOverlay(polyline);
		segments[w][s] = polyline;
	}
	
	var calcBBox = false, bbox;
	
	function drawSegment(wnum, sid, segment) {
		var points = segment.points;
		var arr = [];
		for (var i=0; i<points.length; i++) {
			var ll = Regio.Coords.Convert(points[i].e, points[i].n, 'EST', 'WGS');
			var point = new GLatLng(ll.lat, ll.lon);
			if (calcBBox) {
				bbox.extend(point);
			}
			arr.push(point);
		}
		var polyline = new GPolyline(arr, "#ff0000", 5, 0.7);
		GEvent.addListener(polyline, "click", function() {
			centerToBounds(polyline.getBounds());
		}, {
			clickable: true
		});
		updateSegment(wnum, sid, polyline);
	}
	
	function getSegmentPolyline(w, s) {
		if (segments[w] && segments[w][s]) {
			return segments[w][s];
		} else {
			return false;
		}
	}
	
	function clearSegments() {
		for(var w in segments) {
			var seg = segments[w];
			for(var i in seg) {
				map.removeOverlay(seg[i]);
			}
		}
		segments = {};
	}
	
	var renderer = function(updates, queryesCount, route, api, segments) {
		if(!updates) {
			// mark segments to be cleared on next update
			segmentsMustBeClearedOnNextDraw = true;
		} else {
			if (segmentsMustBeClearedOnNextDraw) {
				clearSegments();
			}
			calcBBox = (queryesCount == 0);
			if (calcBBox) {
				bbox = new GLatLngBounds();
			}
			for(var wayPointNum in updates) {
				var wayPointSegments = updates[wayPointNum];
				for(var segmentID in wayPointSegments) {
					var segment = wayPointSegments[segmentID];
					drawSegment(wayPointNum, segmentID, segment);
				}
			}
			
			if (calcBBox && (queryesCount == 0)) {
				// recenter to route
				centerToBounds(bbox);
			}
		}
	}
	
	function centerToBounds(bb) {
		var zoom = map.getBoundsZoomLevel(bb);
		map.setCenter(bb.getCenter(), zoom);
	}
	
	renderer.listeners = listeners;

	listeners.add("getBBox", function() {
		var bounds = map.getBounds();
		var min = bounds.getSouthWest();
		var max = bounds.getNorthEast();
		var minEst = Regio.Coords.Convert(min.lng(), min.lat(), 'WGS', 'EST');
		var maxEst = Regio.Coords.Convert(max.lng(), max.lat(), 'WGS', 'EST');
		return [minEst.e, minEst.n, maxEst.e, maxEst.n];
	});
	
	/*
	listeners.add("hiliteSegment", function(wNum, sID) {
		mapcat.ready(function() {
			mapcat.broadcast("layers.restyleObjectAtSystemLayer", layerName, getObjectID(wNum, sID), hiliteStyle);
		});
	});
	
	listeners.add("deHiliteSegment", function(wNum, sID) {
		mapcat.ready(function() {
			mapcat.broadcast("layers.restyleObjectAtSystemLayer", layerName, getObjectID(wNum, sID), defaultStyle);
		});
	});
	*/
	
	listeners.add("centerSegment", function(wNum, sID) {
		var p = getSegmentPolyline(wNum, sID);
		if (p) {
			centerToBounds(p.getBounds());
		}
	});
	
	return renderer;
}



/**
 * Component: Regio.SearchList.ProviderRegioJGC
 * SearchList provider (Regio JavaGeoCoding)
 * Data provider for <SearchList> component. Uses Regio JavaGeoCoding serivce (through PHP bridge).
 * 
 * Group: Search
 * 
 * Requires: Regio.Utils, Regio.SearchList, jQuery
 * 
 * See also:
 * <Regio.SearchList>
 * 
 * Version: 2.71
 * 
 * Author:
 * Alexandr Smirnov (alex@regio.ee)
 */



/**
 * Function: Regio.SearchList.createProviderRegioJGC
 * Creates SearchList provider.
 * 
 * Parameters:
 * uri - URL template for AJAX requests (default "geoCodeBridge.php?q={query}")
 * cfg - configuration
 * 
 * Configuration:
 * params - optional, array of template vars to be substituted into URI (eg. { a: 123, b: 321 }) default to {}
 * type, dataType - ajax options (default 'GET' and 'json')
 * 
 * Returns:
 * SearchList Provider
 */
Regio.SearchList.createProviderRegioJGC = function(uri, cfg) {
	if (!uri) uri = "geoCodeBridge.php?q={query}&fmt=STD_LEVELS";
	
	cfg = cfg || {};
	
	var lastAjax;
	
	return function(query, listeners, onSuccess, onError) {
		var q, z;
		if (typeof(query) == "object") {
			q = query.query;
			z = query.zoom;
		} else {
			q = query;
		}
		if(!z) z = 10;
		
		var opts = {
			type: 'GET',
			dataType: 'json',
			url: Regio.Utils.format(uri, jQuery.extend({
				query: encodeURIComponent(q)
			}, cfg.params)),
			error: function(req, desc) {
				onError(desc);
			},
			success: function(json) {
				if(json.error) {
					onError(json.error);
				} else {
					for(var i in json) {
						json[i].z = z;
						// <formatted full addr as STD>■<country>■district■district2■city■street■houseno■POI/manor
						// Riia mnt 21, P\u00e4rnu, P\u00e4rnu maakond|Eesti|P\u00e4rnu maakond||P\u00e4rnu||Riia mnt|21||
						var adr = (''+json[i].address).split("\u25a0");
						if(adr.length > 1) {
							json[i].address = adr[0];
							json[i].country = adr[1];
							json[i].district = adr[2];
							json[i].district2 = adr[3];
							json[i].city = adr[4];
							json[i].smallplace = adr[5];
							json[i].street = adr[6];
							json[i].house = adr[7];
							json[i].POI = adr[8];
						}
					}
					onSuccess(json);
				}
			}
		};
		
		opts = jQuery.extend(opts, cfg);
		
		listeners.broadcast("submit", opts);
		
		if (lastAjax) lastAjax.abort();
		lastAjax = jQuery.ajax(opts);
	}
}



/**
 * Component: Regio.SearchList.ProviderRegioMulti
 * SearchList provider
 * Data provider for <SearchList> component. Uses diffrent provider configurations, makes multiple requests, then joins them.
 *
 * Group: Search
 *
 * Requires: Regio.Utils, Regio.SearchList, jQuery
 *
 * See also:
 * <Regio.SearchList>
 *
 * Version: 1.0
 *
 * Author:
 * Jevgeni Kiski (jevgeni@regio.ee)
 */

/**
 * Function: Regio.SearchList.createProviderRegioMulti
 * Creates SearchList provider.
 *
 * Parameters:
 * children - array of provider configurations
 * cfg - configuration
 * 
 * Children:
 * uri - mandatory, URL template for AJAX requests
 * type - optional, ajax option (default 'GET')
 * dataType - optional, ajax options (default 'json')
 * parse - optional, result parce function for conversion to suitable format
 *
 * Configuration:
 * params - optional, array of template vars to be substituted into URI (eg. { a: 123, b: 321 }) default to {}
 *
 * Returns:
 * SearchList Provider
 */
Regio.SearchList.createProviderRegioMulti = function(children) {
	if (!children) throw new Error("Multi Search provider has no children");
	
	var requests = new Array;
	return function(query, listeners, onSuccess, onError) {
		
		var q, z;
		if (typeof(query) == "object") {
			q = query.query;
			z = query.zoom;
		} else {
			q = query;
		}
		if(!z) z = 10;
		
		killRequests();
		
		$(children).each(function(i){
			var child = this;
			
			requests[i] = {status: 0, lastAjax: null, data: []};
			
			var opts = {
				type: this.type?this.type:'GET',
				dataType: this.dataType?this.dataType:'json',
				url: Regio.Utils.format(this.uri, {
					query: encodeURIComponent(q)
				}),
				error: function(req, desc) {
					onError(desc);
					requests[i].status = 2;
					killRequests();
				},
				success: function(data) {
					requests[i].status = 1;
					requests[i].data = child.parse(data);
					if(checkComplete()) onAllComplete();
				}
			};
			
			listeners.broadcast("submit", opts);
			requests[i].lastAjax = jQuery.ajax(opts);
		});
		
		function checkComplete(){
			for (var i in children) {
				if (requests[i]) {
					 if(requests[i].status == 2){
						return false;
					 }else if(requests[i].status != 1){
					 	return false;
					 }
				}else{
					return false;
				}
			}
			return true;
		}
		
		function onAllComplete(){
			var allData = new Array();
			for (var i in children) {
				for (var d in requests[i].data) {
					allData.push(requests[i].data[d]);
				}
			}
			onSuccess(allData);
		}
		
		function killRequests(){
			for(var i in children){
				if (requests[i] && requests[i].lastAjax) 
					requests[i].lastAjax.abort();
			}
		}
	}
}


/**
 * Component: Regio.Layer.StateFetcher
 * State fetcher for Layer
 * 
 * Group: State
 * 
 * Requires: Regio.Layer
 * 
 * Version: 1.0
 * 
 * Author:
 * Jevgeni Kiski (jevgeni@regio.ee)
 */

Regio.Layer.createStateFetcher = function(cmp){
	return {
		get: function() {
			var objects = cmp.getState();
			if(!objects) return null;
			for(var i=0; i<objects.length; i++){
				if(!objects[i].coords) {
					objects.splice(i,1);
				}
			}
			return objects;
		}, 
		set: function(objects) {
			cmp.setState(objects);
		}
	}
}



/**
 * Component: Regio.LayersTree.StateFetcher
 * State fetcher for LayersTree
 * 
 * Group: State
 * 
 * Requires: Regio.LayersTree
 * 
 * Version: 1.1
 * 
 * Author:
 * Jevgeni Kiski (jevgeni@regio.ee)
 */

Regio.LayersTree.createStateFetcher = function(cmp){
	return {
		get: function() {
			return cmp.getCheckedLayers();
		}, 
		set: function(layers) {
			if(!layers) return;
			for (var i = 0; i < layers.length; i++) {
				cmp.checkLayer(true, layers[i], null, true);
			}
		}
	}
}


/**
 * Component: Regio.Route.StateFetcher
 * State fetcher for Routing
 * 
 * Group: State
 * 
 * Requires: Regio.Route
 * 
 * Version: 1.1
 * 
 * Author:
 * Jevgeni Kiski (jevgeni@regio.ee)
 */

Regio.Route.createStateFetcher = function(cmp){
	return {
		get: function() {
			return cmp.getPoints();
		}, 
		set: function(points) {
			if (points && points.length >= 2) {
				cmp.addPoints(points);
				cmp.startRouting();
			}
		}
	}
}



/**
 * Component: Regio.SearchList.StateFetcher
 * State fetcher for Routing
 * 
 * Group: State
 * 
 * Requires: Regio.SearchList
 * 
 * Version: 1.22
 * 
 * Author:
 * Jevgeni Kiski (jevgeni@regio.ee)
 */

Regio.SearchList.createStateFetcher = function(cmp){
	return {
		get: function() {
			var state = cmp.getState();
			var s = null;
			if(state.lastQuery){
				s = {tab: state.tab, page: state.page, query: {
					text: state.lastQuery,
					provider: state.lastProvider
				}};
				if(state.lastClickedRowData) {
					s.object = {e: state.lastClickedRowData.e, n: state.lastClickedRowData.n, ttip: state.lastClickedRowData.address, layer: state.lastClickedRowData.layer};
				}
			}
			return s;
		},
		set: function(data) {
			if(!data || !data.query) return;
			var lastClickedRowData = null;
			if(data.object) {
				lastClickedRowData = {e: data.object.e, n: data.object.n, address: data.object.ttip, layer: data.object.layer};
			}
			var running = false;
			var itemsListener = function(items){
				if(running) return;
				running = true;
				cmp.setState({data:items, lastQuery: data.query.text, lastProvider: data.query.provider, page: data.page, tab: data.tab, lastClickedRowData: lastClickedRowData});
				running = false;
				cmp.listeners.removeByFunction(itemsListener);
			}
			cmp.submit(data.query.text, data.query.provider); //fires items event
			cmp.listeners.add("items", itemsListener);
		}
	};
}


/**
 * Component: Regio.StateList
 * State component base. Gets/sets state of components through statefetchers
 * 
 * Group: State
 * 
 * Version: 1.1
 * 
 * Usage:
 * stateFetcher = new Regio.StateList({
 * search: new Regio.SearchList.createStateFetcher(Search),
 * routing: new Regio.Route.createStateFetcher(Route),
 * objects: new Regio.Controls.Digitizing.createStateFetcher(digitizingLayer),
 * layers: new Regio.LayersTree.createStateFetcher(Layers.tree)
 * 
 * Author:
 * Jevgeni Kiski (jevgeni@regio.ee)
 */
Regio.StateList = function(coms){
	this.fetchers = coms;
	this.get = function(){
		var data = {};
		for(var p in this.fetchers){
			data[p] = this.fetchers[p].get();
		}
		return jQuery.extend({}, data);
	};
	this.set = function(data){
		for(var p in this.fetchers){
			this.fetchers[p].set(data[p]);
		}
	};
}



/**
 * Component: Regio.StateList.ServerAdapter
 * Server adapter to submit states to server
 * 
 * Group: State
 * 
 * Requires: Regio.StateList
 * 
 * Version: 1.0
 * 
 * Author:
 * Jevgeni Kiski (jevgeni@regio.ee)
 */



Regio.StateList.ServerAdapter = function(stateList, cfg){
	if(!cfg || !cfg.url){
		alert('No url defined in Regio.StateList.ServerAdapter constructor');
		return;
	}
	
	var lastData = null;
	var lastResult = null;
	var busy = false;
	
	var me = this;
	if(!parent) parent = document;
	
	this.getStateId = function(callback){
		if(busy) return;
		var co = {};
		if (typeof callback == "function") {
			co.success = callback;
		} else if (typeof callback == "object") {
			co = $.extend({}, callback);
		}
		var data = Object.toJSON(stateList.get());
		if(lastData == data) {
			if(typeof co.success == "function") co.success(lastResult);
			return;
		};
		lastData = data;
		$.ajax({
			url: cfg.url,
			dataType: 'json',
			data: {data: data},
			type: 'POST',
			success: function(data, textStatus){
				if (data && data.success && data.uid) {
					if (typeof co.success == "function") {
						co.success(data.uid);
					}
					lastResult = data.uid;
				}else{
					var error = data && data.error ? data.error : null;
					if(typeof co.error == "function") co.error(error);
				}
			},
			error: function(XMLHttpRequest, textStatus, errorThrown){
				if(typeof co.error == "function") co.error();
			},
			complete: function(){
				busy = false;
			}
		});
	}
};


