//jQuery plugins
import autosize from 'autosize';
import mousewheel from 'jquery-mousewheel';
import waitforimages from 'jquery.waitforimages';

//Simplebar
import SimpleBar from 'simplebar';
window.SimpleBar = SimpleBar;
import ResizeObserver from 'resize-observer-polyfill';
window.ResizeObserver = ResizeObserver;

//Leaflet
import 'leaflet';
import 'leaflet-geometryutil';
import 'leaflet.heightgraph';
import './leaflet.helpers';

export default class Project {

	constructor(oSpot) {
		this.spot = oSpot;
	}

	pageInit(asHash) {
		this.spot.tmp('first_exec', true);
		this.initPage(asHash);
	}

	onResize() {
		this.spot.tmp('map_offset', -1 * (this.isFeedPanelOpen()?this.spot.tmp('$Feed').outerWidth(true):0) / $('body').outerWidth(true));

		if(typeof this.spot.tmp('elev') != 'undefined' && this.spot.tmp('elev')._showState) {
			this.spot.tmp('elev').resize({width:this.getElevWidth()});
		}
	}

	onSamePageMove(asHash) {
		if(this.spot.tmp('first_exec')) this.initPage(asHash);
		else if(asHash.items[0] != this.spot.vars(['project', 'codename'])) {
			this.spot.tmp('map').remove();
			this.spot.tmp('$Map').empty();
			this.spot.tmp('map', null);
			this.spot.tmp('$PostList').empty();
			this.setFeedUpdateTimer(-1);
			this.initProject(asHash.items[0]);
		}
		return false;
	}

	onQuitPage() {
		this.setFeedUpdateTimer(-1);
		return true;
	}

	onKeydown(oEvent) {
		switch(oEvent.which) {
			case 27:
				if(!this.isLightboxOpen()) {
					$bFeedPanelOpen = this.isFeedPanelOpen();
					$bSettingsPanelOpen = this.isSettingsPanelOpen();
					$bAnimation = ($bFeedPanelOpen && $bSettingsPanelOpen)?'none':null;
					if($bFeedPanelOpen) this.toggleFeedPanel(false, $bAnimation);
					if($bSettingsPanelOpen)	this.toggleSettingsPanel(false, $bAnimation);
				}
				break;
		}
	}

	initPage(asHash) {
		if(asHash.items.length==0) this.spot.setHash(asHash.page, [this.spot.vars('default_project_codename')]);
		else {
			this.spot.tmp('$Projects', $('#projects'));
			this.spot.tmp('$Map', $('#map'));
			this.spot.tmp('$Poster', $('#poster'));
			this.spot.tmp('$PostList', $('#posts_list'));
			this.spot.tmp('$Feed', $('#feed'));
			this.spot.tmp('$Settings', $('#settings'));
			this.spot.tmp('$Title', $('#title'));
			this.spot.tmp('updatable', true);
			this.spot.tmp('out-of-data', false);
			this.spot.tmp('markers', 'object');
			this.spot.tmp('trail-markers', 'object');
			this.spot.tmp('marker_size', {width: 32, height: 32});

			if(!this.isMobile()) this.toggleFeedPanel(true, 'none');

			//Lightbox options
			lightbox.option({
				alwaysShowNavOnTouchDevices: true,
				albumLabel: '<i class="fa fa-fw fa-lg fa-media push"></i> %1 / %2',
				fadeDuration: 300,
				imageFadeDuration: 400,
				positionFromTop: 0,
				resizeDuration: 400,
				hasVideo: true,
				onMediaChange: (oMedia) => {
					this.spot.updateHash('media', oMedia.id);
					if(oMedia.set == 'post-medias') this.goToPost({type: 'media', id: oMedia.id});
				},
				onClosing: () => {this.spot.flushHash();}
			});

			//Assign Track Type Colors
			this.spot.tmp('track-type-styles', 'object');
			$('#legend').find('.line').each((iKey, oLegend) => {
				var $Legend = $(oLegend);
				var sTrackType = $Legend.attr('class').replace('line', '').trim();
				this.spot.tmp(['track-type-styles', sTrackType], {weight: parseInt($Legend.css('height')), color: $Legend.css('background-color'), opacity: 1});
			});

			//Post Panel one-off init (see initPosts for project related init)
			//Scrollbar
			this.spot.tmp('simple-bar', new SimpleBar($('#feed-panel')[0]));
			this.spot.tmp('simple-bar').getScrollElement().addEventListener('scroll', (oEvent) => {this.onFeedScroll(oEvent);});

			//Add "Loading" Post
			this.getPost({type: 'loading', headerless: true, formatted_time: '', relative_time: ''}).appendTo($('#loading'));

			//Mobile Touchscreen Events
			this.spot.tmp('$Feed').onSwipe(
				(oPos) => {
					this.$Panels = $('.leaflet-right')
						.add(this.spot.tmp('$Feed'))
						.each((iIndex, oElem) => {
							let $Elem = $(oElem);
							$Elem.data('initial-offset', parseInt($Elem.css('right')));
						});
				},
				(oPos) => {
					this.$Panels.each((iIndex, oElem) => {
						let $Elem = $(oElem);
						$Elem.css({'right': ($Elem.data('initial-offset') + Math.min(oPos.xStart - oPos.xMove, 0))+'px', 'transition': 'none'});
					});
				},
				(oPos) => {
					if(oPos.xEnd - oPos.xStart > 100 && Math.abs(oPos.yEnd - oPos.yStart) < 100) this.toggleFeedPanel(false);
					this.$Panels.css({'right': '', 'transition': ''});
				}
			);
			this.spot.tmp('$Settings').onSwipe(
				(oPos) => {
					this.$Panels = $('.leaflet-left')
						.add(this.spot.tmp('$Settings'))
						.each((iIndex, oElem) => {
							let $Elem = $(oElem);
							$Elem.data('initial-offset', parseInt($Elem.css('left')));
						});
				},
				(oPos) => {
					this.$Panels.each((iIndex, oElem) => {
						let $Elem = $(oElem);
						$Elem.css({'left': ($Elem.data('initial-offset') + Math.min(oPos.xMove - oPos.xStart, 0))+'px', 'transition': 'none'});
					});
				},
				(oPos) => {
					if(oPos.xEnd - oPos.xStart < -100 && Math.abs(oPos.yEnd - oPos.yStart) < 100) this.toggleSettingsPanel(false);
					this.$Panels.css({'left': '', 'transition': ''});
				}
			);

			//Feed Panel
			this.initPosts();

			//Settings Panel
			this.initSettings();

			//project Bootstrap
			var oFocusPost = asHash.items[1]?{type: asHash.items[1], id: asHash.items[2]}:null;
			this.initProject(asHash.items[0], oFocusPost);
		}
	}

	toggleFeedPanel(bShow, sMapAction) {
		let bOldValue = this.isFeedPanelOpen();
		this.spot.tmp('$Projects').toggleClass('with-feed', (typeof bShow === 'undefined')?null:bShow);
		let bNewValue = this.isFeedPanelOpen();

		if(bOldValue != bNewValue) {
			this.spot.onResize();
			sMapAction = sMapAction || 'panTo';
			switch(sMapAction) {
				case 'none': break;
				case 'panTo': this.spot.tmp('map').panBy([(this.isFeedPanelOpen()?1:-1)*this.spot.tmp('$Feed').outerWidth(true)/2, 0], {duration: 0.5}); break;
				case 'panToInstant': this.spot.tmp('map').panBy([(this.isFeedPanelOpen()?1:-1)*this.spot.tmp('$Feed').outerWidth(true)/2, 0]); break;
				case 'fitBounds':  this.spot.tmp('map').fitBounds(this.spot.tmp('track').getBounds(), {paddingTopLeft: L.point(5, this.spot.tmp('marker_size').height + 5), paddingBottomRight: L.point(this.spot.tmp('$Feed').outerWidth(true) + 5, 5)}); break;
			}
		}
	}

	isFeedPanelOpen() {
		return this.spot.tmp('$Projects').hasClass('with-feed');
	}

	toggleSettingsPanel(bShow, sMapAction) {
		let bOldValue = this.isSettingsPanelOpen();
		this.spot.tmp('$Projects').toggleClass('with-settings', (typeof bShow === 'undefined')?null:bShow);
		let bNewValue = this.isSettingsPanelOpen();
		
		if(bOldValue != bNewValue) {
			this.spot.onResize();
			sMapAction = sMapAction || 'panTo';
			switch(sMapAction) {
				case 'none': break;
				case 'panTo': this.spot.tmp('map').panBy([(this.isSettingsPanelOpen()?-1:1)*this.spot.tmp('$Settings').outerWidth(true)/2, 0], {duration: 0.5}); break;
				case 'panToInstant': this.spot.tmp('map').panBy([(this.isSettingsPanelOpen()?-1:1)*this.spot.tmp('$Settings').outerWidth(true)/2, 0]); break;
			}
		}
	}

	isSettingsPanelOpen() {
		return this.spot.tmp('$Projects').hasClass('with-settings');
	}

	updateSettingsPanel(asLastUpdate) {
		var $LastUpdate = this.spot.tmp('$Settings').find('#last_update').toggle(this.spot.vars(['project', 'mode']) == this.spot.consts.modes.blog && asLastUpdate.unix_time > 0);
		$LastUpdate.find('abbr')
			.attr('title', asLastUpdate.formatted_time)
			.text(this.spot.lang('last_update')+' '+asLastUpdate.relative_time);
	}

	isLightboxOpen() {
		return $('#lightbox').is(':visible');
	}

	isMobile() {
		return $('#mobile').is(':visible');
	}


	initProject(sProjectCodeName, oFocusPost){
		this.spot.tmp('first_exec', false);
		this.spot.vars('project', this.spot.vars(['projects', sProjectCodeName]));

		//Page Title
		this.spot.setPageTitle(this.spot.vars(['project', 'name']));
		this.spot.tmp('$Title').find('#project_name').text(this.spot.vars(['project', 'name']));

		//Load Track & Markers
		$.when(
			//Markers: Spot Messages & Medias
			this.spot.get(
				'markers',
				function(){},
				{id_project: this.spot.vars(['project', 'id'])},
				function(e){console.log(e);}
			),
			//Project Geojson: Hike track
			$.ajax({
				dataType: 'json',
				url: this.spot.vars(['project', 'geofilepath']),
				mimeType: 'application/json'
			})
		).done((aoMessages, aoTracks) => {
			var asData = aoMessages[0]['data'];
			this.setMapLayers(asData['maps']);
			this.initSpotMessages(asData['messages'], aoTracks[0], asData['medias']);
			this.updateSettingsPanel(asData['last_update']);
		});

		//Show/Hide Poster Panel
		var bHistoMode = (this.spot.vars(['project', 'mode']) == this.spot.consts.modes.histo);
		this.spot.tmp('$Poster').toggleClass('histo-mode', bHistoMode);

		//Feed auto-update
		this.updateFeed(true, false, ()=>{this.focusOnPost(oFocusPost);});
		this.spot.tmp('simple-bar').getScrollElement().scrollTop = 0;
		if(!bHistoMode) this.onAutoUpdate(true);
	}

	initPosts() {
		//Add post form
		this.spot.tmp('$Poster').append(this.getPost({type: 'poster', formatted_time: '', relative_time: this.spot.lang('post_new_message')}));

		//Add archived Project Notice
		this.spot.tmp('$Poster').append(this.getPost({type: 'archived', headerless: true}));

		//Auto-adjust text area height
		autosize($('#post'));

		//Add post Event handling
		$('#submit').on('click', () => {
			if(this.spot.tmp('$Poster').checkForm()) {
				this.spot.get(
					'add_post',
					() => {
						$('#post').val('');
						this.onAutoUpdate();
					},
					{
						id_project: this.spot.vars(['project', 'id']),
						name: $('#name').val(),
						content: $('#post').val()
					}
				);
			}
		});
	}

	initSettings(){
		//Scrollbar
		new SimpleBar($('#settings-sections-scrollbox')[0]);

		//Feedback display function
		let settingsFeedback = (sType, sMsg) => {
			$('<p>', {'class': sType})
				.append($('<i>', {'class':'fa push fa-'+sType}))
				.append(sMsg)
				.appendTo($('#settings-feedback'))
				.slideDown('fast')
				.delay(5000)
				.slideUp('fast', function() {$(this).remove();});
		};

		//Newsletter Subscription
		$('#nl_btn').on('click', (oEvent) => {
			var sAction = $(oEvent.currentTarget).prop('name');
			var sEmail = $('#email').val();
			var regexEmail = /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
			if(!regexEmail.test(sEmail)) settingsFeedback('error', this.spot.lang('nl_invalid_email'));
			else {
				this.spot.get(
					sAction,
					(asData, sDesc) => {
						settingsFeedback('success', sDesc);
						this.spot.vars('user', asData);
						this.setUserInterface();
					},
					{'email': sEmail, 'name': $('#name').val()},
					(sDesc) => {settingsFeedback('error', sDesc);},
					(sState) => {
						var bLoading = (sState=='start');
						$('#nl_btn')
							.prop('disabled', bLoading)
							.toggleClass('loading', bLoading);
					}
				);
			}
		});

		//Admin Access
		$('#admin_link').toggle(this.spot.checkClearance(this.spot.consts.clearances.admin));

		//Twink interface with user data
		this.setUserInterface();
	}

	setUserInterface() {
		var asUserInfo = this.spot.vars('user');
		if(asUserInfo.id_user) { //Subscribed User
			$('#email').val(asUserInfo.email).prop('disabled', true);
			$('#nl_btn').attr({name: 'unsubscribe', title: this.spot.lang('nl_unsubscribe'), 'class':'unsubscribe'});
			$('#nl_desc').text(this.spot.lang('nl_subscribed_desc'));

			//Populate nickname
			if(asUserInfo.name) $('#name').val(asUserInfo.name);
		}
		else { //Unsubscribed User
			$('#email').val('').prop('disabled', false);
			$('#nl_btn').attr({name: 'subscribe', title: this.spot.lang('nl_subscribe'), 'class':'subscribe'});
			$('#nl_desc').text(this.spot.lang('nl_unsubscribed_desc'));
		}
	}

	getElevWidth() {
		var
			iPageWidth = this.spot.tmp('$Projects').width(),
			iFeedPanelWidth = this.isFeedPanelOpen()?this.spot.tmp('$Feed').outerWidth(true):0,
			iSettingsPanelWidth = this.isSettingsPanelOpen()?this.spot.tmp('$Settings').outerWidth(true):0,
			iLegendWidth = $('.leaflet-bottom.leaflet-left > .leaflet-control-layers').outerWidth(true),
			oElevRightMarging = parseInt($('.leaflet-bottom.leaflet-right > .leaflet-control-scale').css('margin-right').slice(0, -2));

		return iPageWidth - iFeedPanelWidth - iSettingsPanelWidth - iLegendWidth - oElevRightMarging;
	}

	setMapLayers(asLayers) {
		this.spot.tmp('layers', {});
		$.each(asLayers, (iKey, asLayer) => {
			var sMapName = this.spot.lang('map_'+asLayer.codename);
			var asMapOptions = {
				tileSize: parseInt(asLayer.tile_size),
				zoomOffset: (parseInt(asLayer.tile_size) / -256 + 1),
				minZoom: parseInt(asLayer.min_zoom),
				maxZoom: parseInt(asLayer.max_zoom),
				attribution: asLayer.attribution
			}
			if(asLayer.token != '') asMapOptions.token = asLayer.token;

			this.spot.tmp('layers')[sMapName] = L.tileLayer(asLayer.pattern, asMapOptions);
		});
	}

	initSpotMessages(aoMessages, aoTracks, aoMedias) {
		var bIsMobile = this.isMobile();

		//Map
		var oMap = L.map(this.spot.tmp('$Map')[0], {
			layers: [this.spot.tmp('layers')[this.spot.lang('map_satellite')]],
			attributionControl: false,
			zoomControl: false
		});
		this.spot.tmp('map', oMap);

		//Controls: Settings Panel
		var oSettingsPanel = L.control({position: bIsMobile?'bottomleft':'topleft'});
		var $SettingsButton = $('#settings-button').clone();
		$SettingsButton.on('click', () => {this.toggleSettingsPanel();});
		oSettingsPanel.onAdd = function(oMap) {return $SettingsButton[0];};

		//Project Title
		var oTitle = L.control({position: bIsMobile?'bottomleft':'topleft'});
		var $Title = $('#title').clone();
		oTitle.onAdd = function(oMap) {return $Title[0];};
		
		if(bIsMobile) {
			oTitle.addTo(oMap);
			oSettingsPanel.addTo(oMap);
		}
		else {
			oSettingsPanel.addTo(oMap);
			oTitle.addTo(oMap);
		}

		//Controls: Feed Panel
		var oFeedPanel = L.control({position: bIsMobile?'bottomright':'topright'});
		var $FeedButton = $('#feed-button').clone();
		$FeedButton.click(() => {this.toggleFeedPanel();});
		oFeedPanel.onAdd = function(oMap) {return $FeedButton[0];};
		oFeedPanel.addTo(oMap);

		//Controls: Legend
		if(!bIsMobile) {
			var oLegend = L.control({position: 'bottomleft'});
			oLegend.onAdd = function(oMap) {return $('#legend').clone()[0];};
			oLegend.addTo(oMap);
		}

		//Controls: Projects
		var $Labels = $('<div>', {'class': 'leaflet-control-layers-base'});
		$.each(this.spot.vars('projects'), (sCodeName, asProject) => {
			var asRadioAttrs = {'type': 'radio', 'class': 'leaflet-control-layers-selector', 'name':'project', 'value': sCodeName};
			if(asProject.id == this.spot.vars(['project', 'id'])) asRadioAttrs.checked = 'checked';
			var $Radio = $('<input>', asRadioAttrs).on('change', (oEvent) => {
				this.toggleSettingsPanel(false);
				this.spot.setHash(this.spot.vars('page'), [$(oEvent.currentTarget).val()]);
			});

			var $Label = $('<label>').append($('<div>')
				.append($Radio)
				.append($('<span>').text(' '+asProject.name))
				.append($('<a>', {'class':'fa fa-download push-left', href:asProject.gpxfilepath, title:this.spot.lang('track_download')}).on('click', (oEvent) => {oEvent.stopPropagation();}))
			);
			$Labels.append($Label);
		});
		$('#settings-projects').empty().append($Labels);

		//Controls: Scale
		L.control.scale({imperial: false, position: bIsMobile?'topright':'bottomright'}).addTo(oMap);

		//Controls: Elevation
		if(!bIsMobile) {
			var aoElevTracks = {type: 'FeatureCollection', features:[], properties: {summary: 'trackType'}};
			var aoLegend = {trackType: {}};
			for(var i in aoTracks.features) {
				if(aoTracks.features[i].properties.type != 'hitchhiking') {
					//Feature
					var oTrack = jQuery.extend(true, {}, aoTracks.features[i]);
					var sType = aoTracks.features[i].properties.type;
					oTrack.properties.attributeType = sType;
					aoElevTracks.features.push(oTrack);

					//Legend
					aoLegend.trackType[sType] = {text: this.spot.lang('track_'+sType), color: this.spot.tmp(['track-type-styles', sType, 'color'])};
				}
			}
			var oElev = L.control.heightgraph({
				position: 'bottomright',
				expand: false,
				width: this.getElevWidth(),
				height: 200,
				margins: {
					top: 10,
					right: 30,
					bottom: 55,
					left: 50
				},
				translation: {
					distance: this.spot.lang('distance'),
					elevation: this.spot.lang('elevation'),
					segment_length: this.spot.lang('segment_length'),
					type: this.spot.lang('type'),
					legend: this.spot.lang('legend')
				},
				xTicks: 3,
				yTicks: 2,
				mappings: aoLegend,
				highlightStyle: {
					weight: this.spot.tmp(['track-type-styles', 'main', 'weight']),
					opacity: 1,
					color: '#326526'
				},
				expandCallback: (bExpanded)  => {
					$('.leaflet-control-scale').toggle(!bExpanded);
					let iElevWidth = this.getElevWidth();
					if(iElevWidth != this.spot.tmp('elev').options.width) this.spot.tmp('elev').resize({width:iElevWidth});
				}
			});
			oElev.addTo(oMap);
			oElev.addData([aoElevTracks]);
			this.spot.tmp('elev', oElev);
			this.spot.tmp('elev-data', [aoElevTracks]);
		}

		//Controls: Tiles (layers): Add & Move to Settings Panel
		L.control.layers(this.spot.tmp('layers'), null, {position: 'topleft'}).addTo(oMap);
		$('#layers').empty().append($('.leaflet-control-layers-list .leaflet-control-layers-base'));

		//Actual Tracks: Track with corresponding colors
		var oActualTracks = L.geoJson(aoTracks, {
			style: (oTrack) => {
				return this.spot.tmp(['track-type-styles', oTrack.properties.type]);
			}
		}).addTo(oMap);

		//"Hover" Tracks: Wider track (thus hover area) to avoid flickering popups
		this.spot.tmp('track', L.geoJson(aoTracks, {
			style: {weight: 20, opacity: 0},
			onEachFeature: (feature, oLayer) => {
				var asProperties = feature.properties;
				var $Tooltip = $('<div>', {'class':'track_tooltip'});

				//Title
				$('<h1>', {'title':asProperties.name})
					.addIcon('fa-track-'+asProperties.type+' fa-fw', true)
					.append(asProperties.name)
					.appendTo($Tooltip);

				//Description
				var $TooltipBody = $('<div>', {'class':'body'}).appendTo($Tooltip);
				if(asProperties.Name != asProperties.description && asProperties.description != '') {
					$('<p>', {'class':'description', 'title':asProperties.description}).text(asProperties.description).appendTo($TooltipBody);
				}

				//Calculate Details
				var aiCoords = feature.geometry.coordinates;
				if(aiCoords && asProperties.type != 'hitchhiking') {
					var iDistance = 0, iElevDrop = 0, iElevGain = 0, iTime = 0;

					//Track duration (in hours)
					for (var i = 1; i < aiCoords.length; i++) {
						var oCurrPoint = new L.LatLng(aiCoords[i][1], aiCoords[i][0]);
						var oPrevPoint = new L.LatLng(aiCoords[i - 1][1], aiCoords[i - 1][0]);
						var iElevDelta = aiCoords[i][2] - aiCoords[i - 1][2];
						var iSegDistance = oCurrPoint.distanceTo(oPrevPoint);

						iDistance += iSegDistance;
						iElevDrop += Math.min(iElevDelta, 0);
						iElevGain += Math.max(iElevDelta, 0);

						var iSpeedCorrecRatio = 0;
						var iAngle = iElevDelta / iSegDistance;
						if(iAngle < -1)			iSpeedCorrecRatio = 0.5;
						else if(iAngle < -0.2)	iSpeedCorrecRatio = 1.25;
						else if(iAngle < 0.1)	iSpeedCorrecRatio = 1;
						else if(iAngle < 0.25)	iSpeedCorrecRatio = 0.85;
						else if(iAngle < 0.5)	iSpeedCorrecRatio = 0.6;
						else if(iAngle < 1)		iSpeedCorrecRatio = 0.5;
						else					iSpeedCorrecRatio = 0.25;
						iTime += iSegDistance / 1000 * iSpeedCorrecRatio / 3.5 ; //default speed: 3.5km/h
					}

					//Conversion of hours into natural language
					var sDuration = this.spot.getNaturalDuration(iTime);

					//Tooltip details info
					$TooltipBody
						.append($('<div>', {'class':'separator'}))
						.append($('<div>', {'class':'details'})
							.append($('<p>', {'class':'detail'}).addIcon('fa-distance fa-fw', true).append(Math.round(iDistance/1000)+'km'))
							.append($('<p>', {'class':'detail'}).addIcon('fa-time fa-fw', true).append(sDuration))
							.append($('<p>', {'class':'detail'}).addIcon('fa-elev-gain fa-fw', true).append(iElevGain+'m'))
							.append($('<p>', {'class':'detail'}).addIcon('fa-elev-drop fa-fw', true).append(iElevDrop+'m'))
						);
						
					//Trail Start/End
					this.spot.tmp(['trail-markers', asProperties.name], {
						'start'	: L.marker(new L.latLng(aiCoords[0][1], aiCoords[0][0]), {icon: this.getDivIcon('track-start')}),
						'end'	: L.marker(new L.latLng(aiCoords[aiCoords.length - 1][1], aiCoords[aiCoords.length - 1][0]), {icon: this.getDivIcon('track-end')})
					});

					oLayer
						.bindPopup($Tooltip[0])
						.on('click', (e) => {
							this.toggleSoftPopup(e, 'open');
						})
						.on('popupclose', (e) => {
							this.toggleSoftPopup(e, 'close');
						});
				}
			}
		}).addTo(oMap));

		//Last message
		var oLastMsg = (aoMessages.length > 0)?aoMessages[aoMessages.length-1]:{};

		//Centering map
		var iFeedPanelWidth = bIsMobile?0:parseInt(this.spot.tmp('$Feed').outerWidth(true));
		var iTitleHeight = bIsMobile?this.spot.tmp('$Title').outerHeight(true):0;
		if(
			this.spot.vars(['project', 'mode']) == this.spot.consts.modes.blog &&
			!$.isEmptyObject(oLastMsg) &&
			this.spot.getHash()[2] != 'message'
		) {
			//Zoom on last message
			oMap.setView(L.latLng(oLastMsg.latitude, oLastMsg.longitude), 15);
			oMap.panBy([iFeedPanelWidth/2, /*bIsMobile?(this.spot.tmp('$Title').outerHeight(true) * -1/2):*/0]);
		}
		else {
			oMap.fitBounds(
				this.spot.tmp('track').getBounds(),
				{
					paddingTopLeft: L.point(5, 5 + this.spot.tmp('marker_size').height), //So that the first marker isn't off screen
					paddingBottomRight: L.point(5 + iFeedPanelWidth, 5 + iTitleHeight)
				}
			);
		}

		//Add Spot messages
		this.addSpotMarkers(aoMessages);

		//Add Medias
		this.addMediaMarkers(aoMedias)

		//Open tooltip on latest message in mobile mode
		if(
			!$.isEmptyObject(oLastMsg) &&
			this.spot.vars(['project', 'mode']) == this.spot.consts.modes.blog &&
			(!oLastMsg.medias || oLastMsg.medias.length < 3) &&
			this.isMobile()
		) this.spot.tmp(['markers', oLastMsg.id_message]).openPopup();

		/*
		this.spot.tmp('tracks', aoTracks);
		next(24, 0, 0, 5);
		*/
	}

	addSpotMarkers(aoMessages) {
		let iWorkSpaceMinWidth = this.isMobile()?this.spot.tmp('$Projects').width():(this.spot.tmp('$Projects').width() - this.spot.tmp('$Feed').outerWidth(true) - this.spot.tmp('$Settings').outerWidth(true));

		$.each(aoMessages, (iKey, oMsg) => {

			//Tooltip
			let $Tooltip = $('<div>', {'class':'info-window'})
				.append($('<h1>')
					.addIcon('fa-message fa-lg', true)
					.append($('<span>').text(this.spot.lang('post_message')+' '+this.spot.lang('counter', oMsg.displayed_id)))
					.append($('<span>', {'class':'message-type'}).text('('+oMsg.type+')'))
				)
				.append($('<div>', {'class':'separator'}))
				.append($('<p>', {'class':'coordinates'})
					.addIcon('fa-coords fa-fw fa-lg', true)
					.append(this.getGoogleMapsLink(oMsg)))
				.append($('<p>', {'class':'time'})
					.addIcon('fa-time fa-fw fa-lg', true)
					.append(oMsg.formatted_time+(this.spot.vars(['project', 'mode'])==this.spot.consts.modes.blog?' ('+oMsg.relative_time+')':'')));

			//Tooltip: Time Zone
			if(oMsg.formatted_time_local != oMsg.formatted_time) {
				$Tooltip.append($('<p>', {'class':'timezone'})
					.addIcon('fa-timezone fa-fw fa-lg', true)
					.append(this.spot.lang('local_time', this.getRelativeTime(oMsg.formatted_time_local, oMsg.day_offset))));
			}

			//Tooltip: Weather
			if(oMsg.weather_icon && oMsg.weather_icon!='unknown') {
				$Tooltip.append($('<p>', {'class':'weather', 'title':(oMsg.weather_cond==''?'':this.spot.lang(oMsg.weather_cond))})
					.addIcon('fa-'+oMsg.weather_icon+' fa-fw fa-lg', true)
					.append(oMsg.weather_temp+'°C')
				);
			}

			//Marker
			let oMarker = L.marker(
				L.latLng(oMsg.latitude, oMsg.longitude),
				{
					riseOnHover: true,
					icon: this.getDivIcon('message-in fa-rotate-270'),
					$Tooltip: $Tooltip,
					medias: oMsg.medias
				}
			)
			.bindPopup(
				(e) => {
					//Tooltip: Medias: Set on the fly to avoid resource load
					let $Tooltip = e.options.$Tooltip;
					let $Medias = $Tooltip.find('.medias');
					if(e.options.medias && $Medias.length == 0) {
						$Medias = $('<div>', {'class':'medias'});
						$.each(e.options.medias, (iKey, asMedia) => {
							$Medias.append(this.getMediaLink(asMedia, 'marker'));
						});
						$Tooltip.append($Medias);
					}
					return $Tooltip[0];
				},
				{
					maxWidth: iWorkSpaceMinWidth,
					autoPan: false,
					closeOnClick: true,
					offset: new L.Point(0, -30)
				}
			)
			.addTo(this.spot.tmp('map'));

			this.spot.tmp(['markers', oMsg.id_message], oMarker);
		});
	}

	addMediaMarkers(aoMedias) {
		let iWorkSpaceMinWidth = this.isMobile()?this.spot.tmp('$Projects').width():(this.spot.tmp('$Projects').width() - this.spot.tmp('$Feed').outerWidth(true) - this.spot.tmp('$Settings').outerWidth(true));

		$.each(aoMedias, (iKey, oMedia) => {

			//Tooltip
			let $Tooltip = $('<div>', {'class':'info-window'})
				.append($('<h1>')
					.addIcon('fa-'+oMedia.subtype+' fa-lg', true)
					.append($('<span>').text(this.spot.lang(oMedia.subtype)+' '+this.spot.lang('counter', oMedia.displayed_id || oMedia.id_media)))
				)
				.append($('<div>', {'class':'separator'}))
				.append($('<p>', {'class':'time'})
					.addIcon('fa-time fa-fw fa-lg', true)
					.append(oMedia.formatted_time+(this.spot.vars(['project', 'mode'])==this.spot.consts.modes.blog?' ('+oMedia.relative_time+')':'')));

			//Tooltip: Time Zone
			if(oMedia.formatted_time_local != oMedia.formatted_time) {
				$Tooltip.append($('<p>', {'class':'timezone'})
					.addIcon('fa-timezone fa-fw fa-lg', true)
					.append(this.spot.lang('local_time', getRelativeTime(oMedia.formatted_time_local, oMedia.day_offset))));
			}

			//Tooltip: Altitude
			if(oMedia.altitude) {
				$Tooltip.append($('<p>', {'class':'altitude'})
					.addIcon('fa-altitude fa-fw fa-lg', true)
					.append(oMedia.altitude+'m'));
			}

			//Marker
			let oMarker = L.marker(
				L.latLng(oMedia.latitude, oMedia.longitude),
				{
					riseOnHover: true,
					icon: this.getDivIcon(oMedia.subtype),
					$Tooltip: $Tooltip,
					medias: [oMedia]
				}
			)
			.bindPopup(
				(e) => {
					//Tooltip: Medias: Set on the fly to avoid resource load
					let $Tooltip = e.options.$Tooltip;
					let $Medias = $Tooltip.find('.medias');
					if(e.options.medias && $Medias.length == 0) {
						$Medias = $('<div>', {'class':'medias'});
						$.each(e.options.medias, (iKey, asMedia) => {
							$Medias.append(this.getMediaLink(asMedia, 'marker'));
						});
						$Tooltip.append($Medias);
					}
					return $Tooltip[0];
				},
				{
					maxWidth: iWorkSpaceMinWidth,
					autoPan: false,
					closeOnClick: true,
					offset: new L.Point(0, -30)
				}
			)
			.addTo(this.spot.tmp('map'));

			oMarker.on('mouseover', function(e) {this.openPopup();});
		});
	}

	toggleSoftPopup(e, sMode) {
		switch(sMode) {
			case 'open':
				var oLatLng = L.GeometryUtil.closest(this.spot.tmp('map'), e.sourceTarget, e.latlng);
				var oPopup = e.target.getPopup();
				if(!oPopup.isOpen()) oPopup.setLatLng(oLatLng).openOn(this.spot.tmp('map'));

				var asTrailMarkers = this.spot.tmp(['trail-markers', e.target.feature.properties.name]);
				var oPointStart = this.spot.tmp('map').latLngToLayerPoint(asTrailMarkers.start.getLatLng());
				var oPointEnd = this.spot.tmp('map').latLngToLayerPoint(asTrailMarkers.end.getLatLng());
				if(oPointStart.distanceTo(oPointEnd) > 200) {
					asTrailMarkers.start.addTo(this.spot.tmp('map'));
					asTrailMarkers.end.addTo(this.spot.tmp('map'));
				}
				break;
			case 'close':
				//e.target.closePopup();
				var asTrailMarkers = this.spot.tmp(['trail-markers', e.target.feature.properties.name]);
				asTrailMarkers.start.remove();
				asTrailMarkers.end.remove();
				break;
		}
	}

	getDivIcon(sIcon) {
		return L.divIcon({
			className: '',
			html: '<span class="fa-stack"><i class="fa fa-message fa-stack-2x"></i><i class="fa fa-'+sIcon+' fa-stack-1x"></i></span>',
			iconSize: [this.spot.tmp('marker_size').width, this.spot.tmp('marker_size').height],
			iconAnchor: [this.spot.tmp('marker_size').width / 2, this.spot.tmp('marker_size').height] //position from icon's top left corner (iconAnchor = marker's position)
		});
	}

	onFeedScroll(oEvent) {
		var $Box = $(oEvent.currentTarget);
		var $BoxContent = $Box.find('.simplebar-content');
		if(($Box.scrollTop() + $(window).height()) / $BoxContent.height() >= 0.8) this.updateFeed();
	}

	onAutoUpdate(bStart) {
		bStart = bStart || false;
		if(!bStart) this.checkNewFeed();
		this.setFeedUpdateTimer(60, this.onAutoUpdate);
	}

	checkNewFeed() {
		this.spot.get(
			'new_feed',
			(asData) => {
				let iItemCount = Object.keys(asData.feed).length;
				if(iItemCount > 0) this.spot.tmp('ref_id_first', asData.ref_id_first);
				
				//Feed
				let $Posts = $('<div>');
				$.each(asData.feed, (iKey, asPost) => {
					$Posts.append(this.getPost(asPost));
				});
				this.spot.tmp('$PostList').prepend($Posts.children());

				//Markers
				this.addSpotMarkers(asData.messages);

				//Message Last Update
				this.updateSettingsPanel(asData.last_update);
			}, {
				id_project: this.spot.vars(['project', 'id']),
				id: this.spot.tmp('ref_id_first')
			}
		);
	}

	setFeedUpdateTimer(iSeconds, fCallback) {
		if(typeof this.spot.tmp('update_timer') != 'undefined') clearTimeout(this.spot.tmp('update_timer'));
		if(iSeconds >= 0) this.spot.tmp('update_timer', setTimeout(fCallback.bind(this), iSeconds * 1000));
	}

	updateFeed(bFirstChunk, bDiscrete, fCallback) {
		bFirstChunk = bFirstChunk || false;
		bDiscrete = bDiscrete || false;
		fCallback = fCallback || function(){};

		//First/Last Item displayed on the feed
		if(bFirstChunk) this.spot.tmp('ref_id_last', 0);

		if(this.spot.tmp('updatable')) {
			if(!this.spot.tmp('out-of-data') || bFirstChunk) {
				this.spot.tmp('updatable', false);
				if(!bDiscrete) $('#loading').show();

				var $Posts = $('<div>');
				var a$Medias = [];

				this.spot.get(
					'next_feed',
					(asData) => {
						$('#loading').hide();
						var iItemCount = Object.keys(asData.feed).length;
						
						//Update pointers
						this.spot.tmp('out-of-data', iItemCount < this.spot.vars('chunk_size'));
						if(iItemCount > 0) {
							this.spot.tmp('ref_id_last', asData.ref_id_last);
							if(bFirstChunk === true) this.spot.tmp('ref_id_first', asData.ref_id_first);
						}

						//Add posts
						$.each(asData.feed, (iKey, asPost) => {
							var $Post = this.getPost(asPost);
							$Posts.append($Post);
							if(asPost.type == 'media' && this.isLightboxOpen()) a$Medias.push($Post.find('.media-link'));
						});
						if(bFirstChunk === true) this.spot.tmp('$PostList').empty();
						this.spot.tmp('$PostList').append($Posts.children());
						this.spot.tmp('$PostList').find('img').waitForImages(true).done(fCallback);

						//Add media to lightbox
						$.each(a$Medias, (iKey, $Media) => {
							lightbox.addToAlbum($Media);
						});
						if(a$Medias.length > 0) lightbox.updateDetails();
						
						this.spot.tmp('updatable', true);
					}, {
						id_project: this.spot.vars(['project', 'id']),
						id: this.spot.tmp('ref_id_last')
					}
				);
			}
		}
		else if(bFirstChunk) this.setFeedUpdateTimer(0.2, () => {this.updateFeed(bFirstChunk, bDiscrete, fCallback);});
	}

	focusOnPost(oFocusPost) {
		if(oFocusPost) {
			if(oFocusPost.type=='message' && (!this.spot.tmp(['markers', oFocusPost.id]) || !this.spot.tmp('map'))) {
				setTimeout(() => {this.focusOnPost(oFocusPost);}, 100);
			}
			else {
				var sElemId = '#'+oFocusPost.type+'-'+oFocusPost.id;
				var $Post = this.spot.tmp('$PostList').find(sElemId);
				var bGetToTop = (this.spot.tmp('$PostList').height() - (($Post.length > 0)?$Post.position().top:0) >= $(window).height());
				if($Post.length > 0 && (bGetToTop || this.spot.tmp('out-of-data'))) {
					this.goToPost(oFocusPost);
					if(oFocusPost.type=='media' || oFocusPost.type=='message') $Post.find('a.drill').click();
				}
				else if(!this.spot.tmp('out-of-data')) this.updateFeed(false, false, () => {this.focusOnPost(oFocusPost);});
				else console.log('Missing element ID '+sElemId);

				//Reset Hash
				this.spot.flushHash(['post', 'message']);
			}
		}
	}

	goToPost(oPost) {
		var sElemId = '#'+oPost.type+'-'+oPost.id;
		var $Post = this.spot.tmp('$PostList').find(sElemId);
		if($Post.length > 0) {
			this.spot.tmp('simple-bar').getScrollElement().scrollTop += Math.round(
				$Post.offset().top
				- parseInt($('#feed-panel').css('padding-top'))
				/*
				$Post.position().top
				+ parseInt($Post.css('margin-top'))
				+ this.spot.tmp('$Poster').outerHeight(true)
				+ parseInt($('#feed-panel').css('padding-top'))
				*/
			);
		}
		else console.log('Missing element ID '+sElemId);
	}

	getRelativeTime(sLocalTime, iOffset) {
		let $Time = $('<span>').text(sLocalTime.substr(-5));
		if(iOffset != '0') $Time.append($('<sup>', {'title': iOffset+' '+this.spot.lang('unit_day')+' ('+sLocalTime.substr(0, 5)+')'}).text(' '+iOffset));
		return $Time.html();
	}

	getPost(asPost) {
		asPost.headerless = asPost.headerless || false;
		var bLink = false;
		var asHash = this.spot.getHash();
		var sHash = '#'+[asHash.page, asHash.items[0], asPost.type, asPost.id].join(this.spot.consts.hash_sep);

		var $Post = $('<div>', {'class':'post-item '+asPost.type+(asPost.headerless?' headerless':'')});
		if(asPost.id) $Post.prop('id', asPost.type+'-'+asPost.id);

		var sRelTime = (asPost.relative_time!='')?((this.spot.vars('project') && this.spot.vars(['project', 'mode'])==this.spot.consts.modes.histo)?asPost.formatted_time.substr(0, 10):asPost.relative_time):'';
		var sAbsTime = asPost.formatted_time;
		var sAbsTimeLocal = asPost.formatted_time_local;
		var bTimeDiff = (sAbsTime && sAbsTimeLocal != sAbsTime);
		var sType = asPost.subtype || asPost.type;
		var $Body = {};

		switch(asPost.type) {
			case 'message':
				bLink = true;
				$Body = $('<div>', {'class':'body-box'})
					.data('id', asPost.id_message)
					.data('clicked', false)
					.append($('<p>').addIcon('fa-coords', true).append(this.getGoogleMapsLink(asPost)))
					.append($('<p>').addIcon('fa-time', true).append(sAbsTime))
					.append(bTimeDiff?$('<p>').addIcon('fa-timezone', true).append(this.spot.lang('local_time', this.getRelativeTime(sAbsTimeLocal, asPost.day_offset))):'')
					.append($('<a>', {'class':'drill'})
						.append((!asPost.weather_icon || asPost.weather_icon=='unknown')?'':$('<span>', {'class':'weather clickable', 'title':this.spot.lang(asPost.weather_cond)}).addIcon('fa-'+asPost.weather_icon).append($('<span>').text(asPost.weather_temp+'°C')))
						.append($('<img>', {'class':'staticmap clickable', title: this.spot.lang('click_zoom'), src: asPost.static_img_url}))
						.append($('<span>', {'class': 'drill-icon fa-stack clickable'})
							.addIcon('fa-message fa-stack-2x clickable')
							.addIcon('fa-message-in fa-stack-1x fa-rotate-270')
						)
						.on('click', (oEvent) => {
							var $Parent = $(oEvent.currentTarget).parent();
							var oMarker = this.spot.tmp(['markers', $Parent.data('id')]);
							if(this.isMobile()) {
								this.toggleFeedPanel(false, 'panToInstant');
								this.spot.tmp('map').setView(oMarker.getLatLng(), 15);
							}
							else {
								var iOffset = (this.isFeedPanelOpen()?1:-1)*this.spot.tmp('$Feed').outerWidth(true)/2 - (this.isSettingsPanelOpen()?1:-1)*this.spot.tmp('$Settings').outerWidth(true)/2;
								var iRatio = -1 * iOffset / $('body').outerWidth(true);
								this.spot.tmp('map').setOffsetView(iRatio, oMarker.getLatLng(), 15);
							}

							$Parent.data('clicked', true);
							if(!oMarker.isPopupOpen()) oMarker.openPopup();
						})
					)
					.hover(
						(oEvent) => {
							let oMarker = this.spot.tmp(['markers', $(oEvent.currentTarget).data('id')]);
							if(this.spot.tmp('map') && this.spot.tmp('map').getBounds().contains(oMarker.getLatLng()) && !oMarker.isPopupOpen()) oMarker.openPopup();
						},
						(oEvent) => {
							let $This = $(oEvent.currentTarget);
							let oMarker = this.spot.tmp(['markers', $This.data('id')]);
							if(oMarker && oMarker.isPopupOpen() && !$This.data('clicked')) oMarker.closePopup();
							$This.data('clicked', false);
						}
					);
				break;
			case 'media':
				bLink = true;
				$Body = $('<div>', {'class':'body-box'}).append(this.getMediaLink(asPost, 'post'));
				if(asPost.comment) $Body.find('a.drill').append($('<span>', {'class':'comment'}).text(asPost.comment));
				break;
			case 'post':
				bLink = true;
				var $SigImg = asPost.gravatar?$('<img>').attr({'src':'data:image/png;base64, '+asPost.gravatar, 'width':'24', 'height':'24', 'alt':'--'}):($('<span>').text('-- '));
				$Body = $('<div>')
					.append($('<p>', {'class':'message'}).text(asPost.content))
					.append($('<p>', {'class':'signature'})
						.append($SigImg)
						.append($('<span>').text(asPost.formatted_name))
					);
				break;
			case 'poster':
				$Body = $('<p>', {'class':'message'})
							.append($('<textarea>', {id:'post', name:'post', placeholder:this.spot.lang('post_message'), 'class':'autoExpand', rows:'1'}))
							.append($('<input>', {type:'text', id:'name', name:'name', placeholder:this.spot.lang('post_name')}))
							.append($('<button>', {type:'button', id:'submit', name:'submit', 'aria-label':this.spot.lang('send')}).addIcon('fa-send'));
				break;
			case 'archived':
				$Body = $('<div>')
						.append($('<p>').addIcon('fa-success'))
						.append($('<p>').text(this.spot.lang('mode_histo')));
				break;
			case 'loading':
				$Body = $('<p>', {'class':'flicker'}).addIcon('fa-post');
				break;
		}

		$Post
			.append($('<div>', {'class':'header'})
				.append($('<span>', {'class':'index'})
					.addIcon('fa-'+sType)
					.append(asPost.displayed_id?' '+this.spot.lang('counter', asPost.displayed_id):'')
				)
				.append($('<span>', {'class':'time', title:bTimeDiff?this.spot.lang('local_time', sAbsTimeLocal):''}).hoverSwap(sRelTime, bTimeDiff?this.spot.lang('your_time', sAbsTime):sAbsTime)))
			.append($('<div>', {'class':'body'}).append($Body));

		if(bLink) {
			$Post.find('.index').append($('<a>', {'class':'link desktop', 'href':sHash, 'title':this.spot.lang('copy_to_clipboard')})
				.data('url', this.spot.consts.server+sHash)
				.addIcon('fa-link')
				.on('click', ($This) => {
					let $Link = $($This.currentTarget);
					copyTextToClipboard($Link.data('url'));
					$Link.prop('title', this.spot.lang('link_copied')).find('i.fa').addClass('copied');
					$Link.delay(5000).fadeOut(300, () => {
						$Link.prop('title', this.spot.lang('copy_to_clipboard')).find('i.fa').removeClass('copied');
						$Link.fadeIn(300);
					});
				})
			);
		}

		return $Post;
	}

	getMediaLink(asData, sType) {
		var bTimeDiff = (asData.posted_on_formatted && asData.posted_on_formatted_local != asData.posted_on_formatted);

		var $Comment = (!asData.comment || asData.comment == '')?'':
			$('<span>', {'class': 'lb-caption-line comment desktop', 'title': asData.comment})
				.addIcon('fa-post fa-lg fa-fw', true)
				.append($('<span>', {'class':'comment-text'}).text(asData.comment));

		var $PostedOn =
			$('<span>', {'class': 'lb-caption-line', title: bTimeDiff?this.spot.lang('local_time', asData.posted_on_formatted_local):''})
				.addIcon('fa-upload fa-lg fa-fw', true)
				.append(asData.posted_on_formatted);

		var $TakenOn = (asData.taken_on == asData.posted_on)?'':
			$('<span>', {'class': 'lb-caption-line', title: bTimeDiff?this.spot.lang('local_time', asData.taken_on_formatted_local):''})
				.addIcon('fa-'+asData.subtype+'-shot fa-lg fa-fw', true)
				.append(asData.taken_on_formatted);

		var $Title = $('<div>')
			.append($Comment)
			.append(sType=='marker'?$TakenOn:$PostedOn)
			.append(sType=='marker'?$PostedOn:$TakenOn);
		
		var $Link =
			$('<a>', {
				'class': 'media-link drill',
				'href': asData.media_path,
				'data-lightbox': sType+'-medias',
				'data-type': asData.subtype,
				'data-id': asData.id_media,
				'data-title': $Title.html(),
				'data-orientation': asData.rotate
			})
			.append($('<img>')
				.attr({
					'src': asData.thumb_path,
					'width': asData.width, //set image ratio so that the required space can be reserved
					'height': asData.height,
					'title': this.spot.lang((asData.subtype == 'video')?'click_watch':'click_zoom'),
					'class':'clickable'
				})
			)
			.append($('<span>', {'class': 'drill-icon'}).addIcon('fa-drill-'+asData.subtype));

		return $Link;
	}

	getGoogleMapsLink(asInfo) {
		return $('<a>', {
			href:'https://www.google.com/maps/place/'+asInfo.lat_dms+'+'+asInfo.lon_dms+'/@'+asInfo.latitude+','+asInfo.longitude+',10z',
			title: this.spot.lang('see_on_google'),
			target: '_blank',
			rel: 'noreferrer noopener'
		}).text(asInfo.lat_dms+' '+asInfo.lon_dms);
	}
}


/*
function next(iCurrTrack, iCurrIndex, iCurrOffset, iCurrZoom) {
	var aoTracks = this.spot.tmp('tracks');
	var aoOffset = {0:[0,0], 1:[-1,0], 2:[-1,1], 3:[0,1], 4:[1,1], 5:[1,0], 6:[1,-1], 7:[0,-1], 8:[-1,-1]};

	console.log('Getting Track '+iCurrTrack+'/'+(aoTracks.features.length - 1)+', '+
				'Point '+iCurrIndex+'/'+(aoTracks.features[iCurrTrack].geometry.coordinates.length - 1)+', '+
				'Zoom '+iCurrZoom+'/14, '+
				'Offset ['+aoOffset[iCurrOffset][0]+','+aoOffset[iCurrOffset][1]+']');

	//Position map
	var iLat = aoTracks.features[iCurrTrack].geometry.coordinates[iCurrIndex][1] + aoOffset[iCurrOffset][1] * 0.0347910214271;
	var iLng = aoTracks.features[iCurrTrack].geometry.coordinates[iCurrIndex][0] + aoOffset[iCurrOffset][0] * 0.1016235351560;
	this.spot.tmp('map').setView(L.latLng(iLat, iLng), iCurrZoom);

	//Go to next
	iCurrOffset++;
	if(iCurrZoom < 13 || iCurrOffset > 8) {
		iCurrOffset = 0;
		iCurrZoom++;
		if(iCurrZoom > 14) {
			iCurrZoom = 5;
			iCurrIndex += 100;
			if(!(iCurrIndex in aoTracks.features[iCurrTrack].geometry.coordinates)) {
				iCurrIndex = 0;
				iCurrTrack++;
				if(!(iCurrTrack in aoTracks.features)) return true;
			}
		}
	}

	timer = setTimeout(function(){next(iCurrTrack, iCurrIndex, iCurrOffset, iCurrZoom);}, 1000);
}
function stop() {
	clearTimeout(timer);
}
*/