/**
 * openDCN:    freE-Democracy <http://www.opendcn.org/>
 *
 * @date:      $Date: 2010-02-01 12:04:00 +0100 (Tue, 01 Feb 2010) $
 * @author:    Riccardo Attilio Galli <riccardo@sideralis.org>
 * @copyright: Copyright (C) 2007, A.I.Re.C. - Associazione Informatica e Reti Civiche Lombardia (http://www.airec.net)
 *
 * @licence:   GPL licence (read /licence/GPL.txt)
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * $Id: geomap_framework.js 2010-01-26 17:12:19Z riq $
 *
 */

if (! window._) window._=function(x){return x;}; // fallback if localize.js is not loaded

/*
 * String codes used
 *
 * SHOW_LOCATION
 * INSERT_SHAPE
 *
 */

var GEOMAP_FRAMEWORK=(function($){

var jQuery=$,undefined; // get back the reference to jQuery, and redefine undefined against munging

/*
 * grayscale.js
 * Copyright (C) James Padolsey (http://james.padolsey.com)
 *
 * Change the color of a DOM element to grayscale
 */
var grayscale=(function(){var b={colorProps:["color","backgroundColor","borderBottomColor","borderTopColor","borderLeftColor","borderRightColor","backgroundImage"],externalImageHandler:{init:function(j,k){if(j.nodeName.toLowerCase()==="img"){}else{d(j).backgroundImageSRC=k;j.style.backgroundImage=""}},reset:function(j){if(j.nodeName.toLowerCase()==="img"){}else{j.style.backgroundImage="url("+(d(j).backgroundImageSRC||"")+")"}}}},c=function(){try{window.console.log.apply(console,arguments)}catch(j){}},a=function(j){return(new RegExp("https?://(?!"+window.location.hostname+")")).test(j)},d=(function(){var j=[0],k="data"+(+new Date());return function(n){var m=n[k],l=j.length;if(!m){m=n[k]=l;j[m]={}}return j[m]}})(),e=function(o,r,q){var m=document.createElement("canvas"),l=m.getContext("2d"),u=o.naturalHeight||o.offsetHeight||o.height,k=o.naturalWidth||o.offsetWidth||o.width,j;m.height=u;m.width=k;l.drawImage(o,0,0);try{j=l.getImageData(0,0,k,u)}catch(p){}if(r){e.preparing=true;var s=0;(function(){if(!e.preparing){return}if(s===u){l.putImageData(j,0,0,0,0,k,u);q?(d(q).BGdataURL=m.toDataURL()):(d(o).dataURL=m.toDataURL())}for(var v=0;v<k;v++){var w=(s*k+v)*4;j.data[w]=j.data[w+1]=j.data[w+2]=h(j.data[w],j.data[w+1],j.data[w+2])}s++;setTimeout(arguments.callee,0)})();return}else{e.preparing=false}for(var s=0;s<u;s++){for(var t=0;t<k;t++){var n=(s*k+t)*4;j.data[n]=j.data[n+1]=j.data[n+2]=h(j.data[n],j.data[n+1],j.data[n+2])}}l.putImageData(j,0,0,0,0,k,u);return m},g=function(k,m){var j=document.defaultView&&document.defaultView.getComputedStyle?document.defaultView.getComputedStyle(k,null)[m]:k.currentStyle[m];if(j&&/^#[A-F0-9]/i.test(j)){var l=j.match(/[A-F0-9]{2}/ig);j="rgb("+parseInt(l[0],16)+","+parseInt(l[1],16)+","+parseInt(l[2],16)+")"}return j},h=function(l,k,j){return parseInt((0.2125*l)+(0.7154*k)+(0.0721*j),10)},f=function(j){var k=Array.prototype.slice.call(j.getElementsByTagName("*"));k.unshift(j);return k};var i=function(k){if(k&&k[0]&&k.length&&k[0].nodeName){var s=Array.prototype.slice.call(k),r=-1,y=s.length;while(++r<y){i.call(this,s[r])}return}k=k||document.documentElement;if(!document.createElement("canvas").getContext){k.style.filter="progid:DXImageTransform.Microsoft.BasicImage(grayscale=1)";k.style.zoom=1;return}var j=f(k),u=-1,w=j.length;while(++u<w){var m=j[u];if(m.nodeName.toLowerCase()==="img"){var p=m.getAttribute("src");if(!p){continue}if(a(p)){b.externalImageHandler.init(m,p)}else{d(m).realSRC=p;try{m.src=d(m).dataURL||e(m).toDataURL()}catch(A){b.externalImageHandler.init(m,p)}}}else{for(var o=0,v=b.colorProps.length;o<v;o++){var l=b.colorProps[o],x=g(m,l);if(!x){continue}if(m.style[l]){d(m)[l]=x}if(x.substring(0,4)==="rgb("){var q=h.apply(null,x.match(/\d+/g));m.style[l]=x="rgb("+q+","+q+","+q+")";continue}if(x.indexOf("url(")>-1){var t=/\(['"]?(.+?)['"]?\)/,n=x.match(t)[1];if(a(n)){b.externalImageHandler.init(m,n);d(m).externalBG=true;continue}try{var z=d(m).BGdataURL||(function(){var B=document.createElement("img");B.src=n;return e(B).toDataURL()})();m.style[l]=x.replace(t,function(C,B){return"("+z+")"})}catch(A){b.externalImageHandler.init(m,n)}}}}}};i.reset=function(l){if(l&&l[0]&&l.length&&l[0].nodeName){var u=Array.prototype.slice.call(l),q=-1,r=u.length;while(++q<r){i.reset.call(this,u[q])}return}l=l||document.documentElement;if(!document.createElement("canvas").getContext){l.style.filter="progid:DXImageTransform.Microsoft.BasicImage(grayscale=0)";return}var s=f(l),n=-1,o=s.length;while(++n<o){var t=s[n];if(t.nodeName.toLowerCase()==="img"){var j=t.getAttribute("src");if(a(j)){b.externalImageHandler.reset(t,j)}t.src=d(t).realSRC||j}else{for(var p=0,m=b.colorProps.length;p<m;p++){if(d(t).externalBG){b.externalImageHandler.reset(t)}var k=b.colorProps[p];t.style[k]=d(t)[k]||""}}}};i.prepare=function(l){if(l&&l[0]&&l.length&&l[0].nodeName){var t=Array.prototype.slice.call(l),o=-1,q=t.length;while(++o<q){i.prepare.call(null,t[o])}return}l=l||document.documentElement;if(!document.createElement("canvas").getContext){return}var r=f(l),m=-1,n=r.length;while(++m<n){var s=r[m];if(d(s).skip){return}if(s.nodeName.toLowerCase()==="img"){if(s.getAttribute("src")&&!a(s.src)){e(s,true)}}else{var k=g(s,"backgroundImage");if(k.indexOf("url(")>-1){var p=/\(['"]?(.+?)['"]?\)/,j=k.match(p)[1];if(!a(j)){var u=document.createElement("img");u.src=j;e(u,true,s)}}}}};return i})();


/**
 * Function: createChoiceGrid
 *
 * Create a button which, when clicked, will show a grid n*m to choose one of
 *   the provided elements
 *
 * Arguments:
 *     container_id - string, id of the element to use as a button
 *     data         - array, info about what to put into the grid.
 *                    Each array elelement is an object with the following
 *                      attributes.
 *                    'url'              -> string, required, url of the image to show
 *                    'label'            -> string, required, text to associate to the image
 *                    '...' any other attribute is copied and will be available
 *                          when using getElement()
 *
 *     opts         - object, optional, options of the grid.
 *                    Follow the default value
 *
 *     (start code)
 *
 *     {
 *      'cell_width':        32,       // int, width of the cell
 *      'cell_border_width': 0,        // int, width of the cell border
 *      'num_cols':          4,        // int, num of the columns of the grid.
 *                                     //   The rows num will change accordingly
 *      'gridPosition':     'right',   // string, where to show the grid relatively
 *                                     //   to his button. One of 'bottom','right','left'.
 *                                     // Note that his also possible to control this
 *                                     //  option adding one of the following class
 *                                     //  to the container. choiceGrid_bottom,
 *                                     //  choiceGrid_right,choiceGrid_left
 *      'cellClass':        null,      // css class name to give to the grid
 *      'background-image': null,      // string, url to an image to use as
 *                                     //   background for each cell
 *      'background-color': '#cccccc', // string, background color for each cell
 *      'border-color':     '#000000', // string, border color for cells border
 *      'selected_elements':undefined  // object, optional, the data item to use as default
 *     }
 *
 *     (end code)
 *
 * Return:
 *     the object to control the grid. Follows his api
 *
 * (start code)
 *
 *     getElement()     - return the currently selected element
 *     setElement(elem) - set as current the given element
 *     getDomElement()  - return the DOM element container
 *     showGrid()       - show the grid
 *     hideGrid()       -    hide the grid
 *     isGridVisible(bool) - return a boolean telling whether the grid is
 *                             visible or not
 *
 * (end)
 *
 */
function createChoiceGrid(container_id,data,opts) {

    if (!data || data.length===0) throw new Error('data missing or empty');

    var base_opt={
        'cell_width':32,
        'cell_border_width':0,
        'num_cols':4,
        'gridPosition':'right',
        'cellClass':null,
        'background-image':null,
        'background-color':'#cccccc',
        'border-color':'#000000',
        'selected_element':data[0]
    };

    // update base_opt with user provided values, if any
    if (opts!==undefined) {
        for (key in base_opt){
            if (opts[key]!==undefined) base_opt[key]=opts[key];
        }
    }

    var container=$("#"+container_id).css({"position":"relative","width":"32px","height":"32px"});
    container.html('');
    container.hover(function(){$(this).css("cursor","pointer");},function(){});
    var frontImgClass="choiceGrid_frontImg";
    var frontImg=$(document.createElement("img")).addClass(frontImgClass)
        .attr("src",base_opt['selected_element']['url'] || '').attr("alt","choicegrid icon")
        .css("display","none");
    container.append(frontImg);

    var _allElements=[];
    var _selectedElement=base_opt['selected_element'];

    var futureObj={
        'getElement':function (){
            return _selectedElement;
        },
        'getDOMElement': function(){return container;},
        'setElement':function(element){
            _selectedElement=element;
            this._on_element_selected(element);
        },
        '_on_element_selected':function(element){
            this.hideGrid();
            if (element['url']) {
                $("."+frontImgClass,container).attr('src',element.url)
                 .css("display","inline");
            } else {
                $("."+frontImgClass,container).css("display","none");
            }
            this.on_element_selected(element);
        },'on_element_selected':function(element){
           // hoock function
        },
        'showGrid':function() {

            if (choiceGrid.style.display!=='none') return;

            choiceGrid.style.display='block';
            return; // XXX

            var compStyle=getComputedStyle(cpDiv,null);
            var origWidth=parseInt(compStyle.width);

            cpDiv.style.width=cpDiv.style.height="0px";
            cpDiv.style.visibility='visible';

            var counter=1;

            var x=setInterval(function(){
                cpDiv.style.width=(parseInt(cpDiv.style.width)+(origWidth/100*(counter++)))+"px";
                cpDiv.style.height=cpDiv.style.width;
                if (parseInt(cpDiv.style.width)>=origWidth) clearInterval(x);
            },50);

        },
        'hideGrid':function () {
            choiceGrid.style.display='none';
        },
        'isGridVisible':function(){
            return choiceGrid.style.display!=='none';
        },
        'getAllElements':function(){
            return _allElements;
        },
        'getContainerId':function(){
            return container_id;
        }
    };

    // creates <table> and <tbody> elements
    var mytable     = document.createElement("table");
    var mytablebody = document.createElement("tbody");

    var cssCellWidth      = base_opt['cell_width']+'px';
    var cssCellHeight     = cssCellWidth;
    var cssCellBorderWidth= base_opt['cell_border_width']+'px';

    var numElements=data.length;
    var numRows=Math.ceil(numElements/base_opt['num_cols']);
    var idx=0;

    // creating all cells
    for(var i=0;i<numRows;i++) {
        // creates a <tr> element
        var myRow = document.createElement("tr");

        for (var j=0;j<base_opt['num_cols'];j++) {
            // creates a <td> element
            var myCell = document.createElement("td");
            myRow.appendChild(myCell);

            if (idx===numElements) continue;

            var currentElement=data[idx];
            myCell._choiceElement=currentElement;

            _allElements.push(currentElement);

            var bgColor=data[idx]['bgColor'] || 'transparent';

            myCell.style.width=cssCellWidth;
            myCell.style.height=cssCellHeight;

            if (base_opt['cellClass']) myCell.setAttribute('class', base_opt['cellClass']);

            if (data[idx]['url']) {
                var tmpImg=document.createElement('img');
                tmpImg.style.border='none';
                tmpImg.src=data[idx]['url'];
                myCell.appendChild(tmpImg);
            }

            myCell.style.border=base_opt['cell_border_width']+'px solid '+(data[idx]['bgColor']||'black');
            myCell.style.padding="0";

            if (base_opt['border-color']) myCell.style.borderColor=base_opt['border-color'];
            if (base_opt['background-image']) myCell.style.backgroundImage='url('+base_opt['background-image']+')';

            myCell.style.backgroundColor=data[idx]["background-color"] || base_opt['background-color'];

            //myCell.onmouseover=function(cell){return function(){on_cell_over(cell);}}(myCell);

            myCell.onclick=function(cell){return function(ev){
                    futureObj.setElement(cell._choiceElement);

                    /* stop event propagation, or else clicking on the grid
                     * will pass the click on the colorBtn who opened it*/
                    ev.stopPropagation();

                }
            }(myCell);

            idx++;

        }
        mytablebody.appendChild(myRow);
    }

    mytable.appendChild(mytablebody);

    var choiceGrid=mytable;
    $(choiceGrid).css({
        'width':(base_opt['cell_width']+base_opt['cell_border_width']*2)*base_opt['num_cols']+'px',
        'height':(base_opt['cell_width']+base_opt['cell_border_width']*2)*numRows+'px',
        'position':'absolute',
        'display':"none",
        'border-collapse':"separate",
        'border-spacing':"0 1px",
        'line-height':0,
        'z-index':9999
    });

    if (base_opt['gridPosition']==='bottom' || container.hasClass("choiceGrid_bottom")) {
        choiceGrid.style.top=parseInt(container.css('width'))+3+"px";
    } else if (base_opt['gridPosition']==='right' || container.hasClass("choiceGrid_right")) {
        choiceGrid.style.left=parseInt(container.css('width'))+3+"px";
        choiceGrid.style.top=0;
    } else if (base_opt['gridPosition']==='left' || container.hasClass("choiceGrid_left")) {
        choiceGrid.style.right='-3px';
        //choiceGrid.style.left= -(3+(base_opt['cell_width']+base_opt['cell_border_width']*2)*base_opt['num_cols'])+"px";
    }

    container.click(function(){
        if (futureObj.isGridVisible()) futureObj.hideGrid();
        else futureObj.showGrid();
    });

    document.getElementById(container_id).appendChild(choiceGrid);

    futureObj.setElement(base_opt['selected_element']);

    return futureObj;



}

/**
 * Function: decimalToHex
 *
 * Convert a number base 10 to a number base 16, represented as a string
 *
 * Arguments:
 *     num     - int, number to convert
 *     padding - int, optional, if present the resulting string will be of
 *                 "padding" length and 0 padded
 *
 * Return:
 *     string, the resulting hexadecimal number
 */
function decimalToHex(num, padding) {
    if (!padding) padding=2;
    var hex = parseInt(num).toString(16);

    while (hex.length < padding)  hex = "0" + hex;

    return hex;
}

/**
 * Function: rgbToHex
 *
 * Convert an rgb string as rgb(1,2,3) to his hexadecimal form, starting with '#'
 *
 * Arguments:
 *     value - string, rgb color represented as rgb(num1,num2,num3)
 *
 * Return:
 *     string, the hexadecimal color, starting with '#'
 */
function rgbToHex(value){
    var match=/rgb.*?(\d+).*?(\d+).*?(\d+).*/.exec(value);
    if (match!==null) {
        return '#'+
                 decimalToHex(match[1])+
                 decimalToHex(match[2])+
                 decimalToHex(match[3]);
    } else return value;
}

/**
 * Function: createColorPicker
 *
 * Create an color picker which will let you select a color from a given subset.
 * This is a specialized versione of <createChoiceGrid>
 *
 * Arguments:
 *     colors - array, list of colors (as hexadecimal strings) to choose from
 *     opts   - object, optional, see <createChoiceGrid> for the options.
 *                Be aware that the option "background-color" can not be set.
 *
 * Return:
 *   an isntance of choiceGrid with the addictional functions
 *   (start code)
 *
 *   getColor()         - return the current color selected
 *   setColor(aColor)   - set the current color, tacke a color in hexadecimal format as argument
 *   register(function) - function, hook, called when a color is selected: the choosen
 *                          color will be passed as argument
 *
 *   (end)
 *
 */
function createColorPicker(container_id,colors,opts){
    if (!colors || colors.length===0) {
        colors=[
            "#00FFFF", "#000000", "#0000FF", "#FF00FF",
            "#808080", "#008000", "#00FF00", "#800000",
            "#000080", "#808000", "#800080", "#FF0000",
            "#C0C0C0", "#008080", "#FFFFFF", "#FFFF00"
        ];
    }

    if (opts===undefined) opts={};

    var data=[];
    for(var i=0,il=colors.length;i<il;i++) {
        data.push({
            "background-color":colors[i]
        });
    }

    var obj=createChoiceGrid(container_id,data,opts);

    var orig_on_element_selected=obj._on_element_selected;

    obj._on_element_selected=function(el){
        document.getElementById(container_id).style.backgroundColor=el["background-color"];

        orig_on_element_selected.call(obj,el);
        for(var i=0,il=_funcs.length;i<il;i++) _funcs[i](el["background-color"]);
    }

    var _funcs=[];
    obj.register=function(func){
        _funcs.push(func);
    }

    obj.getColor=function(){return this.getElement()["background-color"];}

    obj.setColor=function(aColor){this.setElement({"background-color":aColor});}

    obj.setColor(colors[0]);

    return obj;
}

/**
 *
 * Function: createEditMapController
 *
 * Add drawing functionalities to a map
 *
 * Arguments:
 *     map          - object, map to interact with (NOT his id)
 *     options      - see base_opt variable
 *     btnToolbar   - object created via createToolbar
 *     colorPicker  - object, color picker obtained via createColorPicker
 *     markerPicker - object, picker created via createChoiceGrid to select markers
 *     history_id   - string, id of an input element where save the history as a
 *                      json string
 *
 */
function createEditMapController(map,options,btnToolbar,colorPicker,markerPicker,history_id){

    var base_opt={
        'map_id':null,
        'map_owner':null,
        'map_owner_id':null,

        'max_shapes_number':3,
        'addable_shapes':{
           'point': 1,
           'line' : 0,
           'polygon': 0
         },

         'shape_owner'   :'shape_owner_unknown',
         'shape_owner_id':'123',

         'can_edit_map_properties':false,
         'can_edit_own_shapes_only':true,

         'base_shapes' : map.opendcn.get_default_shapes(), // shape presents on the map at start

         'aggregatedData' :{}, // shape_id -> html as string, to show when a shape is clicked
         'additional_markers' : [] //
    };

    for(key in base_opt) {
        if (options[key]!==undefined) base_opt[key]=options[key];
    }

    var _currentlyDrawing=null;

    var _shapeCounter=0;    // a counter to have new internal ids for each shape

    var _selectedShapes={}; // shapes (lines or polygons) selected on map

    var _markerPicker=null;
    var _markerId2url={};

    // this is a dict shape_id -> shape, with no guarantess to have only
    //   non removed shapes
    var _allShapes={};

    /*
     * base_editable_shapes - shapes loaded with the map editable by the user
     */
    _shapes=(function(options){

        var _max_shapes     = options['max_shapes_number'];
        var _addable_shapes = options['addable_shapes'];
        var _base_shapes    = options['base_shapes'];


        var data={
            'point':{},
            'line':{},
            'polygon':{}
        };
        var quantity={
            'point':0,
            'line':0,
            'polygon':0
        };

        // here we save the differences with the shapes present at page loading
        var history={
            'new':{},
            'removed':{},
            'modified':{}
        };

        function shape_to_object(shape){
            var obj={};
            obj['id']=shape.opendcn.id;
            obj['owner']=shape.opendcn.owner;
            obj['owner_id']=shape.opendcn.owner_id;

            var latLng=null;

            if (shape instanceof GMarker){
                obj['kind']='point';
                latLng=shape.getLatLng();
                obj['points']=[[latLng.lat(),latLng.lng()]];
                obj['marker_id']=shape.opendcn.marker_id;
            } else {
                obj['kind']=(shape instanceof GPolygon) ? 'polygon':'line';
                obj['color']=shape.opendcn.color;

                obj['points']=[];
                var numVertex=shape.getVertexCount();
                for (i=0;i<numVertex;i++){
                    latLng=shape.getVertex(i);
                    obj['points'].push([latLng.lat(),latLng.lng()]);
                }

            }

            return obj;
        }


        return {
            /**
             *
             * kind, one of 'point','line','polygon'
             */
            'canAdd':function(kind){
/*console.debug('>>>>>>>>',kind);
console.debug('max shapes',_max_shapes);
console.debug('quantity',quantity,'sum',quantity['point']+quantity['line']+quantity['polygon']);
console.debug('kind',kind);
console.debug('addable_shapes',_addable_shapes);

console.debug('max shapes is null ?',_max_shapes===null?'true':'false');
console.debug('qauntity all < max shapes ?',quantity['point']+quantity['line']+quantity['polygon']<_max_shapes);
console.debug('_addable_shapes[kind]===null ? ',_addable_shapes[kind]===null ? 'true':'false');
console.debug(' _addable_shapes[kind]>quantity[kind] ? ',_addable_shapes[kind]>quantity[kind] ? 'true':'false');
console.debug('<<<<<<< end',kind);
*/
                if ((_max_shapes===null || quantity['point']+quantity['line']+quantity['polygon']<_max_shapes)
                   &&
                   (_addable_shapes[kind]===null || _addable_shapes[kind]>quantity[kind])) {
                    return true;
                } else return false;
            },
            'add':function(shape){
                var kind=shape instanceof GPolygon ?  'polygon' :
                    (shape instanceof GPolyline ? 'line' :
                     shape instanceof GMarker ?   'point' :
                     null);
                if (kind===null) throw new Error("that is not a shape");
                if (!this.canAdd(kind)) return false;

                data[kind][shape.opendcn.shape_id]=shape;
                quantity[kind]++;

                // update history
                history['new'][shape.opendcn.shape_id]=shape_to_object(shape);

                _allShapes[shape.opendcn.shape_id]=shape;

                this.on_history_updated(this);

                return true;
            },
            'remove':function(shape){
                var kind=shape instanceof GPolygon ?  'polygon' :
                    (shape instanceof GPolyline ? 'line' :
                     shape instanceof GMarker ?   'point' :
                     null);
                if (kind===null) throw new Error("that is not a shape");

                var id=shape.opendcn.shape_id;

                if (data[kind][id]!==undefined){
                    delete(data[kind][id]);
                    quantity[kind]--;
                }

                // update history

                if (history['new'][id]) {
                    // the shape was added in the current session
                    delete(history['new'][id]);
                } else if (_base_shapes[id]){
                    // the shape was present when the map was loaded
                    history['removed'][id]=null; // don't care about the ex shape
                }

                delete(_allShapes[id]);

                this.on_history_updated(this);

            },
            'set_modified':function(shape,countIt){
                // update history
                var kind=shape instanceof GPolygon ?  'polygon' :
                    (shape instanceof GPolyline ? 'line' :
                     shape instanceof GMarker ?   'point' :
                     null);

                var id=shape.opendcn.shape_id;
                if (_base_shapes[id]) {
                    // the shape was present when the map was loaded
                    history['modified'][id]=shape_to_object(shape);
                } else {
                    var tmp=shape_to_object(shape);
                    // the shape was added during the current session
                    if (!history['new'][id]) quantity[kind]++;
                    history['new'][id]=tmp // avoidable, but i'm updating obj in real time
                }
                this.on_history_updated(this);
            },
            'getHistory':function(){
                return history;
            },
            /**
             * Function: on_histoy_updated
             *
             * Hook function, called when history changes
             *
             * Arguments:
             *     obj - object, this instance
             */
            'on_history_updated':function(obj){},

            /**
             * Function: modifyInsertedMarkers
             *
             * Change the icon of the present markers
             *
             * Arguments:
             *     marker_id - int, marker id
             */
            'modifyInsertedMarkers': function(marker_id){
                var new_shapes=history['new'];
                for (var shape_id in new_shapes) {
                    if (!data['point'][shape_id]) continue;
                    data['point'][shape_id].setImage(_markerId2url[marker_id]);
                    data['point'][shape_id].opendcn.marker_id=marker_id;
                    new_shapes[shape_id]=shape_to_object(data['point'][shape_id]);
                }

                for (var shape_id in _base_shapes) {
                    var shape_info=_base_shapes[shape_id];
                    var shape=data['point'][shape_id];
                    if (base_opt['can_edit_own_shapes_only'] &&
                        ! (   base_opt['shape_owner']==shape_info.owner
                           && base_opt['shape_owner_id']==shape_info.owner_id )
                       ) continue;

                    shape.setImage(_markerId2url[marker_id]);
                    shape.opendcn.marker_id=marker_id;
                    this.set_modified(shape);
                }
            },

            'addToShapeCounter':function(shape,kind){
                data[kind][shape.opendcn.shape_id]=shape;
                quantity[kind]++;
            }

        };


    })(base_opt);

    var _markerListener=null; // listener of the onclick event on map to insert a marker

    var _globalHistory={
        'boundingBox':null,
        'maptype':map.opendcn.getMapTypeId(map.getCurrentMapType()),
        'shapes':null,
        'map_id':base_opt['map_id'],
        'map_owner':base_opt['map_owner'],
        'map_owner_id':base_opt['map_owner_id'],
        'shape_owner':base_opt['shape_owner'],
        'shape_owner_id':base_opt['shape_owner_id']
    };

    var editMapController= {
    /**
     * Function: change_marker_picker
     *
     * Set the given marker picker as THE markerPicker.
     * It will remove any marker inserted during the curren work session
     *
     * Arguments:
     *     markerPicker - object, a markerPicker created via createChoiceGrid
     */
    'change_marker_picker':function(markerPicker){
        _markerPicker=markerPicker;

        /*** delete the markers inserted in this session          ***/
        /***   (we don't want old markers visualized on the mark) ***/

        var orig_udpate_func=_shapes.on_history_updated;
        _shapes.on_history_updated=function(){};

        var shape;
        var history=_shapes.getHistory();
        for (var shape_id in history['new']) {
            shape=_allShapes[shape_id];
            if (! shape instanceof GMarker) continue;
            this.delete_shape(shape);
        }

        _shapes.on_history_updated=orig_udpate_func;
        _shapes.on_history_updated(_shapes);

        /*** done ***/

        /*** clean and rebuild the map beetween marker ids and images ***/

        var allMarkers=_markerPicker.getAllElements();

        // clean _markerId2url while mantaining his reference
        for (var key in _markerId2url) delete(_markerId2url[key]);

        // map marker_id->image_url
        for (var i=0,il=allMarkers.length;i<il;i++) {
            _markerId2url[allMarkers[i].id]=allMarkers[i].url;
        }

        // add to the mapping values not present in the markerPicker
        for (var i=0,il=base_opt["additional_markers"].length;i<il;i++) {
            _markerId2url[base_opt["additional_markers"][i].id]=base_opt["additional_markers"][i].path;
        }
    },

    /**
     * Function: modifyInsertedMarkers
     *
     * Wrapper for _shapes.modifyInsertedMarkers
     */
    'modifyInsertedMarkers':function(marker_id){
        _shapes.modifyInsertedMarkers(marker_id);
    },
    'getMarkerPicker':function(){
        return _markerPicker;
    },
    'getHistory':function(){
        return JSON.stringify(_globalHistory);
    },
    '_on_shape_history_updated':function(obj){

        _globalHistory['shapes']=_shapes.getHistory();

        editMapController.on_history_elements_modified();
    },
    '_on_maptype_changed':function(){

        _globalHistory['maptype']=map.opendcn.getMapTypeId(map.getCurrentMapType());

        editMapController.on_history_elements_modified();
    },
    '_on_map_moved':function(){

        var bounds=map.getBounds();
        _globalHistory['boundingBox']=[
            [
                 bounds.getSouthWest().lat(),
                 bounds.getSouthWest().lng()
            ],
            [
                 bounds.getNorthEast().lat(),
                 bounds.getNorthEast().lng()
            ]
        ];

        editMapController.on_history_elements_modified();

    },
    'on_history_elements_modified':function(){
        document.getElementById(history_id).value=JSON.stringify(_globalHistory);
    },
    'recover_history':function(history) {
        if (history["maptype"]) {
            map.setMapType(map.opendcn.getMapTypeFromId(history["maptype"]));
            _globalHistory['maptype']=history["maptype"];
        }

        if (history["boundingBox"]) {
            var bounds = new GLatLngBounds(
                new GLatLng(
                    history["boundingBox"][0][0],
                    history["boundingBox"][0][1]
                ),
                new GLatLng(
                    history["boundingBox"][1][0],
                    history["boundingBox"][1][1]
                )
            );

            map.setCenter(bounds.getCenter(), map.getBoundsZoomLevel(bounds));

            _globalHistory['boundingBox']=history["boundingBox"];
        }

        if (history["shapes"]) {

            var tmpShape=null,skipBehaviour=false,point,shape_obj,shapes;

            for (var editing_type in history["shapes"]) {
                // editing_type is one of "new","removed","modified"

                shapes=history["shapes"][editing_type];

                for (var shape_id in shapes) {
                    tmpShape=shapes[shape_id];

                    if (editing_type==="new") {
                        tmpShape["shape_id"]=this._get_new_shape_id();

                        if (tmpShape['kind']==='point') {
                            point=tmpShape['points'][0];
                            tmpShape["marker_url"]=_markerId2url[tmpShape["marker_id"]];

                            this.add_marker(new GLatLng(point[0],point[1]),
                                tmpShape,false);
                        } else {
                            var poly=this.addLineorPolygon(tmpShape);
                            map.addOverlay(poly);

                            if (! skipBehaviour) {
                                this._add_behaviour_to_poly(poly);
                            }
                            _shapes.add(poly)
                        }

                    } else if (editing_type==="removed") {
                        this.delete_shape(_allShapes[shape_id]);
                    } else if (editing_type==="modified") {
                        shape_obj=_allShapes[shape_id];

                        if (tmpShape['kind']=='point') {
                            point=tmpShape['points'][0];
                            shape_obj.setLatLng(new GLatLng(point[0],point[1]));
                        } else {
                            // get the original number of points
                            var prevNumPoints=shape_obj.getVertexCount();

                            // prepend the new points
                            var numPoints=tmpShape['points'].length;
                            for (var i=0,vertex=prevNumPoints;vertex<prevNumPoints+numPoints;i++,vertex++) {
                                shape_obj.insertVertex(vertex,new GLatLng(
                                    tmpShape['points'][i][0],
                                    tmpShape['points'][i][1]));
                            }

                            // remove the original points
                            for (var i=prevNumPoints-1;i>-1;i--) {
                                shape_obj.deleteVertex(i);
                            }

                            shape_obj.setStrokeStyle({'color':tmpShape['color']});
                            if (shape_obj.setFillStyle) {
                                shape_obj.setFillStyle({'color':tmpShape['color']});
                            }
                            shape_obj.opendcn.color=tmpShape['color'];

                            // note: we don't remove first and add later points
                            //    because the shape could collapse
                        }

                        _shapes.set_modified(shape_obj,true);
                    }
                }
            }

        }

        editMapController.on_history_elements_modified();
        check_shapes_limit();
    },

    'getShapesSelected':function(){
        return _selectedShapes;
    },
    'stopEditing' : function() {

        // if was set an onclick event to insert markers, remove it
        this.activate_markerOnClick(false);

        if (_currentlyDrawing) {
            var poly=_currentlyDrawing;
            _currentlyDrawing=null;

            poly.disableEditing();

            var numVertex=poly.getVertexCount();
            if ((poly instanceof GPolygon && numVertex<3 )
                ||
                (poly instanceof GPolyline && numVertex<2 ) ) {
                map.removeOverlay(poly);
                return;
            }

            if (poly instanceof GPolygon && !poly.getVertex(0).equals(poly.getVertex(poly.getVertexCount()-1))) {
                // it's a polygon unclosed
                poly.insertVertex(poly.getVertexCount(),poly.getVertex(0));
            }
            else {
                // it's a line, don't do anything
            }
        }



    },
    /**
     * Function: activate_markerOnClick
     *
     * Let clicks on map add a marker
     *
     * Arguments:
     *     activate_it - bool, whether let clicks on map add markers or not
     */
    'activate_markerOnClick':function(activate_it){
        if (activate_it) {
            var OBJ=this;
            _markerListener = GEvent.addListener(map, "click", function(overlay, latlng) {
                if (latlng) {OBJ.add_marker(latlng);}
            });
        } else {
            if (_markerListener) GEvent.removeListener(_markerListener);
        }
    },

    'can_add_shape':function(kind){
        return _shapes.canAdd(kind);
    },

    'add_marker': function(latlng,opendcn,skipHistory,skipBehaviour){
        if (opendcn===undefined) {
            opendcn={};
            opendcn.shape_id=this._get_new_shape_id();
            opendcn.marker_id=_markerPicker.getElement()['id'];
            opendcn.owner= base_opt['shape_owner'];
            opendcn.owner_id= base_opt['shape_owner_id'];
            opendcn.marker_url=_markerPicker.getElement()['url'];
        }

        var icon = new GIcon();
        icon.image = (opendcn.marker_url?opendcn.marker_url:'/images/maps/marker_red.png');
//        icon.image = opendcn.marker_url;
        icon.iconSize = new GSize(32, 32);
        icon.iconAnchor = new GPoint(15, 32);

        var marker = new GMarker(latlng, {
            'icon': icon, 'draggable': !skipBehaviour
        });
        marker.opendcn=opendcn;

        map.addOverlay(marker);

        var OBJ=this;

        if (!skipBehaviour) {

            // if an existing marker receive a click, delete it
            GEvent.addListener(marker, "click", function() {
                OBJ.delete_shape(marker);
                check_shapes_limit();
            });

            GEvent.addListener(marker,"dragend",function(){
                _shapes.set_modified(marker);
            })
        }

        if (!skipHistory) {
            _shapes.add(marker);

            if (!_shapes.canAdd('point')) {
               btnToolbar.selectBtn('hand');
               check_shapes_limit();
            }
        }

        return marker;
    },

    '_get_new_shape_id':function(){
        return "shape_" + (++_shapeCounter);
    },
    'startPoly':function(startPoint) {
      var color = colorPicker.getColor();
      var polygon = new GPolygon([], color, 4, 0.7, color, 0.2);

      this.startDrawing(polygon,color,startPoint);
    },

    'startLine':function(startPoint) {
        var color = colorPicker.getColor();
        var line = new GPolyline([], color);

        this.startDrawing(line,color,startPoint);
    },

    'startDrawing':function(poly,color,startPoint) {

        // if was set an onclick event to insert markers, remove it
        this.activate_markerOnClick(false);

        map.addOverlay(poly);
        if (startPoint) {

            // to start a line we need at least point
            // to start a polygon we need at least two points
            // I put an IF to avoid having valid puntiform lines
            //   the double point must be removed later
            poly.insertVertex(0,startPoint);
            if (poly instanceof GPolygon) {
                poly.insertVertex(1,startPoint);
                poly.deleteVertex(1);
            }
        }

        poly.enableDrawing(options);

        poly.opendcn={};
        poly.opendcn.shape_id=this._get_new_shape_id();
        poly.opendcn.isEditingEnabled=true;
        poly.opendcn.color=color;
        poly.opendcn.owner=base_opt['shape_owner'];
        poly.opendcn.owner_id=base_opt['shape_owner_id'];

        _currentlyDrawing=poly;

        OBJ=this;

        // once the poly has been completed
        GEvent.addListener(poly, "endline", function() {
            OBJ.stopEditing();
            poly.opendcn.isEditingEnabled=false;

            //remove the shape from the currently selected shapes
            delete(_selectedShapes[poly.opendcn.shape_id]);

            if (startPoint && poly instanceof GPolygon) {
                // remove the double initial point inserted to simulate
                //  an interactive drawing start
                poly.deleteVertex(1);
            }

            _shapes.add(poly);

            if (!_shapes.canAdd(poly instanceof GPolygon ? 'polygon':'line')) {
                btnToolbar.selectBtn('hand');
                check_shapes_limit();
            }
            else {
                // start again with a new shape
                if (poly instanceof GPolygon) {
                    OBJ.startPoly();
                } else OBJ.startLine();
            }

            // add click effects on the shape. Don't call directly because
            //   there seems to be a bug that would trigger "click" event
            //   if started here
            setTimeout(function(){OBJ._add_behaviour_to_poly(poly);},1000);


        });

    },

    '_add_behaviour_to_poly':function(poly){

        var lineUpdatedEnabled=false;

        // add selection and editing to the shapes created
        GEvent.addListener(poly, "click", function(latlng, index) {

            if (poly.opendcn.isEditingEnabled){
                // it was yet selected

                if (typeof index == "number") {
                    // user has clicked a vertex
                    poly.deleteVertex(index);
                } else {
                    poly.disableEditing();
                    poly.opendcn.isEditingEnabled=false;

                    //remove the shape from the currently selected shapes
                    delete(_selectedShapes[poly.opendcn.shape_id]);

                }

            } else {
                // it wasn't selected
                poly.enableEditing();
                poly.opendcn.isEditingEnabled=true;
                _selectedShapes[poly.opendcn.shape_id]=poly;
            }

            if (!lineUpdatedEnabled) {

                // this event should be bound at the same level of "click"
                // but for unknown reason it's fired when populating with
                // existing shapes the map while loading the page

                GEvent.bind(poly, "lineupdated",null, function(){
                    _shapes.set_modified(poly);
                });

                lineUpdatedEnabled=true;
            }


        });



    },

    'addLineorPolygon':function(opendcn,skipBehaviour){

        var poly=null;

        var gpoints=[];
        for (var i=0,il=opendcn.points.length;i<il;i++) {
            gpoints.push(new GLatLng(opendcn.points[i][0],opendcn.points[i][1]));
        }

        if (opendcn.kind=='line') {
            poly = new GPolyline(gpoints, opendcn.color,5,0.5,{clickable:skipBehaviour!=true});
        } else if (opendcn.kind=='polygon') {
            poly = new GPolygon(gpoints, opendcn.color, 2, 0.7, opendcn.color, 0.2,{clickable:skipBehaviour!=true});
        }

        poly.opendcn=opendcn;
        poly.opendcn.isEditingEnabled=false;

        return poly;
    },
    '_on_color_selected':function(aColor){

        var polyStyleOptions={'color':aColor};

        // update the current poly color
        for (i in _selectedShapes) {
            _selectedShapes[i].setStrokeStyle(polyStyleOptions);
            if (_selectedShapes[i].setFillStyle) {
                _selectedShapes[i].setFillStyle(polyStyleOptions);}

           _selectedShapes[i].opendcn.color=aColor;
           _shapes.set_modified(_selectedShapes[i]);
        }

        if (_currentlyDrawing) {
            _currentlyDrawing.opendcn.color=aColor;
            _currentlyDrawing.setStrokeStyle(polyStyleOptions);
            if (_currentlyDrawing.setFillStyle)
                _currentlyDrawing.setFillStyle(polyStyleOptions);
        }
    },
    /**
     * Function: delete_shape
     *
     * Remove a shape from the map, and updates the history
     *
     * Arguments:
     *     shape - object, a GMarker/GPolyline/GPolygon
     *
     */
    'delete_shape': function(shape){
        map.removeOverlay(shape);
        _shapes.remove(shape);
    },
    /**
     * Function: _on_delete_key_pressed
     *
     * Remove all the currently selected shapes, and call check_shapes_limit
     *
     */
    '_on_delete_key_pressed':function(){
        for (i in _selectedShapes) {
            this.delete_shape(_selectedShapes[i]);
            delete(_selectedShapes[i]);
        }
        check_shapes_limit();
    },
    /**
     * Function: loadShapes
     *
     * Add shapes to the map, to be shown at map start.
     * This shapes are then counted as present since the start of the map,
     *   and are not counted as newly added in the history.
     *
     * Arguments:
     *     shapes - object, the shapes to load.
     *
     * (start code)
     *
     *  // example shapes
     *
     *  {
     *    'id1' : {
     *        'owner'     : string,
     *        'owner_id'  : int,
     *        'kind'      : string ('point','line','polygon')
     *        'marker_id' : int,
     *        'points'    :[[34,40]]
     *    },
     *    'id2' : {
     *        'owner'     : string,
     *        'owner_id'  : int,
     *        'kind'      : 'line',
     *        'points'    :[[25,41],[70,25]]
     *    }
     *
     *  }
     *
     */
    'loadShapes' : function(shapes){

        var tmpShape=null;
        var point=null;
        var shape_obj=null;

        var id2url=_markerId2url;

        for (var shape_id in shapes) {
            tmpShape=shapes[shape_id];
            tmpShape['shape_id']=shape_id;

            var skipBehaviour=true;
            if (  base_opt['can_edit_map_properties']
                ||
                  (
                    tmpShape["owner"]==base_opt["shape_owner"] &&
                    tmpShape["owner_id"]==base_opt["shape_owner_id"]
                  )
               ) {
                skipBehaviour=false;
            }

            if (tmpShape['kind']==='point') {
                point=tmpShape['points'][0];

                tmpShape["marker_url"]=id2url[tmpShape["marker_id"]];
                shape_obj=this.add_marker(new GLatLng(point[0],point[1]),tmpShape,
                    true,skipBehaviour
                );
            } else {

                var poly=this.addLineorPolygon(tmpShape,skipBehaviour);
                map.addOverlay(poly);

                if (! skipBehaviour) {
                    this._add_behaviour_to_poly(poly);
                }
                shape_obj=poly;
            }

            _allShapes[shape_obj.opendcn.shape_id]=shape_obj;

            if (tmpShape['countInHistory']) {
                _shapes.addToShapeCounter(shape_obj,tmpShape['kind']);
            }

            // this should be active only when the map is not editable
            if (base_opt["aggregatedData"] && base_opt["aggregatedData"][shape_id]) {

                GEvent.addListener(shape_obj, "click",(function(htmlString){
                    return function(latlng) {
                        if (latlng) {
                            map.openInfoWindowHtml(latlng,htmlString,{'maxWidth':200});
                        }
                    }
                  })(base_opt["aggregatedData"][shape_id])
                );
            }

        }
    },
    /**
     * Function: _on_toolbarBtn_changed
     *
     * End the current editing session and start a new one for the instrument
     *   identified by the pressed button
     *
     * Arguments:
     *     btnName     - string, the new active btn. Must be one of
     *                     'hand','marker','line','poly'
     *     prevBtnName - string, optional, the previously selected btn
     */
    '_on_toolbarBtn_changed':function(btnName,prevBtnName) {
        this.stopEditing();

        if (btnName=='hand') {
            map.getDragObject().setDraggableCursor('pointer');
            map.getDragObject().setDraggableCursor('-moz-grab');
        } else {
            map.getDragObject().setDraggableCursor('url(http://maps.gstatic.com/intl/en_ALL/mapfiles/ms/crosshairs.cur),crosshair,default');
        }

        switch(btnName) {
            case "hand":
                break;
            case "marker":
                this.activate_markerOnClick(true);
                break;
            case "line":
                this.startLine();
                break;
            case "poly":
                this.startPoly();
                break;

        };
    },
    /**
     * Function: enable_markers_with_id
     *
     * Show/hide all markers with a particular marker_id
     *
     * Arguments:
     *     marker_id - int, id of the marker to show/hide
     *     visible   - boolean, whether to show the markers or not
     *
     */
    'enable_markers_with_id':function(marker_id,visible){
        for(var shape_id in _allShapes) {
            if (! _allShapes[shape_id].opendcn.marker_id ||
                _allShapes[shape_id].opendcn.marker_id!==marker_id) continue;

            if (visible) _allShapes[shape_id].show();
            else _allShapes[shape_id].hide();
        }
    },
    /**
     * Function: get_map
     *
     * Retrieve the google map object
     *
     * Return:
     *     object, the map as generated by google api
     */
    'get_map':function(){
        return map;
    },

    /**
     * Function: connect_to_markerset_editor
     *
     * Each time the markerset is modified, reset the markerpicker of the map
     *
     * NOTE: require makerset_lib.js to be loaded, and will be triggered
     *   whenever a markerset editor fire the "save" event
     *
     **/
    'connect_to_markerset_editor':function(){
        var OBJ=this;

        MARKERSET_LIB.addEventOnMarkersetSaved(function(markerset_ctrl){
            // create the data to use for creating a new markerPicker
            var markers_data=[];
            var allMarkers=MARKERSET_LIB.get_markers();
            var mixedMarkers=markerset_ctrl.getMarkersList();
            var marker_ids=mixedMarkers["user"].concat(mixedMarkers["editor"]);
            for (var i=0,il=marker_ids.length;i<il;i++){
                markers_data.push({
                    "id":marker_ids[i],
                    "url":allMarkers[marker_ids[i]],
                    'label':""
                });
            }

            var btnId=OBJ.getMarkerPicker().getContainerId();
            var tmpMarkerPicker=GEOMAP_FRAMEWORK.createChoiceGrid(btnId,markers_data,
                {'background-image':'/images/maps/btn_empty.png',
                  'num_cols': 1,
                  'cellClass':'elementGridCell_markers',
                  'background-color':'transparent'
                }
            );

            OBJ.change_marker_picker(tmpMarkerPicker);
        });
    }


    }; // <end> editMapController

    if (markerPicker) editMapController.change_marker_picker(markerPicker);

    if (colorPicker) {
        colorPicker.register(editMapController._on_color_selected);
    }

    _shapes.on_history_updated=editMapController._on_shape_history_updated;
    editMapController._on_map_moved(); // force first boundingbox save

    GEvent.addListener(map,'moveend',function(){return editMapController._on_map_moved();});
    GEvent.addListener(map,'maptypechanged',function(){return editMapController._on_maptype_changed();});

    editMapController.loadShapes(map.opendcn.get_default_shapes());
    if (map.opendcn.get_history_to_recover()!==null) {
        editMapController.recover_history(map.opendcn.get_history_to_recover());
    }

    /* #### KEYBOARD EVENTS ##### */

    var keyListenerId=null;

    GEvent.addDomListener(map,"mouseover",function(e){

        keyListenerId=GEvent.addDomListener(document,'keydown',function(e){
            var e=window.event || e;
            var keyCode=e.charCode || e.keyCode;

            if (keyCode===46) { // delete key
                editMapController._on_delete_key_pressed();
            }

            if (!e) var e = window.event;
            e.cancelBubble = true;
            if (e.stopPropagation) e.stopPropagation();

        });

    });

    GEvent.addDomListener(map,"mouseout",function(e){
        GEvent.removeListener(keyListenerId);
    });

    /* #### <END> KEYBOARD EVENTS #####**/

    /* check what shapes are drawable and change behaviour of buttons */
    function check_shapes_limit(){
        // check what shapes are addable
        var kind2btnName={
            'point':'marker',
            'line':'line',
            'polygon':'poly'
        };

        for(kind in kind2btnName){
            // note, kind and buttons names are alike
            btnToolbar.enableBtn(kind2btnName[kind],_shapes.canAdd(kind));
        }
    }

    btnToolbar.on_button_changed=function(a,b){editMapController._on_toolbarBtn_changed(a,b);};

    check_shapes_limit();

    // populate history at start
    editMapController.on_history_elements_modified();

    return editMapController;
}

/**
 * Function: findLocation
 *
 * Arguments:
 *     address - string, address to geolocate
 *     map     - object, map to use as viewport to get nearest results, or null
 *     cb      - callback which will receive the answer. The argument passed is
 *                 an array with elements like
 *                 {'lat':123,'lng':456,'address':'normalized address'}
 */
function findLocation(address,map,cb){
    address=jQuery.trim(address);
    if (!address) {cb([]);return;}

    var geocoder = new GClientGeocoder();
    if (map) geocoder.setViewport(map.getBounds());

    geocoder.getLocations(address,function(response){

        var results=[];

        if (response && response.Status.code == 200) {
            var places=response.Placemark;

            for (var i=0,il=places.length;i<il;i++){
                results.push({
                    'lat':parseFloat(places[i]['Point']['coordinates'][1]),  // notice the index
                    'lng':parseFloat(places[i]['Point']['coordinates'][0]),
                    'address':places[i]['address']
                });
            } // end for
        } // end if

        cb(results);
    });

}

function initSearchWidget(id,map,mapControl,btnToolbar){

    var entry=$('#'+id+' .searchLocation_entry')[0];

    function _add_shape(map,latlng,mapControl){
        map.panTo(latlng);

        switch (btnToolbar.getSelectedBtn()) {

            case "hand" :
                if (mapControl.can_add_shape('point')) {
                    mapControl.stopEditing();
                    mapControl.add_marker(latlng);
                }
                break;
            case "line" :
                if (mapControl.can_add_shape('line')) {
                    mapControl.stopEditing();
                    mapControl.startLine(latlng);
                }
                break;
            case "poly" :
                if (mapControl.can_add_shape('polygon')) {
                    mapControl.stopEditing();
                    mapControl.startPoly(latlng);
                }
                break;

        }


    }

    function _blink_point(map,latlng,mapControl){
        map.panTo(latlng);

        var marker_data={
            'shape_id':'blinker_9000',
            'marker_url':'/images/maps/blinking.gif'
        };

        var marker=mapControl.add_marker(latlng,marker_data,true,true);

        setTimeout(function(){
            map.removeOverlay(marker);
        },3000);
    }

    function fill_result_container(data) {
        var container=$('#'+id+' .searchLocationResults')[0];
        container.innerHTML="";

        for (var i=0,il=data.length;i<il;i++){

            $('<li class="geomap_result_list">').append(
                $('<div class="geomap_result_icons">').append(
                    $('<img src="/img/icons/zoom.png"  title="'+_("SHOW_LOCATION")+'" />')
                      .click(function(latlng){return function(ev){
                        _blink_point(map,latlng,mapControl);
                        ev.preventDefault();
                       }
                      }(new GLatLng(data[i]['lat'],data[i]['lng']))
                    )
                ).append(
                    $('<img src="/img/icons/sticky.png" title="'+_("INSERT_SHAPE")+'" />')
                      .click(function(latlng){return function(ev){
                        _add_shape(map,latlng,mapControl);
                        ev.preventDefault();
                       }
                      }(new GLatLng(data[i]['lat'],data[i]['lng']))
                    )
                )
            ).append($('<div class="geomap_result_text">').text(data[i]['address']))
            .appendTo(container);

        }

    }

    $(entry).keydown(function(ev){
        // if return has been pressed, search
        if (ev.keyCode===13) {
            findLocation(this.value,map,fill_result_container);
            ev.preventDefault();
        }
    });

    $("#"+id+" .searchButton").click(function(ev){
        findLocation(entry.value,map,fill_result_container);
        ev.preventDefault();
    });
}

/**
 * Function: createToolbar
 *
 * Create a toolbar object using an existing html structure
 *
 */
function createToolbar(id){

    var buttons={
        'hand'  : {'enabled':true,
                   'elem':$("#"+id+" .hand_btn")
                  },
        'marker': {'enabled':true,
                   'elem': $("#"+id+" .marker_btn")
                  },
        'line'  : {'enabled':true,
                   'elem': $("#"+id+" .line_btn")
                  },
        'poly'  : {'enabled':true,
                   'elem': $("#"+id+" .poly_btn")
                  }
    };

    var _selectedBtn=null;
    var _curLabel=null;

    var toolbar={
        getSelectedBtn:function(){return _selectedBtn;},
        getBtnInfo:function(btnName){
            return buttons[btnName];
        },
        selectBtn:function(btnName){
            if (_selectedBtn==btnName) return; // selected button not changed

            var prevBtnName=_selectedBtn;

            if (prevBtnName)
                buttons[prevBtnName]['elem'].removeClass("selected").addClass("unselected");
            buttons[btnName]['elem'].removeClass("unselected").addClass("selected");

            _selectedBtn=btnName;

            this._on_button_changed(btnName,prevBtnName);
        },
        on_button_changed:function(btnName,prevBtnName){
            //this._changeLabel(btnName);
        },
        _on_button_changed:function(btnName,prevBtnName){
            this.on_button_changed(btnName,prevBtnName);
        },
        isButtonEnabled:function(btnName){
            return buttons[btnName]['enabled'];
        },
        enableBtn:function(btnName,bool) {
            if (buttons[btnName]['enabled']==bool) return;

            if (!bool) {
                // disable button
                buttons[btnName]['elem'].unbind("click")
                grayscale( buttons[btnName]['elem'][0] );

            } else {
                // enable button
                buttons[btnName]['elem'].bind("click",function(){
                        return toolbar.selectBtn(btnName);
                });
                grayscale.reset( buttons[btnName]['elem'][0] );

            }
            buttons[btnName]['enabled']=bool;
        }
    };

    for (var key in buttons) {
        buttons[key]['elem'].click(
             function(key){
                 return function(){toolbar.selectBtn(key);}
             }(key)
         );
    }

    toolbar.selectBtn('hand');

    return toolbar;
}

// store the created editmapcontroller
var _available_editmaps={};

// store the functions to call when a particular controller is ready
var _funcsToCallOnReady={};

/** Function: initializeMapAndControls
 *
 * Called after map has been created, create a controller and configure the map.
 * Trigger the functions added via <on_EditMapController_ready>
 *
 * Arguments:
 *     map          - object, google map
 *     map_suffix   - string, suffix used to identify the map (the resulting id is "map+map_suffix"
 *     map_options  - object, key->value pairs to customize the new controller
 *     colorPicker  - object, a colorPicker as created via createColorPicker
 *     markerPicker - object, a selector as created via createChoiceGrid, see editMapController
 *                      for details on his element's format
 *
 * Return:
 *     object, an editMapController
 **/
function initializeMapAndControls(map,map_suffix,map_options,colorPicker,markerPicker){
    var btnToolbar=createToolbar("editControlsBox"+map_suffix);
    var history_id="historyField"+map_suffix

    var editMap=createEditMapController(map,map_options,btnToolbar,colorPicker,markerPicker,history_id);

    initSearchWidget("searchLocation_main_container"+map_suffix,map,editMap,btnToolbar);

    var map_dom_id="map"+map_suffix;

    _available_editmaps[map_dom_id]=editMap;

    if (_funcsToCallOnReady[map_dom_id])
        _funcsToCallOnReady[map_dom_id](editMap);

    return editMap;
}


// time to return an api to use GEOMAP_FRAMEWORK

return {
    'start':initializeMapAndControls,
    'createColorPicker':createColorPicker,
    'createChoiceGrid':createChoiceGrid,
    'findLocation':findLocation,
    'getEditmapController':function(map_dom_id) {return _available_editmaps[map_dom_id];},
    'on_EditMapController_ready':function(map_dom_id,func){
        if (_available_editmaps[map_dom_id])
            func(_available_editmaps[map_dom_id]);
        else // else the function will be called on editMap creation
            _funcsToCallOnReady[map_dom_id]=func;
    }
};

})(jQuery); // END OF GEOMAP_FRAMEWORK
