<script>
import 'maplibre-gl/dist/maplibre-gl.css';
import { Map, NavigationControl, Marker, LngLatBounds, LngLat, Popup } from 'maplibre-gl';
import { createApp, defineComponent, nextTick, ref, defineCustomElement, provide, inject } from 'vue';
import simplebar from 'simplebar-vue';

import autosize from 'autosize';
import mousewheel from 'jquery-mousewheel';
import waitforimages from 'jquery.waitforimages';
import lightbox from '../scripts/lightbox.js';

//import SimpleBar from 'simplebar';

import SpotIcon from './spotIcon.vue';
import SpotButton from './spotButton.vue';
import ProjectPost from './projectPost.vue';
import ProjectPopup from './projectPopup.vue';

export default {
	components: {
		SpotIcon,
		SpotButton,
		ProjectPost,
		ProjectPopup,
		simplebar
	},
	data() {
		return {
			server: this.spot.consts.server,
			feed: {loading:false, updatable:true, outOfData:false, refIdFirst:0, refIdLast:0, firstChunk:true},
			refreshRate: 60,
			lastUpdate: { unix_time: 0, relative_time: '', formatted_time: ''},
			feedPanelOpen: false,
			feedSimpleBar: null,
			settingsPanelOpen: false,
			markerSize: {width: 32, height: 32},
			currProject: {},
			currProjectCodeName: null,
			modeHisto: false,
			posts: [],
			nlFeedbacks: [],
			nlLoading: false,
			baseMaps: {},
			baseMap: null,
			messages: null,
			map: null,
			hikes: {
				colors:{'main':'#00ff78', 'off-track':'#0000ff', 'hitchhiking':'#FF7814'},
				width: 4
			},
			popup: {content: null, element: null}
		};
	},
	computed: {
		projectClasses() {
			return [
				this.feedPanelOpen?'with-feed':'',
				this.settingsPanelOpen?'with-settings':''
			].filter(n => n).join(' ');
		},
		nlClasses() {
			return [
				this.nlAction,
				this.nlLoading?'loading':''
			].filter(n => n).join(' ');
		},
		subscribed() {
			return this.user.id_user > 0;
		},
		nlAction() {
			return this.subscribed?'unsubscribe':'subscribe';
		},
		mobile() {
			return this.spot.isMobile();
		}
	},
	watch: {
		baseMap(sNewBaseMap, sOldBaseMap) {
			if(sOldBaseMap) this.map.setLayoutProperty(sOldBaseMap, 'visibility', 'none');
			if(sNewBaseMap) this.map.setLayoutProperty(sNewBaseMap, 'visibility', 'visible');
		},
		currProjectCodeName(sNewCodeName, sOldCodeName) {
			//this.toggleSettingsPanel(false);
			if(this.$parent.hash.items.length==0) this.$parent.setHash(this.$parent.hash.page, [sNewCodeName]);
			this.init();
		}
	},
	provide() {
		return {
			map: {
				panToBetweenPanels: this.panToBetweenPanels,
				openMarkerPopup: this.openMarkerPopup,
				closeMarkerPopup: this.closeMarkerPopup,
				isMarkerVisible: this.isMarkerVisible
			},
			project: this
		};
	},
	inject: ['spot', 'projects', 'user'],
	mounted() {
		this.spot.addPage('project', {
			onResize: () => {
				//this.spot.tmp('map_offset', -1 * (this.feedPanelOpen?getOuterWidth(this.$refs.feed):0) / getOuterWidth(window));

				/* TODO
				if(typeof this.spot.tmp('elev') != 'undefined' && this.spot.tmp('elev')._showState) {
					this.spot.tmp('elev').resize({width:this.getElevWidth()});
				}
				*/
			},
			onQuitPage: () => {
				this.setFeedUpdateTimer(-1);
			}
		});

		this.currProjectCodeName = (this.$parent.hash.items.length==0)?this.spot.vars('default_project_codename'):this.$parent.hash.items[0];
	},
	beforeUnmount() {
		this.setFeedUpdateTimer(-1);
	},
	methods: {
		init() {
			let bFirstLoad = (typeof this.currProject.codename == 'undefined');
			this.initProject();
			if(bFirstLoad) this.initLightbox();
			this.initFeed();
			this.initMap();
		},
		initProject() {
			this.currProject = this.projects[this.currProjectCodeName];
			this.modeHisto = (this.currProject.mode == this.spot.consts.modes.histo);
			this.feed = {loading:false, updatable:true, outOfData:false, refIdFirst:0, refIdLast:0, firstChunk:true};
			this.posts = [];
			//this.baseMap = null;
			this.baseMaps = {};
		},
		initLightbox() {
			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();}
			});
		},
		async initFeed() {
			//Simplebar event
			this.$refs.feedSimpleBar.scrollElement.addEventListener('scroll', (oEvent) => {this.onFeedScroll(oEvent);});

			//Mobile Touchscreen Events
			//TODO

			//Add post Event handling
			//TODO
			
			//Get first posts batch
			await this.getNextFeed();
			this.$refs.feedSimpleBar.scrollElement.scrollTop = 0;

			//Scroll to post
			if(this.$parent.hash.items.length == 3) this.findPost({type: this.$parent.hash.items[1], id: this.$parent.hash.items[2]});
		
			//Start auto-update
			if(!this.modeHisto) this.setFeedUpdateTimer(this.refreshRate);
		},
		async initMap() {
			//Get Map Info
			const aoMarkers = await this.spot.get2('markers', {id_project: this.currProject.id});
			this.baseMap = null;
			this.baseMaps = aoMarkers.maps;
			this.messages = aoMarkers.messages;
			this.lastUpdate = aoMarkers.last_update;

			console.log(this.baseMaps);

			//Base maps (raster tiles)
			let asSources = {};
			let asLayers = [];
			for(const asBaseMap of this.baseMaps) {
				asSources[asBaseMap.codename] = {
					type: 'raster',
					tiles: [asBaseMap.pattern],
					tileSize: asBaseMap.tile_size
				};
				asLayers.push({
					id: asBaseMap.codename,
					type: 'raster',
					source: asBaseMap.codename,
					'layout': {'visibility': 'none'},
					minZoom: asBaseMap.min_zoom,
					maxZoom: asBaseMap.max_zoom
				});
			}

			//Map
			if(this.map) this.map.remove();
			this.map = new Map({
				container: 'map',
				style: {
					version: 8,
					sources: asSources,
					layers: asLayers
				},
				attributionControl: false
			});

			this.map.once('load', async () => {
				//Default Basemap
				this.baseMap = this.baseMaps.filter((asBM) => asBM.default_map)[0].codename;
				
				//Get track
				const oTrack = await this.spot.get2('geojson', {id_project: this.currProject.id});
				this.map.addSource('track', {
					'type': 'geojson',
					'data': oTrack
				});

				//Color mapping
				let asColorMapping = ['match', ['get', 'type']];
				for(const sHikeType in this.hikes.colors) {
					asColorMapping.push(sHikeType);
					asColorMapping.push(this.hikes.colors[sHikeType]);
				}
				asColorMapping.push('black'); //fallback value

				//Track layer
				this.map.addLayer({
					'id': 'track',
					'type': 'line',
					'source': 'track',
					'layout': {
						'line-join': 'round',
						'line-cap': 'round'
					},
					'paint': {
						'line-color': asColorMapping,
						'line-width': this.hikes.width
					}
				});

				//Markers
				this.map.addImage('markerIcon', (await this.map.loadImage('images/footprint_mapbox.png')).data);
				this.map.addSource('markers', {
					type:'geojson',
					data: {
						type: 'FeatureCollection',
						features: this.convertMsgToFeatures(this.messages)
					}
				});
				this.map.addLayer({
					'id': 'markers',
					'type': 'symbol',
					'source': 'markers',
					'layout': {'icon-image': 'markerIcon'}
				});
				this.map.on('click', 'markers', (e) => {
					this.openMarkerPopup(e.features[0]);
				});

				//Centering map
				let bOpenFeedPanel = !this.mobile;
				let oBounds = new LngLatBounds();
				if(
					this.currProject.mode == this.spot.consts.modes.blog && 
					this.messages.length > 0 &&
					this.$parent.hash.items[2] != 'message'
				) {
					//Fit to last message
					let oLastMsg = this.messages[this.messages.length - 1];
					oBounds.extend(new LngLat(oLastMsg.longitude, oLastMsg.latitude));
				}
				else {
					//Fit to track
					for(const iFeatureId in oTrack.features) {
						oBounds = oTrack.features[iFeatureId].geometry.coordinates.reduce(
							(bounds, coord) => {
								return bounds.extend(coord);
							},
							oBounds
						);
					}
				}
				const iFeedPanelPadding = bOpenFeedPanel?(getOuterWidth(this.$refs.feed)/2):0;
				await this.map.fitBounds(
					oBounds, 
					{
						padding: {
							top: 20, 
							bottom: 20, 
							left: (20 + iFeedPanelPadding), 
							right: (20 + iFeedPanelPadding)
						},
						animate: false,
						maxZoom: 15
					}
				);

				//Toggle only when map is ready, for the tilt effet
				this.toggleFeedPanel(bOpenFeedPanel);
			});

			this.map.on('idle', () => {	});

			//Legend
		},
		convertMsgToFeatures(oMsg) {
			return oMsg.map(oMsg => ({
				'type': 'Feature',
				'geometry': {
					'type': 'Point',
					'coordinates': [oMsg.longitude, oMsg.latitude]
				},
				'properties': { 
					...oMsg,
					...{'description': ''}
				}
			}));
		},
		async getNextFeed() {
			if(!this.feed.outOfData && !this.feed.loading) {
				//Get next chunk
				this.feed.loading = true;
				let aoData = await this.spot.get2('next_feed', {id_project: this.currProject.id, id: this.feed.refIdLast});
				let iPostCount = Object.keys(aoData.feed).length;
				
				//Update pointers
				this.feed.outOfData = (iPostCount < this.spot.consts.chunk_size);
				if(iPostCount > 0) {
					this.feed.refIdLast = aoData.ref_id_last;
					if(this.feed.firstChunk) this.feed.refIdFirst = aoData.ref_id_first;
				}
				
				//Add posts
				this.posts.push(...aoData.feed);

				this.feed.loading = false;
				this.feed.firstChunk = false;
			}

			return true;
		},
		onFeedScroll(oEvent) {
			//FIXME remove jquery dependency
			var $Box = $(oEvent.currentTarget);
			var $BoxContent = $Box.find('.simplebar-content');
			if(($Box.scrollTop() + $(window).height()) / $BoxContent.height() >= 0.8) this.getNextFeed();
		},
		setFeedUpdateTimer(iSeconds) {
			if(typeof this.feedTimer != 'undefined') clearTimeout(this.feedTimer);
			if(iSeconds >= 0) this.feedTimer = setTimeout(this.checkNewFeed, iSeconds * 1000);
		},
		async checkNewFeed() {
			let aoData = await this.spot.get2('new_feed', {id_project: this.currProject.id, id: this.feed.refIdFirst});
			
			if(Object.keys(aoData.feed).length > 0) {
				//Update pointer
				this.feed.refIdFirst = aoData.ref_id_first;
				
				//Add new posts
				this.posts.unshift(...aoData.feed);
			}
					
			//Add new Markers
			if(Object.keys(aoData.messages).length > 0) {
				const oMarkerSource = this.map.getSource('markers');
				oMarkerSource.setData({
					type: 'FeatureCollection',
					features: [...oMarkerSource._data.features, ...this.convertMsgToFeatures(aoData.messages)]
				});
			}

			//Message Last Update
			this.lastUpdate = aoData.last_update;

			//Reschedule
			this.setFeedUpdateTimer(this.refreshRate);
		},
		openMarkerPopup(oFeature) {
			this.closeMarkerPopup();

			//Convert ID Message to feature
			if(typeof oFeature == 'number') {
				const oMatchingFeatures = this.map.querySourceFeatures('markers', {
					filter: ['==', ['get', 'id_message'], oFeature]
				});

				if(!oMatchingFeatures.length) {
					console.warn('Marker not found: ', oFeature);
					return;
				}
				else oFeature = oMatchingFeatures[0];
			}

			const $Popup = document.createElement('div');
			this.popup.element = new Popup({
					anchor: 'bottom',
					offset: [0, this.markerSize.height * -1],
					closeButton: false
				})
				.setDOMContent($Popup)
				.setLngLat(oFeature.geometry.coordinates)
				.setMaxWidth(300)
				.addTo(this.map);

			const rProp = ref(oFeature.properties);

			const vPopup = defineComponent({
				extends: ProjectPopup,
				setup: () => {
					provide('spot', this.spot);
					return {
						options: rProp.value,
						medias: JSON.parse(rProp.value.medias || '""'),
						spot: this.spot,
						project: this.currProject
					};
				}
			});

			nextTick(() => {
				this.popup.content = createApp(vPopup);
				this.popup.content.mount($Popup);
			});
		},
		closeMarkerPopup() {
			if(this.popup.content) {
				this.popup.content.unmount();
				this.popup.content = null;
			}
			if(this.popup.element) {
				this.popup.element.remove();
				this.popup.element = null;
			}
		},
		panToBetweenPanels(oLngLat, iZoom, fCallback) {
			const iXOffset = (this.settingsPanelOpen?getOuterWidth(this.$refs.settings):0) - (this.feedPanelOpen?getOuterWidth(this.$refs.feed):0);
			this.map.easeTo({
				center: oLngLat,
				zoom: iZoom,
				offset: [iXOffset / 2, 0],
				duration: 500
			});
			setTimeout(fCallback, 500);
		},
		isMarkerVisible(oLngLat){
			return this.map.getBounds().contains(oLngLat);
		},
		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);
		},
		async manageSubs() {
			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(this.user.email)) this.nlFeedbacks.push({type:'error', 'msg':this.spot.lang('nl_invalid_email')});
			else {
				this.spot.get2(this.nlAction, {'email': this.user.email, 'name': this.user.name}, this.nlLoading)
					.then((asUser, sDesc) => {
						this.nlFeedbacks.push('success', sDesc);
						this.user = asUser;
					})
					.catch((sDesc) => {this.nlFeedbacks.push('error', sDesc);});
			}
		},
		toggleFeedPanel(bShow, sMapAction) {
			let bOldValue = this.feedPanelOpen;
			this.feedPanelOpen = (typeof bShow === 'object' || typeof bShow === 'undefined')?(!this.feedPanelOpen):bShow;

			if(bOldValue != this.feedPanelOpen && !this.mobile) {
				this.spot.onResize();

				sMapAction = sMapAction || 'panTo';
				switch(sMapAction) {
					case 'none': 
						break;
					case 'panTo':
						this.map.panBy(
							[(this.feedPanelOpen?1:-1) * getOuterWidth(this.$refs.feed) / 2, 0],
							{duration: 500}
						);
						break;
					case 'panToInstant':
						this.map.panBy([(this.feedPanelOpen?1:-1) * getOuterWidth(this.$refs.feed) / 2, 0]);
						break;
					case 'fitBounds':
						/*
						this.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;
						*/
				}
			}
		},
		toggleSettingsPanel(bShow, sMapAction) {
			let bOldValue = this.settingsPanelOpen;
			this.settingsPanelOpen = (typeof bShow === 'object' || typeof bShow === 'undefined')?(!this.settingsPanelOpen):bShow;
			
			if(bOldValue != this.settingsPanelOpen && !this.mobile) {
				this.spot.onResize();

				sMapAction = sMapAction || 'panTo';
				switch(sMapAction) {
					case 'none':
						break;
					case 'panTo':
						this.map.panBy(
							[(this.settingsPanelOpen?-1:1) * getOuterWidth(this.$refs.settings) / 2, 0],
							{duration: 500}
						);
						break;
					case 'panToInstant':
						this.map.panBy([(this.settingsPanelOpen?-1:1) * getOuterWidth(this.$refs.settings) /2, 0]);
						break;
				}
			}
		},
		async findPost(oPost) {
			if(this.goToPost(oPost)) {
				//if(oPost.type=='media' || oPost.type=='message') $Post.find('a.drill').click();
			}
			else if(!this.feed.outOfData) {
				await this.getNextFeed();
				this.findPost(oPost);
			}
			else console.log('Missing element ID "'+oPost.id+'" of type "'+oPost.type+'"');
		},
		goToPost(oPost) {
			//TODO remove jquery deps
			let bFound = false;
			let aoRefs = this.$refs.posts.filter((post)=>{return post.postId == oPost.type+'-'+oPost.id;});
			if(aoRefs.length == 1) {
				this.$refs.feedSimpleBar.scrollElement.scrollTop += Math.round(
					$(aoRefs[0].$el).offset().top
					- parseInt($(this.$refs.feedSimpleBar.$el).css('padding-top'))
				);
				bFound = true;
				this.spot.flushHash(['post', 'message']);
			}

			return bFound;
		}
	}
}
</script>

<template>
	<div id="projects" :class="projectClasses">
		<div id="background"></div>
		<div id="submap">
			<div class="loader fa fa-fw fa-map flicker"></div>
		</div>
		<div id="map"></div>
		<div id="settings" class="map-container map-container-left" ref="settings">
			<div id="settings-panel" class="map-panel">
				<div class="settings-header">
					<div class="logo"><img width="289" height="72" src="images/logo_black.png" alt="Spotty" /></div>
					<div id="last_update" v-if="this.currProject.mode == this.spot.consts.modes.blog && lastUpdate.unix_time > 0">
						<p><span><img src="images/spot-logo-only.svg" alt="" /></span><abbr :title="lastUpdate.formatted_time">{{ spot.lang('last_update')+' '+lastUpdate.relative_time }}</abbr></p>
					</div>
				</div>
				<div class="settings-sections">
					<simplebar id="settings-sections-scrollbox">
						<div class="settings-section">
							<h1><SpotIcon :icon="'project'" :classes="'fa-fw'" :text="spot.lang('hikes')" /></h1>
							<div class="settings-section-body">
								<div class="radio" v-for="project in projects">
									<input type="radio" :id="project.id" :value="project.codename" v-model="currProjectCodeName" />
									<label :for="project.id">
										<span>{{ project.name }}</span>
										<a class="download" :href="project.gpxfilepath" :title="spot.lang('track_download')" @click.stop="()=>{}">
											<SpotIcon :icon="'download'" :classes="'push-left'" />
										</a>
									</label>
								</div>
							</div>
						</div>
						<div class="settings-section">
							<h1><SpotIcon :icon="'map'" :classes="'fa-fw'" :text="spot.lang('maps')" /></h1>
							<div class="settings-section-body">
								<div class="radio" v-for="bm in baseMaps">
									<input type="radio" :id="bm.id_map" :value="bm.codename" v-model="baseMap" />
									<label :for="bm.id_map">{{ this.spot.lang('map_'+bm.codename) }}</label>
								</div>
							</div>
						</div>
						<div class="settings-section newsletter">
							<h1><SpotIcon :icon="'newsletter'" :classes="'fa-fw'" :text="spot.lang('newsletter')" /></h1>
							<input type="email" name="email" id="email" :placeholder="spot.lang('nl_email_placeholder')" v-model="user.email" :disabled="nlLoading || subscribed" />
							<SpotButton id="nl_btn" :classes="nlClasses" :title="spot.lang('nl_'+nlAction)" @click="manageSubs" />
							<div id="settings-feedback" class="feedback">
								<p v-for="feedback in nlFeedbacks" :class="feedback.type">
									<SpotIcon :icon="feedback.type" :text="feedback.msg" />
								</p>
							</div>
							{{ spot.lang(subscribed?'nl_subscribed_desc':'nl_unsubscribed_desc') }}
						</div>
						<div class="settings-section admin" v-if="spot.checkClearance(spot.consts.clearances.admin)">
							<h1><SpotIcon :icon="'admin fa-fw'" :text="spot.lang('admin')" /></h1>
							<a class="button" href="#admin"><SpotIcon :icon="'config'" :text="spot.lang('admin_config')" /></a>
							<a class="button" href="#upload"><SpotIcon :icon="'upload'" :text="spot.lang('admin_upload')" /></a>
						</div>
					</simplebar>
				</div>
				<div class="settings-footer">
					<a href="https://git.lutran.fr/franzz/spot" :title="spot.lang('credits_git')" target="_blank" rel="noopener">
						<SpotIcon :icon="'credits'" :text="spot.lang('credits_project')" />
					</a> {{ spot.lang('credits_license') }}</div>
			</div>
			<div :class="'map-control map-control-icon settings-control map-control-'+(mobile?'bottom':'top')" @click="toggleSettingsPanel">
				<SpotIcon :icon="settingsPanelOpen?'prev':'menu'" />
			</div>
			<div v-if="!mobile" id="legend" class="map-control settings-control map-control-bottom">
				<div v-for="(color, hikeType) in hikes.colors" class="track">
					<span class="line" :style="'background-color:'+color+'; height:'+hikes.width+'px;'"></span>
					<span class="desc">{{ spot.lang('track_'+hikeType) }}</span>
				</div>
			</div>
			<div id="title" :class="'map-control settings-control map-control-'+(mobile?'bottom':'top')">
				<span>{{ currProject.name }}</span>
			</div>
		</div>
		<div id="feed" class="map-container map-container-right" ref="feed">
			<simplebar id="feed-panel" class="map-panel" ref="feedSimpleBar">
				<div id="feed-header">
					<ProjectPost v-if="modeHisto" :options="{type: 'archived', headerless: true}" />
					<ProjectPost v-else :options="{type: 'poster', relative_time: spot.lang('post_new_message')}" />
				</div>
				<div id="feed-posts">
					<ProjectPost v-for="post in posts" :options="post" ref="posts" />
				</div>
				<div id="feed-footer" v-if="feed.loading">
					<ProjectPost :options="{type: 'loading', headerless: true}" />
				</div>
			</simplebar>
			<div :class="'map-control map-control-icon feed-control map-control-'+(mobile?'bottom':'top')" @click="toggleFeedPanel">
				<SpotIcon :icon="feedPanelOpen?'next':'post'" />
			</div>
		</div>
	</div>
  </template>