// Feuillet.js
// Définition d'une classe d'objet javascript qui spécialise Leaflet
// pour une utilisation dans les sites Biodiv.
// (C) Renaud LAURETTE 2017-2024

function Feuillet(div,lat,lng,zoom,base) {
	// Initialisation des tuiles
	if(base == 'Local') {
		// Si on demande des tuiles locales c'est qu'il n'y a pas de connexion internet
		// donc inutile de proposer les autres tuiles
		this.baseMaps = {
			"Local": L.tileLayer('data/tiles/{z}/{x}/{y}.png', {attribution: '&copy; OpenStreetMap (Local)'}),
			"Muet": L.tileLayer('data/notiles/{z}/{x}/{y}.png')
			
		};
	} else {
		// Si on demande des tuiles non locales, on a le choix des tuiles , mais
		// on n'a pas besoin des tuiles locales
		// Cette liste doit être alignée avec celle de feuillet_liste_api() dans feuillet_fonctions.php
		this.baseMaps = {
			"OSM": L.tileLayer('https://{s}.tile.osm.org/{z}/{x}/{y}.png', {attribution: '&copy; OpenStreetMap'}),
			"Google Hybrid": L.tileLayer('https://mt1.google.com/vt/lyrs=y&x={x}&y={y}&z={z}', {attribution: '&copy; Google Maps'}),
			"Google Streets": L.tileLayer('https://mt1.google.com/vt/lyrs=m&x={x}&y={y}&z={z}', {attribution: '&copy; Google Maps'}),
			"Esri Street Map": L.tileLayer('https://server.arcgisonline.com/ArcGIS/rest/services/World_Street_Map/MapServer/tile/{z}/{y}/{x}', {attribution: 'Tiles &copy; Esri'}),
			"Esri Topo Map": L.tileLayer('https://server.arcgisonline.com/ArcGIS/rest/services/World_Topo_Map/MapServer/tile/{z}/{y}/{x}', {attribution: '&copy; Esri'}),
			"Esri Imagery": L.tileLayer('https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}', {attribution: '&copy; Esri'}),			
			"ArcGIS Streets": L.tileLayer('http://services.arcgisonline.com/ArcGIS/rest/services/World_Street_Map/MapServer/tile/{z}/{y}/{x}', {attribution: '&copy; ArcGIS'}),
			"CartoDB": L.tileLayer('https://cartodb-basemaps-{s}.global.ssl.fastly.net/light_all/{z}/{x}/{y}.png', {attribution: '&copy; CartoDB, &copy; OpenStreetMap'})
			};
	}
	
	// Initialisation de la méthode qui retournera les tuiles utilisées
	this.defaultMap = function() {
		 // lecture du local storage
		 var userbase;
		 if(window.localStorage && window.localStorage.getItem("feuilletBaseMap")) {
		 	 userbase = window.localStorage.getItem("feuilletBaseMap");
		 } else {
		 	 userbase = base;
		 }
		 // activation de la basemap
		 if(userbase in this.baseMaps) {
		 	 return this.baseMaps[userbase];
		 } else {
		 	 return this.baseMaps["OSM"];
		 }
	};
	
	// Déclaration des différentes icones de marker
	var ceci = this;
	this.icons = {};
	['blue','green','grey','kaki','orange','purple','red','yellow'].forEach(
		item => {
			ceci.icons[item] = L.icon({
					iconSize: [25, 41],
					iconAnchor: [12, 41],
					popupAnchor: [1, -34],
					shadowSize: [41, 41],
					iconUrl: `plugins/feuillet/lib/leaflet/images/${item}-mkr.png`,
					shadowUrl:  'plugins/feuillet/lib/leaflet/images/marker-shadow.png'
			});
		});
		
	// Reste des initialisations
	this.currentOverlay=1;
	this.overlays = {};
	this.userStyles = {};
	this.controlLayer = null;
	this.nbFeatures = 0;
	this.map = L.map(div, {fullscreenControl: true}).setView([lat, lng], zoom);
	this.defaultMap().addTo(this.map);
	this.map.on('baselayerchange',this.onBaseLayerChange);
	this.pointColorSelector = null;
	this.drawLayer = null;
}

// Methods definition
Feuillet.prototype = {
	
	// Le jeu de couleurs pour différentier les couches qui
	// utilisent les styles automatiques
	colors: [ 
             "#000000", 						// noir 
             "#9933FF", "#FF00FF", 				// violets
             "#663300", "#990000", "#FF0000", 	// rouges
             "#009966", "#996600", 				//marrons
             "#FF9900", "#FFFF00", 				// jaune orange
             "#005500", "#336600", "#339900", "#00FF00", // verts
             "#33FFCC", "#00FFFF", "#0000FF" 	// bleus
    ],

    // Retourne la carte courante	
	getMap: function() {
		return this.map;
	},

    // Méthode retournant un style différent à chaque appel
    // Utilisée pour la gestion automatique des styles
    defaultStyle: function() {
        return {
        		"color" : this.colors[0],
        		"weight" : 2,
        		"opacity" : .75,
        		"fillOpacity" : .3   
         };
     },
     
     // interception de l'évenement de changement de base map
     onBaseLayerChange: function(e) {
     	 if(window.localStorage) {
     	 	 window.localStorage.setItem("feuilletBaseMap",e.name);
     	 }   	 	 
     },
          
     // Enregistrement d'un ensemble de styles nommés qui pourront être
     // référencés par les couches
     addStyle: function(uData) {
     	 if(uData instanceof Array) {
     	 	 for (var i = 0, len = uData.length; i < len; i++) {
     	 	 	 var uStyle = uData[i];
     	 	 	 this.userStyles[uStyle.name] = uStyle.style;
     	 	 }
     	 } else {
     	 	  this.userStyles[uData.name] = uData.style;
     	 }
     	 return this;
     },
      
     // Sélection du style de la couche :
     // - nommé si le nom 'name' est connu
     // - automatique sinon
     selectStyle: function(name) {
     	 var styler;
     	 if(name in this.userStyles) {
     	 	 // on choisit un style nommé défini par l'utilisateur
     	 	 var localStyle = this.userStyles[name];
     	 	 styler = function(feature) {
     	 	 	 var xStyle = localStyle;
     	 	 	 return xStyle;
     	 	 }
     	 } else {	 	 
     	 	 // Style par défaut. Alloue une couleur
     	 	 var index = this.currentOverlay;
     	 	 var localStyle = this.defaultStyle();
     	 	 var nbColors = this.colors.length;
     	 	 localStyle.color = this.colors[index % nbColors];
        
     	 	 styler = function(feature) {
     	 	 	 var xStyle = localStyle;
     	 	 	 // Quand le feature dispose d'un style, il l'impose
     	 	 	 if(('properties' in feature) && ('style' in feature.properties)) {
     	 	 	 	 var fStyle = feature.properties.style;
     	 	 	 	 for( var k in fStyle) { xStyle[k] = fStyle[k]; }
     	 	 	 } 
     	 	 	 return xStyle;
     	 	 }
     	 }
        return styler;
     },
     
     // Associe au feature un popup comprenant soit le contenu
     // de la propriété 'popupContent', soit l'ensemble des propriétés
     featurePopup:  function (feature, layer) { 
        if (feature.properties) {
          if(feature.properties.popupContent) {
          	// this feature have a property named popupContent?
        	layer.bindPopup(feature.properties.popupContent);
          } else {
          	// no popupContents : display all properties
            var s="";
            for(var p in feature.properties) {
                    s += p + ": " + feature.properties[p] + "</br>\n";
                    }
            layer.bindPopup(s);
          }
        }
     },
 
     // Déclare une fonction retournant une couleur de marqueur 
     // en fonction d'un feature Geojson et d'un latlng. 
     // Sa signature doit être : selector(feature, latlng)
     setPointColorSelector: function(selector) {
     	 this.pointColorSelector = selector;
     	 return this;
     },

     // Association d'une icone à un Marker
     // Si le feature à une propriété 'confidential', on l'utilise pour choisir
     // la couleur de l'icone. Sinon on met la couleur par défaut.
     pointMapper: function(that) {
     	 return function(feature,latlng) {
     	 	 if(that.pointColorSelector) {
     	 	 	 let color = that.pointColorSelector(feature,latlng);
     	 	 	 return L.marker(latlng).setIcon(that.icons[color]);
     	 	 } else {
     	 	 	 return L.marker(latlng);
     	 	 }
     	 };
     },
     
     recordMarker: function(latfield,lngfield,lat,lng) {
     	 var marker = new L.marker([lat, lng]);
     	 var carte = this.map;
     	 marker.addTo(carte);
  
     	 function onMapClick(e) {
     	 	 if(marker) {
     	 	 	 carte.removeLayer(marker);
     	 	 }
     	 	 marker = new L.Marker(e.latlng);
     	 	 marker.addTo(carte);
     	 	 document.getElementById(latfield).value = e.latlng.lat;
     	 	 document.getElementById(lngfield).value = e.latlng.lng;
     	 };
     	 carte.on('click', onMapClick);    	 
     },
     
     _addOneOverlay: function(elem) {
       if(elem.type != null) {
     	 // Fonctions par defaut
     	 var oef = this.featurePopup;
     	 var ptl = this.pointMapper(this);
     	 // Surcharges possibles
     	 if(elem.oef)
     	 	 oef = elem.oef;
     	 if(elem.ptl)
     	 	 ptl = elem.ptl;
     	 // Preparation du layer GEOjson
     	 var cusLayer = L.geoJson(null, {
     	 	 	 	style: this.selectStyle(elem.style),
     	 	 	 	onEachFeature: oef,
     	 	 	 	pointToLayer: ptl
     	 });
     	 var olayer = null;
     	 if(elem.type == 'data') {
     	 	 // si le type est 'data', il n'y a pas de champ 'url'
     	 	 // mais un champs 'data' qui contient une string GEOJson.
     	 	 cusLayer.addData(elem.data);
     	 	 olayer = cusLayer;
     	 } else {
     	 	 olayer = omnivore[elem.type](elem.url,null,cusLayer);
     	 }
     	 if(elem.onAdd) {
     	 	 olayer.on("add",elem.onAdd);
     	 }
     	 if(elem.onReady) {
     	 	 olayer.on("ready",elem.onReady);
     	 }
     	 this.overlays[elem.name] = olayer; 
     	 this.currentOverlay++;
       }
     },
     
     // Définition d'un ensemble d'overlays qui seront référencés dans
     // le contrôle des overlays. Convient à une définition statique
     // faisant appel à des urls de fichiers. Différents types sont supportés
     // par le plugin Omnivore de Leaflet.
     addOverlays: function(overlays) {
     	 if(overlays instanceof Array) {
     	 	 for (var i = 0, len = overlays.length; i < len; i++) {
     	 	 	var elem = overlays[i];
     	 	 	this._addOneOverlay(elem);
     	 	 } 
     	 } else {
     	 	 this._addOneOverlay(overlays);
     	 }
     	 return this;
     },
     
     // Affichage d'un overlay à partir d'une string
     // fonction générique destinée à remplacer les 3 suivantes
     addStringOverlay: function(name,contents,type) {
     	 switch(type) {
     	 case 'kml':
     	 case 'gpx':
     	 	this.controlLayer.addOverlay(omnivore[type].parse(contents), name);
     	 	return this; 
     	default:
     		this.controlLayer.addOverlay(L.geoJson(contents), name);
     		return this; 
     	 }
     },

   	//*** ADD  
     
     addLayerAsOverlay: function(layer,name) {
         this.overlays[name] = layer;
         this.currentOverlay++;
         return this;
     },

     autofitOverlays: function() {
     	 var that = this;
     	 that.map.on('layeradd layerremove', function() {
     	 		 var bounds = new L.LatLngBounds();
     	 		 for(over in that.overlays) {
     	 		 	 if(that.map.hasLayer(that.overlays[over])) {
     	 		 	 	 bounds.extend(that.overlays[over].getBounds());
     	 		 	 }
     	 		 }
     	 		 if (bounds.isValid()) {
     	 		 	 // Valid, fit bounds
     	 		 	 that.map.fitBounds(bounds);
     	 		 } else {
     	 		 	 // Invalid, fit world
     	 		 	 // that.map.fitWorld();
     	 		 } 
     	 });
     	 return this;
     },    	 		 
     
     setView: function (lat,lng,zoom) {
     	 this.map.setView([lat, lng], zoom);
     },     
     
     
   /// *** ADDED  
     
     // Affichage du controle des layers sur la carte
     setLayers: function(show) {
     	 this.controlLayer = L.control.layers(this.baseMaps,this.overlays);
     	 this.controlLayer.addTo(this.map);
     	 if(show) {
     	   for(over in this.overlays ) {
     	   		this.overlays[over].addTo(this.map);
     	   }
     	 }
     	 return this;
     },
     
     // Affiche ou non (selon show) l'overlay de nom 'name'
     setLayerVisibility: function(name, show) {
     	 if(show) {
     	 	 this.overlays[name].addTo(this.map);
     	 } else {
     	 	 this.overlays[name].remove();
     	 }
     },
     
     // Ajout d'un layer geojson calculé dynamiquement.
     // L'url est celle d'un service web, et options est un objet json passé en POST
     // Le layer sera ajouté aux overlays sous le nom "nom"
     // En cas d'erreur, un message s'affiche dans le div "errdiv" 
     addDynamicGeojson: function(url, options, nom, errdiv) {
     	 var onReturn = function(that, name, errordiv) {
     	 	 return function(data) {
				if (data){
					var lay = L.geoJson(data, {
						onEachFeature: that.featurePopup,
						pointToLayer: that.pointMapper(that)
						}).addTo(that.map);	
					that.controlLayer.addOverlay(lay, name);
				} else {
					$(errordiv).html('aucune donn&eacute;e.');
				}
			};
		 };
     	 jQuery.getJSON(
     	 	 url, options,
     	 	 onReturn(this, nom, errdiv)
     	 	 );
     	   	 	 	 
     },
     
     // Variante avec callback sur les données
      addDynamicGeojsonCbk: function(url, options, nom, errdiv, callback) {
     	 var onReturn = function(that, name, errordiv, cbk) {
     	 	 return function(data) {
				if (data){
					var lay = L.geoJson(data, {
						onEachFeature: that.featurePopup,
						pointToLayer: that.pointMapper(that)
						}).addTo(that.map);	
					that.controlLayer.addOverlay(lay, name);
					cbk(data, that, lay);
				} else {
					$(errordiv).html('aucune donn&eacute;e.');
				}
			};
		 };
     	 jQuery.getJSON(
     	 	 url, options,
     	 	 onReturn(this, nom, errdiv,callback)
     	 	 );
     	   	 	 	 
     },
     
     // Initialisation de draw avec un polygone décrit en tant que feature GeoJSON
     // dans une textarea dont l'id est 'dumparea'. 
     // Fonction utilisée pour permettre la ré-édition d'un polygone strocké sous
     // forme textuelle.
     initDrawnShape: function(dumparea) {
     	 var perimetre = document.getElementById(dumparea).value;
     	 if(perimetre && (perimetre.length > 10)) {
     	 	 const pshape = JSON.parse(perimetre);
     	 	 if(typeof pshape === 'object' && pshape != null && Object.hasOwn(pshape,'geometry')) {
     	 	 	 console.log(pshape.geometry.coordinates[0]);
     	 	 	 // On crée explicitement un polygone en inversant les coordonnées
     	 	 	 // GeoJSON : [lon,lat] mais Leaflet : [lat,lon]
     	 	 	 // Un objet GeoJSON ne serait pas reconnu par leaflet.draw
     	 	 	 L.polygon(pshape.geometry.coordinates[0].map((x)=> x.reverse()))
     	 	 	 	.addTo(this.drawLayer)
     	 	 	 	.setStyle({color: '#ff0000'});
     	 	 	 this.nbFeatures = 1;
     	 	 }
     	 }
     },
     
     // Ajout d'un layer pour dessiner sur la carte grace à leaflet.draw
     // La configuration ne permet que de dessiner un polygone unique pour
     // définir un lieu géographique. Dumparea est l'id d'un textarea qui
     // recevra une description geojson du polygone dessiné.
     // --- NE FONCTIONNE PAS AVEC LEAFLET 1.9.4 ---
     addDrawPolygonLayer: function(dumparea, label, url, descarea) {
     	var drawnItems = new L.FeatureGroup();     
		this.controlLayer.addOverlay(drawnItems, label);
		this.map.addLayer(drawnItems);
		this.drawLayer = drawnItems;
     	
		// Set the title to show on the polygon button
		L.drawLocal.draw.toolbar.buttons.polygon = 'Definir un lieu';

		var drawControl = new L.Control.Draw({
			// position: 'topright',
			draw: {
				polyline: false,
				rectangle:false,
				polygon: {
					allowIntersection: false,
					showArea: true,
					drawError: {
						color: '#b00b00',
						timeout: 1000
					},
					shapeOptions: {
						color: '#ff0000' 
					}
				},
				circle: false,
				marker: false
			},
			edit: {
				featureGroup: drawnItems,
				remove: false
			}
		});
		
		this.map.addControl(drawControl);

		var that = this;
		this.map.on('draw:created', function (e) {
				console.log("Creation event.");
				if(that.nbFeatures == 0) {
					console.log("Creation starts.");
					that.nbFeatures = 1;
					var type = e.layerType, layer = e.layer;
					var whr = document.getElementById(dumparea);
					whr.value = JSON.stringify(layer.toGeoJSON());
					drawnItems.addLayer(layer);
					console.log("Created layer.");
				}
		});

		this.map.on('draw:edited', function (e) {
			var layers = e.layers;
			var whr = document.getElementById(dumparea);
			whr.value = "";
			layers.eachLayer(function(layer) {
  				whr.value += JSON.stringify(layer.toGeoJSON());
			});
		});			

		if(url) {
			// Initialisation de la forme à éditer par le contenu d'une URL pointant 
			// vers un GeoJSON (ou lieu). Un seul feature doit être présent.
			 var whr = document.getElementById(dumparea);
			 var dsc = document.getElementById(descarea);
     	 	 var cusLayer = L.geoJson(null, {
     	 	  		  onEachFeature: function(feature,layer) {
     	 	  		  	  layer.props = feature.properties;
     	 	  		  	  drawnItems.addLayer(layer);
     	 	  		  	  whr.value = JSON.stringify(layer.toGeoJSON());
     	 	  		  	  if(feature.properties.descriptif) {
     	 	  		  	  	  dsc.value =  feature.properties.descriptif;
     	 	  		  	  }
     	 	  		  }
     	 	 });
     	 	 var olayer = omnivore.geojson(url,null,cusLayer);
     	 }
     }
};





