<?php
/**
 * @author       Aicha Vack
 * @package      Joomla.Administration
 * @subpackage   com_visforms
 * @link         https://www.vi-solutions.de
 * @license      GNU General Public License version 2 or later; see license.txt
 * @copyright    2017 vi-solutions
 */

namespace Visolutions\Component\Visforms\Administrator\Service\HTML;

// no direct access
defined('_JEXEC') or die('Restricted access');

use Joomla\CMS\Factory;
use Joomla\CMS\Language\Text;
use Joomla\CMS\Component\ComponentHelper;

class Visformslocation
{
	protected static $loaded = array();

	public static function createDbValue($setting = array()) {
		if (!is_array($setting)) {
			$setting = array();
		}
		return \VisformsHelper::registryStringFromArray($setting);
	}

	public static function extractDbValue($string = '') {
		return \VisformsHelper::registryArrayFromString($string);
	}

	public static function includeLocationSearchJs() {
		if (!empty(static::$loaded[__METHOD__])) {
			return true;
		}
		$doc = Factory::getApplication()->getDocument();
		$script = '
			jQuery(document).ready(function () {
				jQuery(".placessearchbox").keypress(function (event) {
			        var key = event.keyCode;
			        if (key === 13) {
			            event.preventDefault();
			            //if we call geocodeSearchAddress now, this function will be called afterwards a second time by the onchange event handler of the field
			            //So we "trigger" change event by leaving the field by setting the focus to matching radius field
			            var id = event.target.getAttribute("id") + "_radius";
			            var next = document.getElementById(id);
			            next.focus(); 
			            return false;
			        }
			    });
			});
			function geocodeSearchAddress(element) {
				var id = jQuery(element).attr("id");
				if (element.value) {
					var geocoder = new google.maps.Geocoder();
					geocoder.geocode({"address": element.value}, function(results, status) {
			            if (status === "OK") {
			                var lat = results[0].geometry.location.lat();
			                var lng = results[0].geometry.location.lng();
			                var location = \'{"lat":"\' + results[0].geometry.location.lat() + \'", "lng":"\' + results[0].geometry.location.lng()+ \'"}\';
			                jQuery("#" + id + "_location").val(location);
			            } else {
			                alert("'. addslashes(Text::_( "COM_VISFORMS_GEOCODING_ERROR" )).' " + status);
			                jQuery("#" + id + "_location").val("");
			            }
			        });
				} else {
					jQuery("#" + id + "_location").val("");
				}
			}
		';
		$doc->addScriptDeclaration($script);
		self::loadGoogleMapsApi();
		static::$loaded[__METHOD__] = true;
		return false;
	}

	public static function includeLocationFieldJs($mapcontextmenu = false) {
		if (!empty(static::$loaded[__METHOD__])) {
			return true;
		}
		$doc = Factory::getApplication()->getDocument();
		$script = '
			function onChangeCoord(event) {
				var lat = parseFloat(document.getElementById("field"+event.data.field+"_lat").value);
				var lng = parseFloat(document.getElementById("field"+event.data.field+"_lng").value);
				updateMap(lat, lng, event.data.field);
				clearSearchField(event.data.field);
				validateLatLngInputs(event.data.field);
			}
			function updateMap(lat, lng, fieldId) {
				removeVfMarker(fieldId);
				if (!isNaN(lat) && !isNaN(lng)) {
					setVfMarker(lat, lng, fieldId);
				}
			}
			function setUserPosition(event) {
				event.preventDefault();
				var fieldId = event.data.field;
				if (navigator.geolocation) {
					navigator.geolocation.getCurrentPosition(
						function(position) {
						if (event.data.displayMap) {
								updateMap(position.coords.latitude, position.coords.longitude, fieldId);
							}
							updateLocationInputFields(position.coords.latitude.toFixed(8), position.coords.longitude.toFixed(8), fieldId);
							clearSearchField(event.data.field);
						}, function (error) {
							var code = error.code;
							var msg = ["'. addslashes(Text::_( "COM_VISFORMS_UNKNOWN_ERROR" )).'", 
							"'. addslashes(Text::_( "COM_VISFORMS_NO_PERMISSION" )).'", 
							"'. addslashes(Text::_( "COM_VISFORMS_NO_POSITION" )).'", 
							"'. addslashes(Text::_( "COM_VISFORMS_GEOLOCATION_TIMEOUT" )).'"];
							alert("'. addslashes(Text::_( "COM_VISFORMS_GEOLOCATION_POSITION_ERROR" )).' "+ msg[error.code]);
						}
					);
				} else {
					alert("'. addslashes(Text::_( "COM_VISFORMS_GEOLOCATION_NOT_SUPPORTED" )).'");
				}
			}
			function updateLocationInputFields(lat, lng, id) {
				var latFieldId = "field" + id + "_lat";
				var lngFieldId = "field" + id + "_lng"
				jQuery("#"+latFieldId).val(lat);
				jQuery("#"+lngFieldId).val(lng);
				validateLatLngInputs(id);
			}
			function validateLatLngInputs(id){
				var latFieldId = "field" + id + "_lat";
				var lngFieldId = "field" + id + "_lng"
				var latError = false;
				var lngError = false;
				if (jQuery("#"+latFieldId).hasClass("error") || jQuery("#"+latFieldId).hasClass("valid")) {latError = true;}
				if (jQuery("#"+lngFieldId).hasClass("error") || jQuery("#"+lngFieldId).hasClass("valid")) {lngError = true;}
				if (latError) {jQuery("#"+latFieldId).valid();}
				if (lngError) {jQuery("#"+lngFieldId).valid();}
			}
			function removeVfMarker(fieldId) {
				var markersName = "vfMarkers" + fieldId;
				if (typeof window[markersName] != "undefined") {
					for (var i = 0; i < window[markersName].length; i++) {window[markersName][i].setMap(null);}
					window[markersName] = [];
				}
			}
			function setVfMarker(lat, lng, fieldId) {
				var markersName = "vfMarkers" + fieldId;
				var mapName = "vfMap" + fieldId;
				if (typeof window[mapName] != "undefined" && typeof window[markersName] != "undefined") {
					var pos = {lat: lat, lng: lng};
					var marker = new google.maps.Marker({position:  pos , map: window[mapName]});
					window[mapName].setCenter(pos);
					window[markersName].push(marker);
				}
			}
			function initLocation(fieldOptions) {
				fieldOptions = fieldOptions || {};
				if (!fieldOptions.fieldId) {return false;}
				this.fieldId = fieldOptions.fieldId;
				this.displayMap = fieldOptions.displayMap;
				this.defaultPos = fieldOptions.attPos;
				this.mapCenter = fieldOptions.mapCenter;
				this.latFieldId = "field" + this.fieldId + "_lat";
				this.lngFieldId = "field" + this.fieldId + "_lng"
				this.zoom = fieldOptions.zoom;
				this.searchBoxButtonId = "searchLocationfield" + this.fieldId;			
				this.geocoder = new google.maps.Geocoder();
				if (this.displayMap) {
					this.markersName = "vfMarkers" + this.fieldId;
					this.mapName = "vfMap" + this.fieldId;
					this.center = (this.defaultPos.lat && this.defaultPos.lng) ? this.defaultPos : this.mapCenter;
					this.showMarkers = (this.defaultPos.lat && this.defaultPos.lng) ? 1 : 0;
					this.mapOptions = {center: this.center, zoom: this.zoom, mapTypeId: \'roadmap\'}; 
					this.map = new google.maps.Map(document.getElementById(this.mapName), this.mapOptions);
					this.map.setOptions({draggableCursor:"default"});
	                this.map.setOptions({draggingCursor:"move"});
					if (this.showMarkers) {
						this.marker = new google.maps.Marker({position:  this.center , map: this.map});
						if (typeof window[this.markersName] != "undefined") {window[this.markersName].push(this.marker);}
					}
					window[this.mapName] = this.map;
					jQuery("#"+this.latFieldId).on("change", {field: this.fieldId}, onChangeCoord);
					jQuery("#"+this.lngFieldId).on("change", {field: this.fieldId}, onChangeCoord);
					jQuery("#"+this.latFieldId).keypress({handler: "onChangeCoord", field: this.fieldId}, vfMapKeyEventHandler);
					jQuery("#"+this.lngFieldId).keypress({handler: "onChangeCoord", field: this.fieldId}, vfMapKeyEventHandler);
					jQuery(".conditional.field"+fieldId).on("reloadVfMap", {field: this.fieldId}, refreshVfMap);
					jQuery(".field"+fieldId).closest("form").on("reloadVfMap", {field: this.fieldId}, refreshVfMap);
				}
				jQuery("#getLocationfield"+this.fieldId).on("click", {field: this.fieldId, displayMap: this.displayMap}, setUserPosition);
				jQuery("#" + this.searchBoxButtonId).on("click", {field: this.fieldId, displayMap: this.displayMap, geocoder: this.geocoder }, geocodeAddress);
				jQuery("#searchLocationInputfield" + this.fieldId).keypress({handler: "geocodeAddress", field: this.fieldId, displayMap: this.displayMap, geocoder: this.geocoder }, vfMapKeyEventHandler);
			}
			function vfMapKeyEventHandler (event) {
				var key = event.keyCode;
		        if (key === 13) {
		            event.preventDefault();
		            if (typeof event.data.handler !== "undefined" && typeof window[event.data.handler] !== "undefined") {
		                window[event.data.handler](event);
		            }
		            return false;
		        }
			}
			function geocodeAddress(event) {
				event.preventDefault();
				var searchBox = "searchLocationInputfield" + event.data.field;
				var geocoder = event.data.geocoder;
				var value = jQuery("#" + searchBox).val();
				// using event object in function geocode does not work, if click event is triggered programatically on search button
				// store values in local variables for use in geocoder.geocode()
				var field = event.data.field;
				var displayMap = event.data.displayMap;
				if (value) {
					geocoder.geocode({"address": value}, function(results, status) {
		            if (status === "OK") {
		                var lat = results[0].geometry.location.lat();
		                var lng = results[0].geometry.location.lng();
		                updateLocationInputFields(lat.toFixed(8), lng.toFixed(8), field);
		                if (displayMap) {
							updateMap(lat, lng, field);
						}
		            } else {
		                alert("'. addslashes(Text::_( "COM_VISFORMS_GEOCODING_ERROR" )).' " + status);
		            }
		        });
				}
			}
			
			function clearSearchField(fieldId) {
				jQuery("#searchLocationInputfield" + fieldId).val("");
			}
			function refreshVfMap(event) {
				var mapName = "vfMap" + event.data.field;
				if (typeof window[mapName] != "undefined") {
					var map = window[mapName];
					var center = map.getCenter();
					google.maps.event.trigger(map, "resize");
					map.setCenter(center);
					window[mapName] = map;
				}
			}
			function initDataMap(fieldOptions) {
				fieldOptions = fieldOptions || {};
				if (!fieldOptions.fieldId) {return false;}
				this.fieldId = fieldOptions.fieldId;
				this.defaultPos = fieldOptions.attPos;
				this.center = fieldOptions.attPos;
				this.zoom = fieldOptions.zoom;
				this.markersName = "vfMarkers" + this.fieldId;
				this.mapName = "vfMap" + this.fieldId;
				this.mapOptions = {center: this.center, zoom: this.zoom, mapTypeId: \'roadmap\'}; 
				this.map = new google.maps.Map(document.getElementById(this.mapName), this.mapOptions);
				this.map.setOptions({draggableCursor:"default"});
	            this.map.setOptions({draggingCursor:"move"});
				this.marker = new google.maps.Marker({position:  this.center , map: this.map});
				if (typeof window[this.markersName] != "undefined") {window[this.markersName].push(this.marker);}
				window[this.mapName] = this.map;
			};
		';

		$doc->addScriptDeclaration($script);

		if(isset($mapcontextmenu) && $mapcontextmenu) {
			self::addGoogleMapContextMenuJS();
		}

		self::loadGoogleMapsApi();
		static::$loaded[__METHOD__] = true;
		return false;
	}

	protected static function addGoogleMapContextMenuJS() {
		$doc = Factory::getApplication()->getDocument();
		$script = '
		jQuery(document).ready(function() {
	        google.maps.ContextMenu = function(mapid, options, callback) {
	            this.mapid = mapid;
	            let map = window["vfMap" + this.mapid];
	            this.setMap(map);
	            options = options || {};
	            this.id = options.id;
	            this.classNames_ = options.classNames || {};
	            this.map_ = map;
	            this.mapDiv_ = map.getDiv();
	            this.menuItems_ = options.menuItems || [];
	            this.pixelOffset = options.pixelOffset || new google.maps.Point(10, -5);
	            this.callback = callback || null;
	            this.eventName = options.eventName || "menu_item_selected";
	
	            // draggableCursor: "url(http://www.example.com/icon.png), auto;"
	            // draggingCursor: "url(http://www.example.com/icon.png), auto;"
	            // map.setOptions({draggableCursor:"crosshair"});   // will set the cursor toa crosshair.
	            // map.setOptions({draggableCursor:null});          // sets it back to default.
	
	            map.setOptions({draggableCursor:"default"});        // the standard arrow
	            map.setOptions({draggingCursor:"move"});            // the navigation cross
	
	            let $this = this; //	used for closures
	
	            google.maps.event.addListener(map, "rightclick", function(event) {
	                $this.show(event.latLng, event.pixel);
	            });
	
	            this.addListener("menu_item_selected", function(event) {
	                console.log("menu_item_selected: " + event.id);
	                switch (event.id) {
	                    case "menu_option_1":
	                        this.map_.setCenter(this.latLng_);
	                        console.log("done");
	                        break;
	                    case "menu_option_2":
	                        updateMap(this.latLng_.lat(), this.latLng_.lng(), this.mapid);
	                        updateLocationInputFields(this.latLng_.lat(), this.latLng_.lng(), this.mapid);
	                        break;
	                }
	            });
	
	            /**
	             * [createMenuItem description]
	             * @param  {Object} itemOptions An object with a label (required), a className (optional) and an id (optional)
	             * @param  {Boolean} before when True, the menuitem is prepended to the menu instead of appended.
	             */
	            this.createMenuItem = function(itemOptions, before) {
	                let self = this;
	                if (!self.menu_) {
	                    console.log("No menu");
	                    return;
	                }
	                itemOptions = itemOptions || {};
	                let menuItem = document.createElement("div");
	                menuItem.innerHTML = itemOptions.label;
	                menuItem.className = itemOptions.className || self.classNames_.menuItem;
	                menuItem.eventName = itemOptions.eventName || self.eventName;
	                if (itemOptions.id) {
	                    menuItem.id = itemOptions.id;
	                }
	                menuItem.style.cssText = "cursor:pointer; white-space:nowrap";
	
	                menuItem.onclick = function() {
	                    google.maps.event.trigger(self, menuItem.eventName, { position: self.latLng_, id: menuItem.id });
	                };
	
	                if (before) {
	                    self.menu_.insertBefore(menuItem, self.menu_.firstChild);
	                }
	                else if (itemOptions.container_id) {
	                    document.getElementById(itemOptions.container_id).appendChild(menuItem);
	                }
	                else {
	                    self.menu_.appendChild(menuItem);
	                }
	            };
	
	            /**
	             * [createMenuGroup description]
	             * @param  {Boolean} before when True, the menugroup is prepended to the menu instead of appended.
	             */
	            this.createMenuGroup = function(itemOptions, before) {
	                let self = this;
	                if (!self.menu_) {
	                    console.log("No menu");
	                    return;
	                }
	                itemOptions = itemOptions || {};
	                let menuGroup = document.createElement("span");
	
	                if (itemOptions.id) {
	                    menuGroup.id = itemOptions.id;
	                }
	                if (before) {
	                    self.menu_.insertBefore(menuGroup, self.menu_.firstChild);
	                }
	                else {
	                    self.menu_.appendChild(menuGroup);
	                }
	            };
	
	            /**
	             * [createMenuSeparator description]
	             * @param  {Boolean} before when True, the menuitem is prepended to the menu instead of appended.
	             */
	            this.createMenuSeparator = function(itemOptions, before) {
	                let self = this;
	                if (!self.menu_) {
	                    console.log("No menu");
	                    return;
	                }
	                itemOptions = itemOptions || {};
	                let menuSeparator = document.createElement("div");
	                if (self.classNames_.menuSeparator) {
	                    menuSeparator.className = self.classNames_.menuSeparator;
	                }
	                if (itemOptions.id) {
	                    menuSeparator.id = itemOptions.id;
	                }
	                if (before) {
	                    self.menu_.insertBefore(menuSeparator, self.menu_.firstChild);
	                }
	                else if (itemOptions.container_id) {
	                    document.getElementById(itemOptions.container_id).appendChild(menuSeparator);
	                }
	                else {
	                    self.menu_.appendChild(menuSeparator);
	                }
	            };
	        };
	
	        google.maps.ContextMenu.prototype = new google.maps.OverlayView();
	
	        google.maps.ContextMenu.prototype.draw = function() {
	            if (this.isVisible_) {
	                let mapSize = new google.maps.Size(this.mapDiv_.offsetWidth, this.mapDiv_.offsetHeight);
	                let menuSize = new google.maps.Size(this.menu_.offsetWidth, this.menu_.offsetHeight);
	                // we do have a strange offset in this case
	                // let mousePosition = this.getProjection().fromLatLngToDivPixel(this.latLng_);
	                let mousePosition = this.pixel_;
	
	                let left = mousePosition.x;
	                let top = mousePosition.y;
	
	                if (mousePosition.x > mapSize.width - menuSize.width - this.pixelOffset.x) {
	                    left = left - menuSize.width - this.pixelOffset.x;
	                }
	                else {
	                    left += this.pixelOffset.x;
	                }
	
	                if (mousePosition.y > mapSize.height - menuSize.height - this.pixelOffset.y) {
	                    top = top - menuSize.height - this.pixelOffset.y;
	                }
	                else {
	                    top += this.pixelOffset.y;
	                }
	
	                this.menu_.style.left = left + "px";
	                this.menu_.style.top = top + "px";
	            }
	        };
	
	        google.maps.ContextMenu.prototype.getVisible = function() {
	            return this.isVisible_;
	        };
	
	        google.maps.ContextMenu.prototype.hide = function() {
	            if (this.isVisible_) {
	                this.menu_.style.display = "none";
	                this.isVisible_ = false;
	            }
	        };
	
	        google.maps.ContextMenu.prototype.onAdd = function() {
	            let $this = this; //	used for closures
	
	            let menu = document.createElement("div");
	            if (this.classNames_.menu) {
	                menu.className = this.classNames_.menu;
	            }
	            if (this.id) {
	                menu.id = this.id;
	            }
	            menu.style.cssText = "display:none; position:absolute;z-index:250;";
	            $this.menu_ = menu;
	            for (let i = 0, j = this.menuItems_.length; i < j; i++) {
	                if (this.menuItems_[i].label) {
	                    this.createMenuItem(this.menuItems_[i]);
	                }
	                else {
	                    this.createMenuSeparator();
	                }
	            }
	            menu.onmouseover = function() {
	                $this.map_.inmenu = true;
	                //console.log("mouseover Menu");
	            };
	            menu.onmouseout = function() {
	                $this.map_.inmenu = false;
	                //console.log("mouseout Menu");
	            };
	            //delete this.classNames_;
	            delete this.menuItems_;
	
	            this.isVisible_ = false;
	
	            this.position_ = new google.maps.LatLng(0, 0);
	
	            google.maps.event.addListener(this.map_, "click", function(mouseEvent) {
	                $this.hide();
	            });
	            this.getPanes().floatPane.parentNode.parentNode.appendChild(menu);
	
	            if (this.callback) this.callback();
	        };
	
	        google.maps.ContextMenu.prototype.onRemove = function() {
	            this.getPanes().floatPane.appendChild(this.menu);
	            this.menu_.parentNode.removeChild(this.menu_);
	            delete this.mapDiv_;
	            delete this.menu_;
	            delete this.latLng_;
	            delete this.pixel_;
	        };
	
	        google.maps.ContextMenu.prototype.show = function(latLng, pixel) {
	            if (!this.isVisible_) {
	                this.menu_.style.display = "block";
	                this.isVisible_ = true;
	            }
	            this.latLng_ = latLng;
	            this.pixel_ = pixel;
	            this.draw();
	        };
	
	        // start
	
	        let menuStyle = {
	            menu: "vfMap_context_menu",
	            menuSeparator: "vfMap_context_menu_separator",
	            menuItem: "vfMap_context_menu_item"
	        };
	
	        let contextMenuOptions = {
	            id: "map_rightclick",
	            eventName: "menu_item_selected",
	            classNames: menuStyle,
	            menuItems:
	                [
	                    {label: "'. addslashes(Text::_( "COM_VISFORMS_GEOLOCATION_CONTEXTMENU_CENTER_MAP" )).'", id: "menu_option_1"},
	                    {label: "'. addslashes(Text::_( "COM_VISFORMS_GEOLOCATION_CONTEXTMENU_CENTER_MAP_SET_MARKER" )).'", id: "menu_option_2"},
	                ]
	        };
	
	        const useGlobal = false;
	        
	        if(useGlobal) {
	            let fieldMaps = [];
	            jQuery("div[id^=vfMap]").each(function() {
	                let fid = $(this).attr("id").replace("vfMap", "");
	                fieldMaps.push({
	                    key: fid,
	                    value: new google.maps.ContextMenu(fid, contextMenuOptions, function () {
	                        // optional callback ...
	                    })
	                });
	            });
	        }
	        else {
	            jQuery("div[id^=vfMap]").each(function() {
	                let fid = $(this).attr("id").replace("vfMap", "");
	                new google.maps.ContextMenu(fid, contextMenuOptions, function () {
	                    // optional callback ...
	                });
	            });
	        }
	
	    });
		';
		$doc->addScriptDeclaration($script);
		$css= '
		.vfMap_context_menu {
	        font-size: 1rem;
	        background-color: lightgray;
	        padding: 12px;
	        line-height: 1.5rem;
	    }
		';
		$doc->addStyleDeclaration($css);
	}

	protected static function loadGoogleMapsApi() {
		if (!empty(static::$loaded[__METHOD__])) {
			return true;
		}
		$url = self::getApiUrl();
		if (empty($url)) {
			return true;
		}
		$doc = Factory::getApplication()->getDocument();
		$doc->addScript($url, array('relative' => false, 'detectBrowser' => false, 'detectDebug' => false), array('defer' =>true, 'async' => false));
		static::$loaded[__METHOD__] = true;
		return true;
	}

	public static function getMapOption($displayData) {
		$view  = $displayData['view'];
		$field = $displayData['field'];
		$data  = $displayData['data'];
		$zoom  = 8;
		$lat   = '';
		$lng   = '';
		if (isset($data['lat'])) {
			$lat = $data['lat'];
		}
		if (isset($data['lng'])) {
			$lng = $data['lng'];
		}
		if (($lat !== '' && $lng !== '')) {
			if ($view == 'list') {
				if (!empty($field->listMapZoom) && is_numeric($field->listMapZoom)) {
					$zoom = (int) $field->listMapZoom;
				}
			}
			if ($view == 'detail') {
				if (!empty($field->detailMapZoom) && is_numeric($field->detailMapZoom)) {
					$zoom = (int) $field->detailMapZoom;
				} else {
					$zoom = 13;
				}
			}
			// Map default values for script
			$mapId = '';
			if (!empty($displayData['makeUnique'])) {
				$mapId .= 'plugin';
			}
			$mapId        .= 'f' . $field->id . 'd' . $displayData['rowId'];
			$attPos       = '{lat: ' . $lat . ', lng: ' . $lng . '}';
			$fieldOptions = '{fieldId: "' . $mapId . '", attPos : ' . $attPos . ', zoom : ' . $zoom . '}';
			$mapOptions = new \stdClass();
			$mapOptions->mapId = $mapId;
			$mapOptions->mapOptions = $fieldOptions;
			return $mapOptions;
		}
	}

	protected static function getApiKey() {
		return ComponentHelper::getParams('com_visforms')->get('googleMapApiKey');
	}

	protected static function getApiUrl () {
		$apiKey = self::getApiKey();
		if (empty($apiKey)) {
			return '';
		}
		return  'https://maps.googleapis.com/maps/api/js?key='.$apiKey;
	}
}