/*
 *  Copyright 2015 Anyware Services
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */

/**
 * @private
 * The google maps panel, supporting address lookup as well as marker and shapes edition
 */
Ext.define('Ametys.plugins.maps.GMapPanel', {
	extend: 'Ext.ux.GMapPanel',

	statics :{
		/**
		 * @private
		 * @readonly
		 * @property {Number} ADD_MARKER_MODE The mode when adding a marker
		 */
		ADD_MARKER_MODE: 0,

		/**
		 * @private
		 * @readonly
		 * @property {Number} MOVE_MAP_MODE The mode when moving the map
		 */
		MOVE_MAP_MODE: 1,

		/**
		 * @private
		 * @readonly
		 * @property {Number} ADD_SHAPE_MAP_MODE The mode when adding a shape
		 */
		ADD_SHAPE_MAP_MODE: 2,

		/**
		 * @private
		 * @readonly 
		 * @property {String} The default color code for the shapes
		 */
		DEFAULT_SHAPE_COLOR: 'FF0000',
			
		/**
		 * @private
		 * @readonly
		 * @property {Object} LOOKUP_ERRORS The array of error code/error message mappings
		 */
		LOOKUP_ERRORS:
		[
	         {
	        	 code: 'UNKNOWN_ERROR',
	        	 // msg: 'A geocoding or directions request could not be successfully processed, yet the exact reason for the failure is not known.' 
	        	 msg : "{{i18n PLUGINS_MAPS_GMAP_UNKNOWN_ERROR}}"
	         },
	         {
	        	 code: 'ERROR',
	        	 // msg: 'There was a problem contacting the Google servers.'
	        	 msg: "{{i18n PLUGINS_MAPS_GMAP_ERROR}}"
	         },
	         {
	        	 code: 'ZERO_RESULTS',
	        	 //msg: 'The request did not encounter any errors but returns zero results.'
	        	 msg: "{{i18n PLUGINS_MAPS_GMAP_ZERO_RESULTS}}"
	         },
	         {
	        	 code: 'INVALID_REQUEST',
	        	 //msg: 'This request was invalid.' 
	        	 msg: "{{i18n PLUGINS_MAPS_GMAP_ERROR}}"
	         },
	         {
	        	 code: 'REQUEST_DENIED',
	        	 //msg: 'The webpage is not allowed to use the geocoder for some reason.' 
	        	 msg: "{{i18n PLUGINS_MAPS_GMAP_ERROR}}"
	         },
	         {
	        	 code: 'OVER_QUERY_LIMIT',
	        	 //msg: 'The webpage has gone over the requests limit in too short a period of time.' 
	        	 msg: "{{i18n PLUGINS_MAPS_GMAP_ERROR}}"
	         }
	     ],
		
		/**
		 * @private
		 * @readonly
		 * @property {Object} LOCATION_TYPES The array of accuracy code/accuracy level mappings
		 */
	     LOCATION_TYPES: 
		 [
	    	  {
	    		  level: 1,
	    		  code: 'APPROXIMATE'
	    	  },
	    	  {
	    		  level: 2,
	    		  code: 'GEOMETRIC_CENTER'
	    	  },
	    	  {
	    		  level: 3,
	    		  code: 'RANGE_INTERPOLATED'
	    	  },
	    	  {
	    		  level: 4,
	    		  code: 'ROOFTOP'
	    	  }
		  ]
	},

	
	/**
	 * @cfg {Number} zoomLevel the zoom level of the map
	 */
	
	/**
	 * @cfg {google.maps.LatLng} center the center object of the map
	 */
	
	/**
	 * @cfg {String} mapTypeId the id of the map type
	 */
	
	/**
	 * @cfg {Ext.data.Store} store the store of the map panel
	 */
	
    /**
	 * @cfg {Object} mapOpts the selected map options when instantiating the google map
	 */
	
	/* ---------------------------------------------------------------------------------------------- */
     
	/**
	 * @private
	 * @property {Ext.data.Record} _selectedRecord the selected record
	 */

	/**
	 * @private
	 * @property {Array} _shapeMarkers the array of google.maps.Marker
	 */

	/**
	 * @private
	 * @property {google.maps.Polyline} _drawingShape the polyline holding the shape points
	 */

	/**
	 * @private
	 * @property {google.maps.Polyline} _nextShapePath the polyline used to visualize the next path of the drawing shape
	 */

	/**
	 * @private
	 * @property {Ametys.plugins.maps.EditShapeDialog} _shapePropertiesWindow the window allowing to edit the properties of a shape
	 */

	/**
	 * @private
	 * @property {google.maps.MarkerImage} _vertexNormal the default marker image
	 */
	
    /**
	 * @private
	 * @property {google.maps.Map} _gmap the google map
	 */
    
    /**
	 * @private
	 * @property {google.maps.Geocoder} _geocoder the geocoder
	 */
    
    /**
	 * @private
	 * @property {google.maps.LatLng} _lastCenter the last center of the map
	 */
	
	/**
	 * @private
	 * @property {Number} _currentMode the current mode (move, edit marker, or edit shape) 
	 */
    
	/**
	 * @private
	 * @property {Ext.XTemplate} _infoWindowTemplate the template used for the information window of the markers/shapes
	 */
	_infoWindowTemplate: new Ext.XTemplate (
		"<tpl for='.'>",
			'<div class="infowindow">',
				'<span class="title">{title}</span>',
				'<p class="description">{description}</p>',
			'</div>',
		'</tpl>',
		{
			// XTemplate configuration:
			compiled: true,
			disableFormats: true
		}
	),
	
	initComponent : function() 
	{   
        Ext.applyIf(this,{
            markers: [],
            cache: {
                marker: [],
                polyline: [],
                polygon: [],
                infowindow: []
            }
          });
		
        this.callParent(arguments);
        
        // bind listeners
        if (this.store)
        {
        	this.store.on('add', Ext.bind(this._recordAddedHandler, this));
        	this.store.on('remove', Ext.bind(this._recordRemovedHandler, this));      
        	this.store.on('update', Ext.bind(this._recordUpdatedHandler, this));            
        }   
	},
	
    afterRender: function()
    {
        var wh = this.ownerCt.getSize();
        Ext.applyIf(this, wh);
        
        this.callParent(arguments);
        
        // Not loading properly otherwise...
        Ext.defer(this._initMap, 200, this);
    },
    
	constructor: function(config)
	{
		this._shapeMarkers = [];
		var me = this;
		
		config = Ext.apply({

			gmapType: 'map',
			border: true,
			
			mapOpts: {
				mapTypeControl: true,
				zoomControl: true,
				disableDoubleClickZoom: true,
				streetViewControl: false
			},

			dockedItems: [
			              {
			            	  dock: 'top',
			            	  xtype: 'toolbar',
			            	  items: [
			            	          {
			            	        	  itemId: 'handButton',
			            	        	  xtype: 'button',
                                          height: 26,
                                          width: 26,
			            	        	  iconCls: 'ametysicon-hand-drag',
			            	        	  toggleGroup: 'actionGroup',
			            	        	  width: 'auto',
			            	        	  enableToggle: true,
			            	        	  scope: this,
			            	        	  toggleHandler: function(btn, state)
			            	        	  {
			            	        		  if (state)
			            	        		  {
			            	        			  this._moveMap();
			            	        		  }
			            	        	  },
			            	        	  pressed: false
			            	          },
			            	          {
			            	        	  itemId : 'markerButton',
			            	        	  xtype: 'button',
			            	        	  tooltip: "{{i18n PLUGINS_MAPS_SERVICE_CONFIGURATION_POI_BUTTON}}",
                                          height: 26,
                                          width: 26,
			            	        	  iconCls: 'ametysicon-placeholder19',
			            	        	  toggleGroup: 'actionGroup',
			            	        	  width: 'auto',
			            	        	  enableToggle: true,
			            	        	  scope: this,          
			            	        	  toggleHandler: function(btn, state)
			            	        	  {
			            	        		  if (state)
			            	        		  {
			            	        			  this._placeMarker();
			            	        		  }
			            	        	  },
			            	        	  pressed: false
			            	          },
			            	          {
			            	        	  itemId : 'shapeButton',
			            	        	  xtype: 'button',
			            	        	  iconCls: 'ametysicon-shape',
                                          height: 26,
                                          width: 26,
			            	        	  tooltip: "{{i18n PLUGINS_MAPS_SERVICE_CONFIGURATION_SHAPE_BUTTON}}",
			            	        	  toggleGroup: 'actionGroup',            
			            	        	  width: 'auto',
			            	        	  enableToggle: true,
			            	        	  scope: this,
			            	        	  toggleHandler: function(btn, state) 
			            	        	  {
			            	        		  if (state)
			            	        		  {
			            	        			  this._startShape();
			            	        		  }
			            	        	  },
			            	        	  pressed: false
			            	          }, 
			            	          {
			            	        	  itemId: 'centerAddressField',
			            	        	  xtype: 'textfield',

			            	        	  height: 26,
			            	        	  flex: 1,
			            	        	  emptyText: "{{i18n PLUGINS_MAPS_SERVICE_CONFIGURATION_CENTER_ON_ADDRESS_PLACEHOLDER}}",
			            	        	  enableKeyEvents: true,  
			            	        	  listeners: {
			            	        		  keyup: function (field, event) 
			            	        		  {
			            	        			  if (event.isSpecialKey() && (event.getCharCode() == event.RETURN || event.getCharCode() == event.ENTER))
			            	        			  {
			            	        				  event.stopEvent();
			            	        				  var btn = me.down('#centerButton');
			            	        				  btn.handler.call(btn.scope, btn, null);
			            	        			  }
			            	        		  },
			            	        		  specialkey: function(field, event)
			            	        		  {
			            	        			  if (event.getKey() == event.ENTER) 
			            	        				{
			            	        				  	event.preventDefault();
			            	        				  	event.stopPropagation();
			            	        				}	
			            	        		  }
			            	        		  
			            	        	  }
			            	          },
			            	          {
			            	        	  iconCls: 'ametysicon-magnifier12',
                                          height: 26,
			            	        	  tooltip: "{{i18n PLUGINS_MAPS_SERVICE_CONFIGURATION_CENTER_ON_ADDRESS_BTN}}",
			            	        	  itemId:'centerButton',
			            	        	  xtype: 'button',
			            	        	  scope: this,
			            	        	  handler: function(btn) 
			            	        	  {
			            	        		  var addressField = me.down('#centerAddressField');
			            	        		  if (!Ext.isEmpty(addressField.getValue()))
			            	        		  {
			            	        			  this._geoCodeLookup(addressField.getValue(), null, false, true, null, true);
			            	        		  }
			            	        	  }             
			            	          }
		            	          ]
			              }
		              ]
		}
		, config);
		
		this._currentMode = Ametys.plugins.maps.GMapPanel.MOVE_MAP_MODE;
		this.store = Ext.StoreMgr.lookup(this.store);

		this.callParent(arguments);
	},

	/**
	 * Get the google map
	 * @return {google.maps.Map} the google map
	 */
	getMap: function()
	{
		return this._gmap;
	},

	/**
	 * Get the zoom level of the map
	 * @return {Number} the zoom level of the map
	 */
	getZoomLevel: function() 
	{
		return this.getMap().getZoom();
	},

	/**
	 * Get the id of the map type
	 * @return {String} the id of the map type
	 */
	getMapTypeId: function() 
	{
		return this.getMap().getMapTypeId();
	},
	
	/**
	 * Get the last center of the map
	 * @return {google.maps.LatLng} _lastCenter the last center of the map
	 */
	getLastCenter: function() 
	{
		return this._lastCenter;
	},
	
    /**
	 * @private 
	 * Initialize the map
	 */
    _initMap: function()
    {
	    var mapOptions = 
	    {
	       zoom: this.zoomLevel,
	       mapTypeId: this.mapTypeId
	    };
	    
	    if (Ext.isObject(this.mapOpts))
	    {
	        Ext.applyIf(mapOptions, this.mapOpts);
	    }
	    
	    this._gmap = new google.maps.Map(this.body.dom, mapOptions);
	    google.maps.event.addListenerOnce(this._gmap, 'tilesloaded', Ext.bind(this._onMapReady, this));
	    
	    if (Ext.isObject(this.center)) 
	    {
	        var point = new google.maps.LatLng(this.center.lat, this.center.lng);
	        this._gmap.setCenter(point, this.zoomLevel);
	        this._lastCenter = point;  
	    } 
	    else 
	    {
	      // no center defined => center on Greenwich
	      var point = new google.maps.LatLng(0.0, 0.0);
	      this.zoomLevel = 1;
	      this._gmap.setCenter(point, this.zoomLevel);
	      this._lastCenter = point;
	    }
    },
	
	/**
	 * @private
	 * Function invoked when the map is ready
	 * @param {google.maps.Map} map the google map
	 */
	_onMapReady: function (map) 
	{        
		this.down('#handButton').toggle();
		this._initializeMarkerImages();

		if (this.store)
		{
			this.store.each( function(record)
			{
				if (record.get('gtype') == "marker")
				{
					this._addMarkerForRecord(record);
				}
				else if (record.get('gtype') == "polygon")
				{
					this._addShapeForRecord(record);
				}
			}, this);
		}
	},

	/**
	 * @private
	 * Initialize the marker images
	 */
	_initializeMarkerImages: function() 
	{
		this._vertexNormal = new google.maps.MarkerImage(
				Ametys.getPluginResourcesPrefix('maps') + '/img/vertex.png',
				new google.maps.Size(11, 11),
				new google.maps.Point(0, 0),
				new google.maps.Point(6, 6)
		);
	},
	
   /**
    * @private
    * Seek the geocode corresponding to the given address
    * @param {String} addr the address to lookup.
    * @param {google.maps.Marker} marker the marker to add (optional).
    * @param {Boolean} clear clear other markers before creating this marker
    * @param {Boolean} center true to set this point as the center of the map.
    * @param {Object} listeners a listeners config
    * @param {Boolean} adaptZoom true to set the zoom to position accuracy
    */
	_geoCodeLookup: function(addr, marker, clear, center, listeners, adaptZoom) 
	{
        if (!this._geocoder) 
        {
            this._geocoder = Ext.create('google.maps.Geocoder', {});
        }
        
        this._geocoder.geocode(
			{
				address: addr
			}, 
			Ext.bind(this._centerMap, this, [addr, marker, clear, center, listeners, adaptZoom], true)
		);
	},
	
	/**
	 * @private
	 * Center the map 
	 * @param {Object} response the server's response
	 * @param {String} status the status of the response
     * @param {String} addr the address to lookup.
     * @param {google.maps.Marker} marker the marker to add (optional).
     * @param {Boolean} clear clear other markers before creating this marker
     * @param {Boolean} center true to set this point as the center of the map.
     * @param {Object} listeners a listeners config
     * @param {Boolean} adaptZoom true to set the zoom to position accuracy
	 */
	_centerMap: function(response, status, addr, marker, clear, center, listeners, adaptZoom)
	{
		if (!response || status !== 'OK') 
		{
            this._showErrorMsg(status);
        }
        else
        {
        	var accuracy = this._getAccuracy(response[0].geometry.location_type);
            if (adaptZoom)
            {                      
              this.getMap().setZoom(accuracy*10);
              this.zoomLevel = accuracy*8;
            }                  
                              
            var location = response[0].geometry.location,
                point = new google.maps.LatLng(location.lat(),location.lng());
            
            if (center)
            {
                this.getMap().setCenter(point, this.zoomLevel);
                this._lastCenter = point;
            }
            
            if (Ext.isObject(marker)) 
            {
                if (!marker.title)
                {
                    marker.title = response.formatted_address;
                }
                var mkr = this._addMarker(point, marker, clear, false, listeners);
                if (marker.callback)
                {
                  marker.callback(this, mkr, point);
                }
            }
        }
    },
    
    /**
     * @private
     * Show the error message corresponding to the given code
     * @param {String} code the code of the error
     */
    _showErrorMsg : function(code)
    {
        Ext.each(Ametys.plugins.maps.GMapPanel.LOOKUP_ERRORS, function(obj)
		{
            if (code == obj.code)
            {
                Ext.MessageBox.alert("{{i18n PLUGINS_MAPS_GMAP_LOOKUP_ERROR_TITLE}}", obj.msg); 
            }
        }, this);
    },
    
    /**
     * @private
     * Get the accuracy corresponding to the given location type
     * @param {String} serverLocationType the location type given by the server
     * @return
     */
    _getAccuracy: function(serverLocationType)
    {
        var level = 1;
        Ext.each(Ametys.plugins.maps.GMapPanel.LOCATION_TYPES, function(locationType)
	    {
            if (locationType.code === serverLocationType)
            {
            	level = locationType.level;
                return false;
            }
        });
      
        return level;
    },
    
	/**
	 * @private
	 * Function invoked when records are added
	 * @param {Ext.data.Store} store the store
	 * @param {Array} records the array of {@link Ext.data.Record}
	 * @param {Number} index the index at which the records were inserted
	 */
	_recordAddedHandler: function(store, records, index)
	{
		if (records)
		{
			Ext.each(records, function(record)
			{
				if (record.get('gtype') == "marker")
				{
					this._addMarkerForRecord(record);
				}
				else if (record.get('gtype') == "polygon")
				{
					this._addShapeForRecord(record);
				}
			}, this);
		}
	},

	/**
	 * @private
	 * Function invoked when records are updated
	 * @param {Ext.data.Store} store the store
	 * @param {Array} records the array of {@link Ext.data.Record}
	 * @param {Number} index the index at which the records were inserted
	 */
	_recordUpdatedHandler: function(store, records, index) 
	{
		Ext.each(records, function(record)
		{
			if (record.get('gtype') == "marker")
			{
				this._recordRemovedHandler(store, record, index);
				this._addMarkerForRecord(record);
			}
			else if (record.get('gtype') == "polygon")
			{
				this._recordRemovedHandler(store, record, index);
				this._addShapeForRecord(record);
			}
		}, this);
	},

	/**
	 * @private
	 * Function invoked when a record is deleted
	 * @param {Ext.data.Store} store the store
	 * @param {Array} records the array of {@link Ext.data.Record}
	 * @param {Number} index the index at which the records were inserted
	 */
	_recordRemovedHandler: function(store, records, index) 
	{
		var me = this;
		Ext.Array.each(records, function(record)
		{
			if (record.get('gtype') == 'marker')
			{
				me._removeEntryFromCache(me.cache.marker, record);
			}
			else if (record.get('gtype') == 'polygon')
			{
				me._removeEntryFromCache(me.cache.polygon, record);
			}
		});
	},

	/**
	 * @private
	 * Remove the given displayed item link to the given record
	 * @param {Array} entries  the items to remove (Marker or Polygon)
	 * @param {Ext.data.Record} record the record to remove
	 */
	_removeEntryFromCache:function (entries, record)
	{
		if  (record)
		{
			for (var i = 0; i < entries.length; i++)
			{
				if(entries[i].recordId == record.id)
				{
					entries[i].setMap(null);
					entries.splice(i,1);
					break;
				}
			}
		}
	},

	/**
	 * @private
	 * Set up the move map mode
	 */
	 _moveMap: function() 
	 {
		 // clean up listeners
		 google.maps.event.clearListeners(this.getMap(), 'click');
		 google.maps.event.clearListeners(this.getMap(), 'dblclick');      
		 google.maps.event.clearListeners(this.getMap(), 'mousemove');          
	 },

	 /**
	  * @private
	  * Add marker for a record
	  * @param {Ext.data.Record} record the record to add a marker for
	  */
	 _addMarkerForRecord: function (record) 
	 {
		 if (record)
		 {
			 var point = new google.maps.LatLng(record.get('lat'), record.get('lng'))

			 // add marker property
			 var marker = 
			 {
				 animation: google.maps.Animation.DROP,
				 title: record.get('title'),
				 draggable: true,
				 icon : this._getMarkerIconUrl(record),
				 infoWindow : 
				 {
					 content : this._infoWindowTemplate.apply({
						 title: record.get('title'), 
						 description: Ext.util.Format.nl2br(record.get('description'))
					 })
				 },        
				 recordId: record.id
			 };

			 // register listeners
			 var listeners = {
					 'dragstart': Ext.bind(this._markerDragStartHandler, this),
					 'dragend': Ext.bind(this._markerDragEndHandler, this)
			 };

			 this._addMarker(point, marker, false, null, listeners);
		 }
	 },

	 /**
	  * @private
	  * Creates a single marker.
	  * @param {google.maps.LatLng} point a GLatLng point
	  * @param {google.maps.Marker} marker a marker object consisting of at least lat and lng
	  * @param {Boolean} clear clear other markers before creating this marker
	  * @param {Boolean} center true to center the map on this marker
	  * @param {Object} listeners a listeners config
	  */
	 _addMarker: function (point, marker, clear, center, listeners)
	 {
		 Ext.applyIf(marker,{});

		 if (clear === true)
		 {
			 this._clearMarkers();
		 }
		 if (center === true) 
		 {
			 this.getMap().setCenter(point, this.zoomLevel)
			 this._lastCenter = point;
		 }

		 var mark = new google.maps.Marker(Ext.apply(marker, {
			 position: point
		 }));

		 if (marker.infoWindow)
		 {
			 this._createInfoWindow(marker.infoWindow, point, mark);
		 }

		 this.cache.marker.push(mark);
		 mark.setMap(this.getMap());

		 if (Ext.isObject(listeners))
		 {
			 for (evt in listeners) 
			 {
				 google.maps.event.addListener(mark, evt, listeners[evt]);
			 }
		 }

		 return mark;
	 },
	 
	 /**
	  * @private
	  * Create an Info Window.
	  * @param {Object} inwin an Info Window configuration
	  * @param {google.maps.LatLng} point the point to show the Info Window at
	  * @param {google.maps.Marker} marker a marker to attach the Info Window to
	  */
	 _createInfoWindow : function(inwin, point, marker)
	 {
		 var me = this; 
		 var infoWindow = Ext.create('google.maps.InfoWindow', 
		 {
			 content: inwin.content,
			 position: point
		 });

		 if (marker) 
		 {
			 google.maps.event.addListener(marker, 'click', function(){
				 me._hideAllInfoWindows();
				 infoWindow.open(me.getMap());
			 });
		 }

		 this.cache.infowindow.push(infoWindow);

		 return infoWindow;
	 },
	 
	 /**
	  * @private
	  *  Hide all information windows
	  */
	 _hideAllInfoWindows: function()
	 {
		 for (var i = 0; i < this.cache.infowindow.length; i++)
		 {
			 this.cache.infowindow[i].close();
		 }
	 },

	 /**
	  * @private
	  * Clear all the markers
	  */
	 _clearMarkers : function()
	 {
		 this._hideAllInfoWindows();
		 this._hideMarkers();
	 },

	 /**
	  * @private 
	  * Hide all the markers
	  */
	 _hideMarkers : function()
	 {
		 Ext.each(this.cache.marker, function(mrk){
			 mrk.setMap(null);
		 });
	 },

	 /**
	  * @private 
	  * Show the markers
	  */
	 _showMarkers : function()
	 {
		 var me = this;
		 Ext.each(this.cache.marker, function(mrk){
			 mrk.setMap(me.getMap());
		 });
	 },

	 /**
	  * @private 
	  * Create the marker for the given mapEntry.
	  * @param {Ext.data.Model} record The record to edit
	  */
	 _addShapeForRecord : function (record) 
	 {
		 if (record)
		 {
			 // add marker property
			 var polygonOptions = 
			 {  
					 clickable:true,
					 fillColor: '#'+record.get('color'),     
					 strokeColor: '#'+record.get('color'),     
					 infoWindow : {
						 content : this._infoWindowTemplate.apply({
							 title:record.get('title'), 
							 description:Ext.util.Format.nl2br(record.get('description'))
						 })
					 },
					 recordId: record.id
			 }
			 this._addPolygon(record.get('points'), polygonOptions, null);
		 }
	 },

	 /**
	  * @private 
	  * Creates a single polygon.
	  * @param {Array} points an array of polygon points
	  * @param {Object} polygonOptions an object defining the style to use
	  * @param {Object} listeners the listeners to add to the polygon
	  */
	 _addPolygon: function(points, polygonOptions, listeners)
	 {
		 var polygonPoints = new google.maps.MVCArray();

		 Ext.applyIf(polygonOptions,{
			 strokeColor: '#FF0000',
			 strokeOpacity: 1.0,
			 strokeWeight: 2,
			 fillColor: "#FF0000",
			 fillOpacity: 0.4
		 });

		 var latLng;
		 var bounds = new google.maps.LatLngBounds();
		 Ext.each(points, function(point)
		 {
			 latLng = new google.maps.LatLng(point.lat, point.lng)
			 polygonPoints.push(latLng);
			 bounds.extend(latLng);
		 });

		 var polygon = new google.maps.Polygon(Ext.apply({
			 path: polygonPoints
		 }, polygonOptions));


		 if (polygonOptions.infoWindow)
		 {
			 this._createInfoWindow(polygonOptions.infoWindow, bounds.getCenter(), polygon);
		 }        

		 this.cache.polygon.push(polygon);

		 polygon.setMap(this.getMap());

		 if (typeof listeners === 'object')
		 {
			 for (event in listeners) 
			 {
				 google.maps.event.addListener(polygon, event, listeners[event]);
			 }
		 }

		 return polygon;
	 },

	 /**
	  * @private 
	  * Select the button matching the given mode in the toolbar
	  * @param {Number} newMode the mode to switch to
	  */
	 _selectMapMode: function (newMode) 
	 {
		 // Cancel the drawing of the shape
		 if (this._currentMode == Ametys.plugins.maps.GMapPanel.ADD_SHAPE_MAP_MODE && newMode != this._currentMode)
		 {
			 this._clearDrawingShape();
		 }
		 
 		 switch (newMode)
		 {
			 case Ametys.plugins.maps.GMapPanel.MOVE_MAP_MODE:
				 this.down('#handButton').toggle(true);
				 this.getMap().setOptions({draggableCursor: null});
				 break;
	
			 case Ametys.plugins.maps.GMapPanel.ADD_MARKER_MODE:
				 this.down('#markerButton').toggle(true);
				 this.getMap().setOptions({draggableCursor: 'crosshair'});
				 break;
	
			 case Ametys.plugins.maps.GMapPanel.ADD_SHAPE_MAP_MODE:
				 this.down('#shapeButton').toggle(true);
				 this.getMap().setOptions({draggableCursor: 'crosshair'});
				 break;
	
			 default:
				 throw 'Unknown mode' + newMode;
		 }
		 
		 this._currentMode = newMode;
	 },
	 
	 /*
	  * Markers
	  */

	 /**
	  * @private 
	  * Place a new marker on map click
	  */
	 _placeMarker : function() 
	 {
		 this._selectMapMode(Ametys.plugins.maps.GMapPanel.ADD_MARKER_MODE);
		 
		 // Cancel previous click handlers
		 google.maps.event.clearListeners(this.getMap(), 'click');
		 
		 google.maps.event.addListenerOnce(this.getMap(), "click", Ext.bind(this._placeMarkerHandler, this));
	 },

	 /**
	  * @private 
	  * Open dialog to edit a marker
	  * @param record The current marker. Can NOT be null.
	  */
	 _editMarkerProperties: function (record)
	 {
		 if (!this._markerPropertiesWindow)
		 {
			 this._markerPropertiesWindow = Ext.create('Ametys.plugins.maps.EditMarkerDialog', {saveFn: Ext.bind(this._saveMarkerProperties, this) });
		 }

		 this._editingMarker = record;
		 this._markerPropertiesWindow._initForm(record.get("title"), record.get("description"), record.get("icon"));
		 this._markerPropertiesWindow.show();
	 },

	 /**
	  * @private 
	  * Save marker properties
	  * @param title the title of the marker
	  * @param description the description of the marker
	  * @param icon the path of the icon representing the marker
	  */
	 _saveMarkerProperties: function (title, description, icon) 
	 {
		 this._editingMarker.set("title", title);
		 this._editingMarker.set("description", description);      
		 this._editingMarker.set("icon", icon);              

		 if (!this.store.getById(this._editingMarker.id))
		 {
			 this.store.addSorted(this._editingMarker);
		 }
		 this.store.commitChanges();
		 this._editingMarker = null;
		 
		 this._selectMapMode(Ametys.plugins.maps.GMapPanel.MOVE_MAP_MODE);
	 },

	 /**
	  * @private 
	  * Place a new marker on a the map
	  * @param {Event} event the 'click' event
	  */
	 _placeMarkerHandler: function (event)
	 {
		 if (event.latLng) 
		 {
			 this._selectMapMode(Ametys.plugins.maps.GMapPanel.MOVE_MAP_MODE);

			 this._selectedRecord = Ext.create ('Ametys.plugins.maps.GMapConfiguration.GMapElement', {
				 lat: event.latLng.lat(), 
				 lng: event.latLng.lng(),
				 title: '',
				 description: '',
				 icon: 'pin',
				 // color : this.DEFAULT_SHAPE_COLOR,
				 gtype:'marker'
			 });

			 // Edit marker properties
			 this._editMarkerProperties(this._selectedRecord);      
		 }
	 },

	 /**
	  * @private 
	  * Handles dragEnd handler on marker
	  * @param {Event} event the 'dragend' event
	  */
	 _markerDragEndHandler: function (event)
	 {
		 if (this._selectedRecord && event)
		 {
			 this._selectedRecord.set("lat",event.latLng.lat());
			 this._selectedRecord.set("lng",event.latLng.lng());
			 this.store.commitChanges();
			 this._selectedRecord = null;
		 }
	 },

	 /**
	  * @private 
	  * Handles dragStart handler on marker find the record that is dragged and set the _selectedRecord property
	  * @param {Event} event the 'dragstart' event
	  */  
	 _markerDragStartHandler: function (event)
	 {
		 if (event)
		 {
			 var recordIdx = this.store.findBy(function (record, id)
			 {
			 	return record.get('lat') == event.latLng.lat() && record.get('lng') == event.latLng.lng();
			 });      
			 var record = this.store.getAt(recordIdx);
			 if(record)
			 {
				 this._selectedRecord = record;
			 }     
		 }
	 },

	 /**
	  * @private 
	  * Get the icon url for given record
	  * @param {Ext.data.Record} record the marker's record
	  */
	 _getMarkerIconUrl: function (record)
	 {    
		 var icon = record.get('icon') || 'pin';
		 return Ametys.getPluginResourcesPrefix('maps') + '/img/poi/' + icon + '.png';   
	 },


	 /*
	  * Shape
	  */
	 
	 /**
	  * @private 
	  * Enter in shape mode
	  */
	 _startShape: function () 
	 {
		 this._selectMapMode(Ametys.plugins.maps.GMapPanel.ADD_SHAPE_MAP_MODE);

		 this.shapeMarkers = [];  
		 var polyOptions = {
				 path: [],
				 strokeColor: "#" + Ametys.plugins.maps.GMapPanel.DEFAULT_SHAPE_COLOR,
				 strokeOpacity: 1,
				 strokeWeight: 2,
				 fillColor: "#" + Ametys.plugins.maps.GMapPanel.DEFAULT_SHAPE_COLOR,
				 fillOpacity: 0.4
		 };

		 this._drawingShape = new google.maps.Polyline(polyOptions);
		 this._drawingShape.setMap(this.getMap());    

		 polyOptions = 
		 {
				 path: [],
				 strokeColor: "#" + Ametys.plugins.maps.GMapPanel.DEFAULT_SHAPE_COLOR,
				 strokeOpacity: 1,
				 strokeWeight: 2,
				 clickable : false
		 };

		 this._nextShapePath = new google.maps.Polyline(polyOptions);
		 this._nextShapePath.setMap(this.getMap());
		 
		 // Clear previous listeners
		 google.maps.event.clearListeners(this.getMap(), 'click');
		 
		 google.maps.event.addListener(this.getMap(), "click", Ext.bind(this._addLineMarkerHandler, this));
		 google.maps.event.addListener(this._drawingShape, "dblclick", Ext.bind(this._stopShape, this));
	 },

	 /**
	  * @private 
	  * Stop the drawing of the shape 
	  * @param {Object} event the 'dblclick' event
	  */
	 _stopShape: function (event)
	 {
		 if (event)
		 {
			 // Do not add the marker cause it is managed by the click event
			 // this.addLineMarkerHandler(event);
			 google.maps.event.clearListeners(this.getMap(), 'click');
			 google.maps.event.clearListeners(this._drawingShape, 'dblclick');      
			 google.maps.event.clearListeners(this.getMap(), 'mousemove');      

			 var polyOptions = 
			 {
					 path: this._drawingShape.getPath(),
					 strokeColor: "#" + Ametys.plugins.maps.GMapPanel.DEFAULT_SHAPE_COLOR,
					 strokeOpacity: 1,
					 strokeWeight: 2,
					 fillColor: "#" + Ametys.plugins.maps.GMapPanel.DEFAULT_SHAPE_COLOR,
					 fillOpacity: 0.4
			 };
			 
			 // Remove the line
			 this._drawingShape.setMap(null);
			 // create the polygon
			 this._drawingShape = new google.maps.Polygon(polyOptions);
			 this._drawingShape.setMap(this.getMap());

			 var points = [];
			 
			 this._drawingShape.getPath().forEach(function (latLng){
				 points.push({lat:latLng.lat(), lng:latLng.lng()});
			 })      

			 this._selectedRecord = Ext.create('Ametys.plugins.maps.GMapConfiguration.GMapElement', {
				 title:"",
				 description : "",
				 color: Ametys.plugins.maps.GMapPanel.DEFAULT_SHAPE_COLOR,
				 points: points,
				 gtype:'polygon'
			 });      

			 this._editShapeProperties(this._selectedRecord);
			 
			 // Switch mode
			 this._selectMapMode(Ametys.plugins.maps.GMapPanel.MOVE_MAP_MODE);
		 }
	 },  

	 /**
	  * @private 
	  * Handler invoked when the map is clicked
	  * @param {Event} event the 'click' event
	  */
	 _addLineMarkerHandler: function (event)
	 {
		 if(event && this._drawingShape)
		 {   
			 this._addLineMarker(event.latLng);
			 this._drawingShape.getPath().push(event.latLng);

			 this._nextShapePath.getPath().clear();
			 this._nextShapePath.getPath().push(event.latLng);

			 if (this._drawingShape.getPath().getLength() == 1)
			 {
				 google.maps.event.addListener(this.getMap(), "mousemove", Ext.bind(this._mouseMoveOnShapeDrawHandler, this));
			 }
		 }
	 },

	 /**
	  * @private
	  * Add a marker on the given point for a shape
	  * @param {google.maps.LatLng} point the position of the marker
	  */
	 _addLineMarker: function (point)
	 {
		 var marker = Ext.create ('google.maps.Marker', 
		 {
			 position: point,
			 map: this.getMap(),
			 icon: this._vertexNormal,
			 raiseOnDrag: false,
			 draggable: false,
			 clickable : false
		 });

		 this._shapeMarkers.push(marker);
	 },


	 /**
	  * @private
	  * Handler invoked when the mouse is moved on the map
	  * @param {Object} event the 'mousemove' event
	  */
	 _mouseMoveOnShapeDrawHandler: function (event)
	 {
		 if (event && this._nextShapePath)
		 {
			 if(this._nextShapePath.getPath().getLength() == 2)
			 {
				 this._nextShapePath.getPath().pop();
			 }
			 this._nextShapePath.getPath().push(event.latLng);        
		 }
	 },

	 /**
	  * @private
	  * Open dialog to edit a shape
	  * @param {Ext.data.Model} record The current shape. Can NOT be null.
	  */
	 _editShapeProperties: function (record)
	 {
		 if (!this._shapePropertiesWindow)
		 {
			 this._shapePropertiesWindow = Ext.create('Ametys.plugins.maps.EditShapeDialog', {saveFn: Ext.bind(this._saveShapeProperties, this)});
		 }

		 this._editingShape = record;
		 this._shapePropertiesWindow._initForm(record.get("title"), record.get("description"), record.get("color"));
		 this._shapePropertiesWindow.show();
	 },

	 /**
	  * @private
	  * Save shape properties
	  * @param title the title of the shape
	  * @param description the description of the shape
	  * @param color the colot of the shape
	  */
	 _saveShapeProperties: function (title, description, color)
	 {
		 this._editingShape.set("title", title);
		 this._editingShape.set("description", description);      
		 this._editingShape.set("color", color);              

		 if (!this.store.getById(this._editingShape.id))
		 {
			 this.store.addSorted(this._editingShape);
			 this._clearDrawingShape();
		 }
		 
		 this.store.commitChanges();
		 this._editingShape = null;
	 },

	 /**
	  * @private
	  * Clear the shape being drawn
	  */
	 _clearDrawingShape: function()
	 {
		 // remove drawing shape & next shape
		 if (this._drawingShape)
		 {
			 this._drawingShape.setMap(null);
			 this._drawingShape = null;
		 }
		 
		 if (this._nextShapePath)
		 {
			 this._nextShapePath.setMap(null);
			 this._nextShapePath = null;
		 }

		 // clean addlineMarkers
		 if (this._shapeMarkers)
		 {
			 Ext.each(this._shapeMarkers, function(marker)
			 {
				 marker.setMap(null);
			 });
		 }
	 }
});