<?php
/**
 * @package     VikBooking
 * @subpackage  com_vikbooking
 * @author      Alessio Gaggii - e4j - Extensionsforjoomla.com
 * @copyright   Copyright (C) 2018 e4j - Extensionsforjoomla.com. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 * @link        https://vikwp.com
 */

defined('ABSPATH') or die('No script kiddies please!');

/**
 * Helper class for the geocoding functions.
 * 
 * @since 	1.4.0
 */
class VikBookingHelperGeocoding
{
	/**
	 * The singleton instance of the class.
	 *
	 * @var VikBookingHelperGeocoding
	 */
	protected static $instance = null;

	/**
	 * The Google Maps API Key.
	 *
	 * @var string
	 */
	protected $gmaps_apikey = null;

	/**
	 * The current room geo-params.
	 *
	 * @var mixed
	 */
	protected $room_geoparams = null;

	/**
	 * The current room params.
	 *
	 * @var mixed
	 */
	protected $room_params = null;

	/**
	 * Class constructor is protected.
	 *
	 * @see 	getInstance()
	 */
	protected function __construct()
	{
		// load the Google Maps API Key from the configuration settings
		$this->setGoogleMapsKey(VikBooking::getGoogleMapsKey());
	}

	/**
	 * Returns the global object, either
	 * a new instance or the existing instance
	 * if the class was already instantiated.
	 *
	 * @return 	self 	A new instance of the class.
	 */
	public static function getInstance()
	{
		if (is_null(static::$instance)) {
			static::$instance = new static();
		}

		return static::$instance;
	}

	/**
	 * Sets the Google Maps API Key.
	 * 
	 * @param 	string 	the Google Maps API Key to set
	 * 
	 * @return 	self
	 */
	public function setGoogleMapsKey($apikey = null)
	{
		$this->gmaps_apikey = $apikey;

		return $this;
	}

	/**
	 * Gets the current Google Maps API Key.
	 * 
	 * @return 	mixed 	string or false if empty.
	 */
	public function getGoogleMapsKey()
	{
		if (empty($this->gmaps_apikey)) {
			return false;
		}
		
		return $this->gmaps_apikey;
	}

	/**
	 * Checks whether the Google Maps can be used.
	 * 
	 * @return 	bool 	true or false
	 */
	public function isSupported()
	{
		return (!empty($this->gmaps_apikey));
	}

	/**
	 * Loads the necessary assets to the document for Google Maps.
	 * 
	 * @param 	mixed 	string or array values values to append to the URL.
	 * 
	 * @return 	self
	 * 
	 * @since 	1.18.3 (J) - 1.8.3 (WP) loaded file gmaps-utils.js.
	 */
	public function loadAssets($params = null)
	{
		static $loaded = null;

		if (empty($this->gmaps_apikey) || $loaded) {
			return $this;
		}

		// turn flag on
		$loaded = true;

		// values like &callback=initMap could be passed as argument
		$qstring = '';

		if (!empty($params)) {
			if (is_string($params)) {
				$qstring = (substr($params, 0, 1) != '&' ? '&' : '') . $params;
			} elseif (!is_scalar($params)) {
				$qstring = '&' . http_build_query($params);
			}
		}

		$document = JFactory::getDocument();

		// load the Google Maps Utils file within VikBooking
		$document->addScript(VBO_SITE_URI . 'resources/gmaps-utils.js', ['version' => VIKBOOKING_SOFTWARE_VERSION]);

		// load Google Maps API JS
		$document->addScript('https://maps.googleapis.com/maps/api/js?key=' . $this->gmaps_apikey . $qstring);

		return $this;
	}

	/**
	 * Gets the room parameters (one or all) related to the geocoding.
	 * Class variables are using to cache parameters into the buffer.
	 * 
	 * @param 	mixed 	$rparams 	string to be decoded or array/object params.
	 * @param 	string 	$key 		the optional param key to get.
	 * @param 	mixed 	$def 		the default room param value to return.
	 * 
	 * @return 	mixed 	object with all params or empty object, requested param otherwise.
	 */
	public function getRoomGeoParams($rparams, $key = null, $def = null)
	{
		$geoparams = new stdClass;

		if (empty($rparams)) {
			return !empty($key) ? $def : $geoparams;
		}

		if ($this->room_geoparams === null || $this->room_params != $rparams || $key === null) {
			if (is_string($rparams)) {
				$rparams_obj = json_decode($rparams);
				if (!is_object($rparams_obj) || !isset($rparams_obj->geo)) {
					return !empty($key) ? $def : $geoparams;
				}
				$geoparams = $rparams_obj->geo;
			} elseif (is_array($rparams)) {
				// rooms parameters already decoded, convert it to object
				$geoparams = json_decode(json_encode($rparams));
			} elseif (is_object($rparams)) {
				// rooms parameters already decoded
				$geoparams = $rparams;
			}

			if (is_object($geoparams) && isset($geoparams->geo)) {
				$geoparams = $geoparams->geo;
			}

			// update global vars
			$this->room_params = $rparams;
			$this->room_geoparams = $geoparams;
		}

		if (($key == 'units_pos' || $key === null) && is_object($this->room_geoparams) && isset($this->room_geoparams->units_pos)) {
			$units_pos = $this->room_geoparams->units_pos;
			// make sure to adjust some properties of type float (number) so that they are not strings
			foreach ($units_pos as $k => $unit) {
				if (isset($unit->lat)) {
					$units_pos->{$k}->lat = (float)$unit->lat;
				}
				if (isset($unit->lng)) {
					$units_pos->{$k}->lng = (float)$unit->lng;
				}
				if (isset($unit->icon)) {
					foreach ($unit->icon as $icon_prop => $icon_val) {
						if ($icon_prop == 'fillOpacity') {
							$units_pos->{$k}->icon->{$icon_prop} = (float)$icon_val;
						}
						if ($icon_prop == 'scale') {
							$units_pos->{$k}->icon->{$icon_prop} = (float)$icon_val;
						}
						if ($icon_prop == 'strokeWeight') {
							$units_pos->{$k}->icon->{$icon_prop} = (float)$icon_val;
						}
						if ($icon_prop == 'anchor' && isset($icon_val->x) && isset($icon_val->y)) {
							// anchor point object is composed of coordinates with numbers
							$units_pos->{$k}->icon->{$icon_prop}->x = (float)$icon_val->x;
							$units_pos->{$k}->icon->{$icon_prop}->y = (float)$icon_val->y;
						}
						if (($icon_prop == 'size' || $icon_prop == 'scaledSize') && isset($icon_val->width) && isset($icon_val->height)) {
							// size and scaledSize object is composed of size width and height numbers
							$units_pos->{$k}->icon->{$icon_prop}->width = (int)$icon_val->width;
							$units_pos->{$k}->icon->{$icon_prop}->height = (int)$icon_val->height;
						}
					}
				}
			}
			$this->room_geoparams->units_pos = $units_pos;
		}

		if (!empty($key)) {
			return is_object($this->room_geoparams) && isset($this->room_geoparams->{$key}) ? $this->room_geoparams->{$key} : $def;
		}

		return $geoparams;
	}

	/**
	 * Gets the list of default marker symbols (SVG icons) for Google Maps.
	 * 
	 * @return 	array 			list of default marker symbols.
	 */
	public function getDefaultMarkerSymbols()
	{
		$symbols = array();
		$group = 'default';
		$color = '#000000';
		$opacity = 1;

		$symbol = new stdClass;
		$symbol->id = 'hotel';
		$symbol->name = 'Hotel';
		$symbol->group = $group;
		$symbol->fill = $color;
		$symbol->opacity = $opacity;
		$symbol->width = 48.4;
		$symbol->height = 43;
		$symbol->path = 'M47,5.4c0.7,0,1.3-0.6,1.3-1.3V1.3C48.4,0.6,47.8,0,47,0H1.3C0.6,0,0,0.6,0,1.3V4c0,0.7,0.6,1.3,1.3,1.3h1.3v32.2H1.3 C0.6,37.6,0,38.2,0,39v2.7C0,42.4,0.6,43,1.3,43h20.2v-6.7c0-0.7,0.6-1.3,1.3-1.3h2.7c0.7,0,1.3,0.6,1.3,1.3V43H47 c0.7,0,1.3-0.6,1.3-1.3V39c0-0.7-0.6-1.3-1.3-1.3h-1.3V5.4H47z M21.5,9.1c0-0.5,0.5-1.1,1.1-1.1h3.2c0.5,0,1.1,0.5,1.1,1.1v3.2 c0,0.5-0.5,1.1-1.1,1.1h-3.2c-0.5,0-1.1-0.5-1.1-1.1L21.5,9.1L21.5,9.1z M21.5,17.2c0-0.5,0.5-1.1,1.1-1.1h3.2 c0.5,0,1.1,0.5,1.1,1.1v3.2c0,0.5-0.5,1.1-1.1,1.1h-3.2c-0.5,0-1.1-0.5-1.1-1.1L21.5,17.2L21.5,17.2z M10.8,9.1 c0-0.5,0.5-1.1,1.1-1.1h3.2c0.5,0,1.1,0.5,1.1,1.1v3.2c0,0.5-0.5,1.1-1.1,1.1h-3.2c-0.5,0-1.1-0.5-1.1-1.1L10.8,9.1L10.8,9.1z  M15.1,21.5h-3.2c-0.5,0-1.1-0.5-1.1-1.1v-3.2c0-0.5,0.5-1.1,1.1-1.1H15c0.5,0,1.1,0.5,1.1,1.1v3.2C16.1,21,15.6,21.5,15.1,21.5 L15.1,21.5z M16.1,32.2c0-4.5,3.6-8.1,8.1-8.1s8.1,3.6,8.1,8.1H16.1z M37.6,20.4c0,0.5-0.5,1.1-1.1,1.1h-3.2c-0.5,0-1.1-0.5-1.1-1.1 v-3.2c0-0.5,0.5-1.1,1.1-1.1h3.2c0.5,0,1.1,0.5,1.1,1.1V20.4L37.6,20.4z M37.6,12.4c0,0.5-0.5,1.1-1.1,1.1h-3.2 c-0.5,0-1.1-0.5-1.1-1.1V9.1c0-0.5,0.5-1.1,1.1-1.1h3.2c0.5,0,1.1,0.5,1.1,1.1V12.4z';
		array_push($symbols, $symbol);

		$symbol = new stdClass;
		$symbol->id = 'home';
		$symbol->name = 'Home';
		$symbol->group = $group;
		$symbol->fill = $color;
		$symbol->opacity = $opacity;
		$symbol->width = 56.8;
		$symbol->height = 43;
		$symbol->path = 'M27.7,11.2L10,25.8v15.7c0,0.8,0.7,1.5,1.5,1.5l10.8,0c0.8,0,1.5-0.7,1.5-1.5v-9.2c0-0.8,0.7-1.5,1.5-1.5h6.1 c0.8,0,1.5,0.7,1.5,1.5v9.2c0,0.8,0.7,1.5,1.5,1.5c0,0,0,0,0,0l10.8,0c0.8,0,1.5-0.7,1.5-1.5V25.7L29.1,11.2 C28.7,10.8,28.1,10.8,27.7,11.2L27.7,11.2z M55.6,21.1l-8-6.6V1.2c0-0.6-0.5-1.2-1.2-1.2h-5.4c-0.6,0-1.2,0.5-1.2,1.2v7l-8.6-7.1 c-1.7-1.4-4.2-1.4-5.9,0l-24.3,20c-0.5,0.4-0.6,1.1-0.2,1.6c0,0,0,0,0,0l2.4,3c0.4,0.5,1.1,0.6,1.6,0.2c0,0,0,0,0,0L27.7,7.2 c0.4-0.3,1-0.3,1.5,0l22.6,18.6c0.5,0.4,1.2,0.3,1.6-0.2c0,0,0,0,0,0l2.4-3C56.2,22.2,56.1,21.5,55.6,21.1 C55.6,21.1,55.6,21.1,55.6,21.1L55.6,21.1z';
		array_push($symbols, $symbol);

		$symbol = new stdClass;
		$symbol->id = 'city';
		$symbol->name = 'City';
		$symbol->group = $group;
		$symbol->fill = $color;
		$symbol->opacity = $opacity;
		$symbol->width = 53.8;
		$symbol->height = 43;
		$symbol->path = 'M51.7,16.1H40.3V2c0-1.1-0.9-2-2-2H26.2c-1.1,0-2,0.9-2,2v6h-5.4V1.3c0-0.7-0.6-1.3-1.3-1.3h-1.3c-0.7,0-1.3,0.6-1.3,1.3 v6.7H9.4V1.3C9.4,0.6,8.8,0,8.1,0H6.7C6,0,5.4,0.6,5.4,1.3v6.7H2c-1.1,0-2,0.9-2,2v30.2C0,41.8,1.2,43,2.7,43h48.4 c1.5,0,2.7-1.2,2.7-2.7V18.1C53.8,17,52.8,16.1,51.7,16.1z M10.8,33.9c0,0.6-0.5,1-1,1H6.4c-0.6,0-1-0.5-1-1v-3.4c0-0.6,0.5-1,1-1 h3.4c0.6,0,1,0.5,1,1V33.9z M10.8,25.9c0,0.6-0.5,1-1,1H6.4c-0.6,0-1-0.5-1-1v-3.4c0-0.6,0.5-1,1-1h3.4c0.6,0,1,0.5,1,1V25.9z  M10.8,17.8c0,0.6-0.5,1-1,1H6.4c-0.6,0-1-0.5-1-1v-3.4c0-0.6,0.5-1,1-1h3.4c0.6,0,1,0.5,1,1V17.8z M21.5,33.9c0,0.6-0.5,1-1,1h-3.4 c-0.6,0-1-0.5-1-1v-3.4c0-0.6,0.5-1,1-1h3.4c0.6,0,1,0.5,1,1V33.9z M21.5,25.9c0,0.6-0.5,1-1,1h-3.4c-0.6,0-1-0.5-1-1v-3.4 c0-0.6,0.5-1,1-1h3.4c0.6,0,1,0.5,1,1V25.9z M21.5,17.8c0,0.6-0.5,1-1,1h-3.4c-0.6,0-1-0.5-1-1v-3.4c0-0.6,0.5-1,1-1h3.4 c0.6,0,1,0.5,1,1V17.8z M34.9,25.9c0,0.6-0.5,1-1,1h-3.4c-0.6,0-1-0.5-1-1v-3.4c0-0.6,0.5-1,1-1h3.4c0.6,0,1,0.5,1,1V25.9z  M34.9,17.8c0,0.6-0.5,1-1,1h-3.4c-0.6,0-1-0.5-1-1v-3.4c0-0.6,0.5-1,1-1h3.4c0.6,0,1,0.5,1,1V17.8z M34.9,9.7c0,0.6-0.5,1-1,1h-3.4 c-0.6,0-1-0.5-1-1V6.4c0-0.6,0.5-1,1-1h3.4c0.6,0,1,0.5,1,1V9.7z M48.4,33.9c0,0.6-0.5,1-1,1H44c-0.6,0-1-0.5-1-1v-3.4 c0-0.6,0.5-1,1-1h3.4c0.6,0,1,0.5,1,1V33.9z M48.4,25.9c0,0.6-0.5,1-1,1H44c-0.6,0-1-0.5-1-1v-3.4c0-0.6,0.5-1,1-1h3.4 c0.6,0,1,0.5,1,1V25.9z';
		array_push($symbols, $symbol);

		$symbol = new stdClass;
		$symbol->id = 'campground';
		$symbol->name = 'Campground';
		$symbol->group = $group;
		$symbol->fill = $color;
		$symbol->opacity = $opacity;
		$symbol->width = 53.7;
		$symbol->height = 43;
		$symbol->path = 'M52.4,37.6h-2.1L30.2,9.9l4.5-6.2c0.4-0.6,0.3-1.4-0.3-1.9l-2.2-1.6c-0.6-0.4-1.4-0.3-1.9,0.3l-3.5,4.8l-3.5-4.8 C23,0,22.1-0.2,21.5,0.3l-2.2,1.6c-0.6,0.4-0.7,1.3-0.3,1.9l4.5,6.2L3.4,37.6H1.3C0.6,37.6,0,38.2,0,39v2.7C0,42.4,0.6,43,1.3,43 h51.1c0.7,0,1.3-0.6,1.3-1.3V39C53.7,38.2,53.1,37.6,52.4,37.6z M26.9,24.2l9.8,13.4H17.1L26.9,24.2z';
		array_push($symbols, $symbol);

		$symbol = new stdClass;
		$symbol->id = 'trailer';
		$symbol->name = 'Trailer';
		$symbol->group = $group;
		$symbol->fill = $color;
		$symbol->opacity = $opacity;
		$symbol->width = 66.2;
		$symbol->height = 43;
		$symbol->path = 'M64.5,26.5h-8.3V1.7c0-0.9-0.7-1.7-1.7-1.7H1.7C0.7,0,0,0.7,0,1.7v29.8c0,0.9,0.7,1.7,1.7,1.7h5.1c0.8-5.6,5.6-9.9,11.4-9.9 s10.6,4.3,11.4,9.9h34.9c0.9,0,1.7-0.7,1.7-1.7v-3.3C66.2,27.2,65.4,26.5,64.5,26.5z M9.9,18.6c-1.2,0.6-2.3,1.3-3.3,2.1V7.4 C6.6,7,7,6.6,7.4,6.6h1.7c0.5,0,0.8,0.4,0.8,0.8V18.6z M19.8,16.7c-0.5-0.1-1.1-0.1-1.7-0.1c-0.6,0-1.1,0.1-1.7,0.1V7.4 c0-0.5,0.4-0.8,0.8-0.8H19c0.5,0,0.8,0.4,0.8,0.8V16.7z M29.8,20.7c-1-0.8-2.1-1.5-3.3-2.1V7.4c0-0.5,0.4-0.8,0.8-0.8h1.7 c0.5,0,0.8,0.4,0.8,0.8V20.7z M39.7,26.5h-3.3v-19c0-0.5,0.4-0.8,0.8-0.8h1.7c0.5,0,0.8,0.4,0.8,0.8V26.5z M49.6,26.5h-3.3v-19 c0-0.5,0.4-0.8,0.8-0.8h1.7c0.5,0,0.8,0.4,0.8,0.8V26.5z M18.2,26.5c-4.6,0-8.3,3.7-8.3,8.3s3.7,8.3,8.3,8.3s8.3-3.7,8.3-8.3 S22.8,26.5,18.2,26.5z M18.2,38c-1.8,0-3.3-1.5-3.3-3.3s1.5-3.3,3.3-3.3s3.3,1.5,3.3,3.3S20,38,18.2,38z';
		array_push($symbols, $symbol);

		return $symbols;
	}

	/**
	 * Gets the list of marker symbols for Google Maps.
	 * 
	 * @param 	string 	$group 	optionally filter the symbols by group
	 * 
	 * @return 	array 			list of marker symbols.
	 */
	public function getMarkerSymbols($group = null)
	{
		$dbo = JFactory::getDbo();
		$symbols = array();
		
		$q = "SELECT `param`, `setting` FROM `#__vikbooking_config` WHERE `param` LIKE 'marker_symbols_%' ORDER BY `param` ASC;";
		$dbo->setQuery($q);
		$dbo->execute();
		if ($dbo->getNumRows()) {
			$all_symbols = $dbo->loadAssocList();
			foreach ($all_symbols as $symbols_data) {
				$list = json_decode($symbols_data['setting']);
				if (!is_array($list) || !count($list)) {
					continue;
				}
				$symbols = array_merge($symbols, $list);
			}
		} else {
			$default_symbols = $this->getDefaultMarkerSymbols();
			$q = "INSERT INTO `#__vikbooking_config` (`param`,`setting`) VALUES ('marker_symbols_default', " . $dbo->quote(json_encode($default_symbols)) . ");";
			$dbo->setQuery($q);
			$dbo->execute();

			return $default_symbols;
		}

		$symbols = count($symbols) ? $symbols : $this->getDefaultMarkerSymbols();

		if (!empty($group)) {
			// filter symbol objects by group
			$group = strtolower($group);
			foreach ($symbols as $k => $symbol) {
				if (!isset($symbol->group)) {
					// filtering is impossible
					continue;
				}
				if ($symbol->group != $group) {
					unset($symbols[$k]);
				}
			}
			// reset keys
			$symbols = array_values($symbols);
		}

		return $symbols;
	}

	/**
	 * Gets one marker symbol for Google Maps.
	 * 
	 * @param 	string 	$id 	the symbol identifier
	 * @param 	string 	$group 	optionally filter the symbols by group
	 * 
	 * @return 	mixed 			marker symbol object or null.
	 */
	public function getMarkerSymbol($id, $group = null)
	{
		if (empty($id)) {
			return null;
		}

		$symbols = $this->getMarkerSymbols($group);
		foreach ($symbols as $symbol) {
			if (isset($symbol->id) && $symbol->id == $id) {
				// symbol found
				return $symbol;
			}
		}

		// symbol not found
		return null;
	}

	/**
	 * AJAX method called by the main controller when adding
	 * or updating a new or existing SVG symbol icon.
	 * 
	 * @return 	mixed
	 * 
	 * @throws 	Exception
	 */
	public function storeSvgSymbol()
	{
		$symbol = VikRequest::getVar('symbol', array(), 'request', 'array');
		if (!is_array($symbol) || empty($symbol['id'])) {
			throw new Exception("Symbol ID not found", 404);
		}

		if (empty($symbol['name'])) {
			throw new Exception("Symbol name is empty", 500);
		}

		if (empty($symbol['path'])) {
			throw new Exception("Symbol path is empty", 500);
		}

		// sanitize values
		$symbol['group'] = !empty($symbol['group']) ? strtolower($symbol['group']) : 'default';
		if (isset($symbol['opacity'])) {
			// this must be a float, not a string
			$symbol['opacity'] = floatval($symbol['opacity']);
		}

		$current_symbol = $this->getMarkerSymbol($symbol['id']);
		$addnew = false;
		if (is_object($current_symbol)) {
			// force symbol group because ID exists
			$symbol['group'] = $current_symbol->group;
			// update existing marker symbol
			foreach ($current_symbol as $prop => $val) {
				if (isset($symbol[$prop])) {
					// overwrite existing property taken from the new icon
					$current_symbol->{$prop} = $symbol[$prop];
				}
			}
		} else {
			// create new marker symbol
			$addnew = true;
			$current_symbol = (object)$symbol;
		}

		$dbo = JFactory::getDbo();

		$group_symbols = array();
		$update = false;
		$q = "SELECT `setting` FROM `#__vikbooking_config` WHERE `param`=" . $dbo->quote('marker_symbols_' . $symbol['group']) . ";";
		$dbo->setQuery($q);
		$dbo->execute();
		if ($dbo->getNumRows()) {
			$update = true;
			$group_symbols = json_decode($dbo->loadResult());
			$group_symbols = !is_array($group_symbols) ? array() : $group_symbols;
		}

		if ($addnew) {
			// push new symbol
			array_push($group_symbols, $current_symbol);
		} else {
			// find current symbol
			$found = false;
			foreach ($group_symbols as $k => $s) {
				if ($s->id == $symbol['id']) {
					// replace existing symbol
					$found = true;
					$group_symbols[$k] = $current_symbol;
					break;
				}
			}
			if (!$found) {
				// push new symbol as it was not found
				array_push($group_symbols, $current_symbol);
			}
		}

		// update values in db
		if ($update) {
			$q = "UPDATE `#__vikbooking_config` SET `setting`=" . $dbo->quote(json_encode($group_symbols)) . " WHERE `param`=" . $dbo->quote('marker_symbols_' . $symbol['group']) . ";";
		} else {
			$q = "INSERT INTO `#__vikbooking_config` (`param`,`setting`) VALUES (" . $dbo->quote('marker_symbols_' . $symbol['group']) . ", " . $dbo->quote(json_encode($group_symbols)) . ");";
		}
		$dbo->setQuery($q);
		$dbo->execute();

		// return the new symbol object just stored
		return $current_symbol;
	}

	/**
	 * AJAX method called by the main controller when needing
	 * to delete an existing SVG symbol icon.
	 * 
	 * @return 	mixed
	 * 
	 * @throws 	Exception
	 */
	public function deleteSvgSymbol()
	{
		$symbol_id = VikRequest::getString('symbol_id', '', 'request');
		if (empty($symbol_id)) {
			throw new Exception("Symbol ID is empty", 404);
		}

		$current_symbol = $this->getMarkerSymbol($symbol_id);
		if (!is_object($current_symbol)) {
			throw new Exception("Symbol ID not found", 404);
		}

		$dbo = JFactory::getDbo();
		$q = "SELECT `setting` FROM `#__vikbooking_config` WHERE `param`=" . $dbo->quote('marker_symbols_' . $current_symbol->group) . ";";
		$dbo->setQuery($q);
		$dbo->execute();
		if (!$dbo->getNumRows()) {
			throw new Exception("Symbol Group not found", 404);
		}
		$records = json_decode($dbo->loadResult());
		if (!is_array($records) || !count($records)) {
			throw new Exception("Symbol Group has got no symbols", 404);
		}

		$found = false;
		foreach ($records as $k => $v) {
			if ($v->id == $symbol_id) {
				$found = true;
				unset($records[$k]);
				break;
			}
		}

		if (!$found) {
			throw new Exception("Symbol ID not found in all symbols of this group", 404);
		}

		// reset keys
		$records = array_values($records);

		// update db
		$q = "UPDATE `#__vikbooking_config` SET `setting`=" . $dbo->quote(json_encode($records)) . " WHERE `param`=" . $dbo->quote('marker_symbols_' . $current_symbol->group) . ";";
		$dbo->setQuery($q);
		$dbo->execute();

		return $symbol_id;
	}

	/**
	 * AJAX method called by the main controller when starting to work
	 * on the geo coding information for one specific room.
	 * 
	 * @return 	int 		a simple result integer code identifier.
	 * 
	 * @throws 	Exception
	 */
	public function initRoomGeoTransient()
	{
		$dbo = JFactory::getDbo();
		$room_id = VikRequest::getInt('room_id', 0, 'request');

		$param = "geocoding_transient_room_" . $room_id;
		$q = "SELECT `setting` FROM `#__vikbooking_config` WHERE `param`=" . $dbo->quote($param) . ";";
		$dbo->setQuery($q);
		$dbo->execute();
		if ($dbo->getNumRows()) {
			// make the temporary record empty
			$q = "UPDATE `#__vikbooking_config` SET `setting`='' WHERE `param`=" . $dbo->quote($param) . ";";
			$dbo->setQuery($q);
			$dbo->execute();

			return 2;
		}

		return 1;
	}

	/**
	 * AJAX method called by the main controller when updating
	 * the geo coding information for one specific room.
	 * 
	 * @return 	object 		the geo params stored.
	 * 
	 * @throws 	Exception
	 */
	public function updateRoomGeoTransient()
	{
		$dbo = JFactory::getDbo();

		$room_id = VikRequest::getInt('room_id', 0, 'request');
		$geo_enabled = VikRequest::getInt('geo_enabled', 0, 'request');
		$geo_address = VikRequest::getString('geo_address', '', 'request');
		$geo_latitude = VikRequest::getFloat('geo_latitude', 0, 'request');
		$geo_longitude = VikRequest::getFloat('geo_longitude', 0, 'request');
		$geo_zoom = VikRequest::getInt('geo_zoom', 1, 'request');
		$geo_mtype = VikRequest::getString('geo_mtype', '', 'request');
		$geo_height = VikRequest::getInt('geo_height', 100, 'request');
		$geo_markers_multi = VikRequest::getInt('geo_markers_multi', 0, 'request');
		$geo_marker_lat = VikRequest::getFloat('geo_marker_lat', 0, 'request');
		$geo_marker_lng = VikRequest::getFloat('geo_marker_lng', 0, 'request');
		$geo_marker_hide = VikRequest::getInt('geo_marker_hide', 0, 'request');
		$geo_units_pos = VikRequest::getVar('geo_units_pos', array(), 'request', 'array');
		$geo_goverlay = VikRequest::getInt('geo_goverlay', 0, 'request');
		$geo_overlay_img = VikRequest::getString('geo_overlay_img', '', 'request');
		$geo_overlay_south = VikRequest::getFloat('geo_overlay_south', 0, 'request');
		$geo_overlay_west = VikRequest::getFloat('geo_overlay_west', 0, 'request');
		$geo_overlay_north = VikRequest::getFloat('geo_overlay_north', 0, 'request');
		$geo_overlay_east = VikRequest::getFloat('geo_overlay_east', 0, 'request');

		// prepare geo params object
		$geo_params = new stdClass;
		/**
		 * Property "enabled" must ALWAYS be the first one
		 * 
		 * @see 	getImportableConfigRooms()
		 */
		$geo_params->enabled = $geo_enabled;
		//
		$geo_params->address = $geo_address;
		$geo_params->latitude = $geo_latitude;
		$geo_params->longitude = $geo_longitude;
		$geo_params->zoom = $geo_zoom;
		$geo_params->mtype = $geo_mtype;
		$geo_params->height = $geo_height;
		$geo_params->markers_multi = $geo_markers_multi;
		$geo_params->marker_lat = $geo_marker_lat;
		$geo_params->marker_lng = $geo_marker_lng;
		$geo_params->marker_hide = $geo_marker_hide;
		$geo_params->units_pos = $geo_units_pos;
		$geo_params->goverlay = $geo_goverlay;
		$geo_params->overlay_img = $geo_overlay_img;
		$geo_params->overlay_south = $geo_overlay_south;
		$geo_params->overlay_west = $geo_overlay_west;
		$geo_params->overlay_north = $geo_overlay_north;
		$geo_params->overlay_east = $geo_overlay_east;

		// check transient record
		$param = "geocoding_transient_room_" . $room_id;
		$q = "SELECT `setting` FROM `#__vikbooking_config` WHERE `param`=" . $dbo->quote($param) . ";";
		$dbo->setQuery($q);
		$dbo->execute();
		if ($dbo->getNumRows()) {
			$q = "UPDATE `#__vikbooking_config` SET `setting`=" . $dbo->quote(json_encode($geo_params)) . " WHERE `param`=" . $dbo->quote($param) . ";";
		} else {
			$q = "INSERT INTO `#__vikbooking_config` (`param`,`setting`) VALUES (" . $dbo->quote($param) . ", " . $dbo->quote(json_encode($geo_params)) . ");";
		}
		// update transient record with new geo params
		$dbo->setQuery($q);
		$dbo->execute();

		return $geo_params;
	}

	/**
	 * Retrieves the last transient for the given room ID (or new room). This
	 * way the room geo params are ready to be stored by the main controller.
	 * 
	 * @param 	int 	$room_id 	the room ID (0 for new).
	 * 
	 * @return 	mixed 				geo params object or false.
	 */
	public function getRoomGeoTransient($room_id)
	{
		$dbo = JFactory::getDbo();
		$param = "geocoding_transient_room_" . (int)$room_id;

		$q = "SELECT `setting` FROM `#__vikbooking_config` WHERE `param`=" . $dbo->quote($param) . ";";
		$dbo->setQuery($q);
		$dbo->execute();
		if (!$dbo->getNumRows()) {
			$geo_enabled = VikRequest::getInt('geo_enabled', 0, 'request');
			if ($geo_enabled) {
				VikError::raiseWarning('', 'no records found ' . $param);
			}
			return false;
		}
		$geo_params = json_decode($dbo->loadResult());

		return is_object($geo_params) ? $geo_params : false;
	}

	/**
	 * Returns a list of room-types with a complete geocoding information that could be imported.
	 * This facilitates the setup of the geographic information for a new room-type.
	 * 
	 * @param 	int 	$exclude_id 	the current room to exclude, 0 for new.
	 * 
	 * @return 	array 	list of room-types with an importable geo configuration.
	 */
	public function getImportableConfigRooms($exclude_id = 0)
	{
		$importable = array();

		$dbo = JFactory::getDbo();
		$clauses = array();

		if ($exclude_id > 0) {
			array_push($clauses, '`id` != ' . $exclude_id);
		}

		// fetch all rooms with the geo information enabled
		array_push($clauses, '`params` LIKE ' . $dbo->quote('%"geo":{"enabled":1%'));

		$q = "SELECT `id`, `name` FROM `#__vikbooking_rooms` WHERE " . implode(' AND ', $clauses) . " ORDER BY `name` ASC;";
		$dbo->setQuery($q);
		$dbo->execute();
		if ($dbo->getNumRows()) {
			$importable = $dbo->loadAssocList();
		}

		return $importable;
	}

	/**
	 * AJAX method called by the main controller when requesting
	 * to import the geo coding information for one specific room.
	 * 
	 * @return 	object 		the geo params of the room requested.
	 * 
	 * @throws 	Exception
	 */
	public function importRoomGeoConfig()
	{
		$dbo = JFactory::getDbo();

		$room_id = VikRequest::getInt('room_id', 0, 'request');
		
		$q = "SELECT `id`, `name`, `params` FROM `#__vikbooking_rooms` WHERE id={$room_id};";
		$dbo->setQuery($q);
		$dbo->execute();
		if (!$dbo->getNumRows()) {
			throw new Exception("Room not found for importing geo config", 404);
		}
		$room = $dbo->loadAssoc();
		$rparams = json_decode($room['params']);
		if (!is_object($rparams)) {
			throw new Exception("Unable to decode room params", 500);
		}

		// get all geo params
		$geo_params = $this->getRoomGeoParams($rparams);

		if (!is_object($geo_params)) {
			throw new Exception("Unable to get room geo params", 500);
		}

		return $geo_params;
	}
}
