<!--
  - @copyright Copyright (c) 2020 Julien Veyssier <eneiluj@posteo.net>
  - @author Julien Veyssier <eneiluj@posteo.net>
  -
  - @license GNU AGPL version 3 or any later version
  -
  - This program is free software: you can redistribute it and/or modify
  - it under the terms of the GNU Affero General Public License as
  - published by the Free Software Foundation, either version 3 of the
  - License, or (at your option) any later version.
  -
  - This program is distributed in the hope that it will be useful,
  - but WITHOUT ANY WARRANTY; without even the implied warranty of
  - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  - GNU Affero General Public License for more details.
  -
  - You should have received a copy of the GNU Affero General Public License
  - along with this program. If not, see <http://www.gnu.org/licenses/>.
  -
  -->

<template>
	<li :class="{ inline }">
		<div id="weather-status-menu-item">
			<NcActions class="weather-status-menu-item__subheader"
				:default-icon="weatherIcon"
				:aria-hidden="true"
				:aria-label="currentWeatherMessage"
				:menu-title="currentWeatherMessage">
				<NcActionText v-if="gotWeather"
					:aria-hidden="true"
					:icon="futureWeatherIcon">
					{{ forecastMessage }}
				</NcActionText>
				<NcActionLink v-if="gotWeather"
					icon="icon-address"
					target="_blank"
					:aria-hidden="true"
					:href="weatherLinkTarget"
					:close-after-click="true">
					{{ locationText }}
				</NcActionLink>
				<NcActionButton v-if="gotWeather"
					:icon="addRemoveFavoriteIcon"
					:aria-hidden="true"
					@click="onAddRemoveFavoriteClick">
					{{ addRemoveFavoriteText }}
				</NcActionButton>
				<NcActionSeparator v-if="address && !errorMessage" />
				<NcActionButton icon="icon-crosshair"
					:close-after-click="true"
					:aria-hidden="true"
					@click="onBrowserLocationClick">
					{{ t('weather_status', 'Detect location') }}
				</NcActionButton>
				<NcActionInput ref="addressInput"
					:disabled="false"
					icon="icon-rename"
					:aria-hidden="true"
					type="text"
					value=""
					@submit="onAddressSubmit">
					{{ t('weather_status', 'Set custom address') }}
				</NcActionInput>
				<NcActionButton v-show="favorites.length > 0"
					:icon="toggleFavoritesIcon"
					:aria-hidden="true"
					@click="showFavorites = !showFavorites">
					{{ t('weather_status', 'Favorites') }}
				</NcActionButton>
				<NcActionButton v-for="f in displayedFavorites"
					:key="f"
					icon="icon-starred"
					:aria-hidden="true"
					@click="onFavoriteClick($event, f)">
					{{ f }}
				</NcActionButton>
			</NcActions>
		</div>
	</li>
</template>

<script>
import { showError } from '@nextcloud/dialogs'
import moment from '@nextcloud/moment'
import { getLocale } from '@nextcloud/l10n'
import NcActions from '@nextcloud/vue/dist/Components/NcActions.js'
import NcActionButton from '@nextcloud/vue/dist/Components/NcActionButton.js'
import NcActionInput from '@nextcloud/vue/dist/Components/NcActionInput.js'
import NcActionLink from '@nextcloud/vue/dist/Components/NcActionLink.js'
import NcActionSeparator from '@nextcloud/vue/dist/Components/NcActionSeparator.js'
import NcActionText from '@nextcloud/vue/dist/Components/NcActionText.js'
import * as network from './services/weatherStatusService.js'

const MODE_BROWSER_LOCATION = 1
const MODE_MANUAL_LOCATION = 2
const weatherOptions = {
	clearsky_day: {
		icon: 'icon-clearsky-day',
		text: (temperature, unit, later = false) => later
			? t('weather_status', '{temperature} {unit} clear sky later today', { temperature, unit })
			: t('weather_status', '{temperature} {unit} clear sky', { temperature, unit }),
	},
	clearsky_night: {
		icon: 'icon-clearsky-night',
		text: (temperature, unit, later = false) => later
			? t('weather_status', '{temperature} {unit} clear sky later today', { temperature, unit })
			: t('weather_status', '{temperature} {unit} clear sky', { temperature, unit }),
	},
	cloudy: {
		icon: 'icon-cloudy',
		text: (temperature, unit, later = false) => later
			? t('weather_status', '{temperature} {unit} cloudy later today', { temperature, unit })
			: t('weather_status', '{temperature} {unit} cloudy', { temperature, unit }),
	},
	fair_day: {
		icon: 'icon-fair-day',
		text: (temperature, unit, later = false) => later
			? t('weather_status', '{temperature} {unit} fair weather later today', { temperature, unit })
			: t('weather_status', '{temperature} {unit} fair weather', { temperature, unit }),
	},
	fair_night: {
		icon: 'icon-fair-night',
		text: (temperature, unit, later = false) => later
			? t('weather_status', '{temperature} {unit} fair weather later today', { temperature, unit })
			: t('weather_status', '{temperature} {unit} fair weather', { temperature, unit }),
	},
	partlycloudy_day: {
		icon: 'icon-partlycloudy-day',
		text: (temperature, unit, later = false) => later
			? t('weather_status', '{temperature} {unit} partly cloudy later today', { temperature, unit })
			: t('weather_status', '{temperature} {unit} partly cloudy', { temperature, unit }),
	},
	partlycloudy_night: {
		icon: 'icon-partlycloudy-night',
		text: (temperature, unit, later = false) => later
			? t('weather_status', '{temperature} {unit} partly cloudy later today', { temperature, unit })
			: t('weather_status', '{temperature} {unit} partly cloudy', { temperature, unit }),
	},
	fog: {
		icon: 'icon-fog',
		text: (temperature, unit, later = false) => later
			? t('weather_status', '{temperature} {unit} foggy later today', { temperature, unit })
			: t('weather_status', '{temperature} {unit} foggy', { temperature, unit }),
	},
	lightrain: {
		icon: 'icon-lightrain',
		text: (temperature, unit, later = false) => later
			? t('weather_status', '{temperature} {unit} light rainfall later today', { temperature, unit })
			: t('weather_status', '{temperature} {unit} light rainfall', { temperature, unit }),
	},
	rain: {
		icon: 'icon-rain',
		text: (temperature, unit, later = false) => later
			? t('weather_status', '{temperature} {unit} rainfall later today', { temperature, unit })
			: t('weather_status', '{temperature} {unit} rainfall', { temperature, unit }),
	},
	heavyrain: {
		icon: 'icon-heavyrain',
		text: (temperature, unit, later = false) => later
			? t('weather_status', '{temperature} {unit} heavy rainfall later today', { temperature, unit })
			: t('weather_status', '{temperature} {unit} heavy rainfall', { temperature, unit }),
	},
	rainshowers_day: {
		icon: 'icon-rainshowers-day',
		text: (temperature, unit, later = false) => later
			? t('weather_status', '{temperature} {unit} rainfall showers later today', { temperature, unit })
			: t('weather_status', '{temperature} {unit} rainfall showers', { temperature, unit }),
	},
	rainshowers_night: {
		icon: 'icon-rainshowers-night',
		text: (temperature, unit, later = false) => later
			? t('weather_status', '{temperature} {unit} rainfall showers later today', { temperature, unit })
			: t('weather_status', '{temperature} {unit} rainfall showers', { temperature, unit }),
	},
	lightrainshowers_day: {
		icon: 'icon-light-rainshowers-day',
		text: (temperature, unit, later = false) => later
			? t('weather_status', '{temperature} {unit} light rainfall showers later today', { temperature, unit })
			: t('weather_status', '{temperature} {unit} light rainfall showers', { temperature, unit }),
	},
	lightrainshowers_night: {
		icon: 'icon-light-rainshowers-night',
		text: (temperature, unit, later = false) => later
			? t('weather_status', '{temperature} {unit} light rainfall showers later today', { temperature, unit })
			: t('weather_status', '{temperature} {unit} light rainfall showers', { temperature, unit }),
	},
	heavyrainshowers_day: {
		icon: 'icon-heavy-rainshowers-day',
		text: (temperature, unit, later = false) => later
			? t('weather_status', '{temperature} {unit} heavy rainfall showers later today', { temperature, unit })
			: t('weather_status', '{temperature} {unit} heavy rainfall showers', { temperature, unit }),
	},
	heavyrainshowers_night: {
		icon: 'icon-heavy-rainshowers-night',
		text: (temperature, unit, later = false) => later
			? t('weather_status', '{temperature} {unit} heavy rainfall showers later today', { temperature, unit })
			: t('weather_status', '{temperature} {unit} heavy rainfall showers', { temperature, unit }),
	},
}

export default {
	name: 'App',
	components: {
		NcActions,
		NcActionButton,
		NcActionInput,
		NcActionLink,
		NcActionSeparator,
		NcActionText,
	},
	props: {
		inline: {
			type: Boolean,
			default: false,
		},
	},
	data() {
		return {
			locale: getLocale(),
			loading: true,
			errorMessage: '',
			mode: MODE_BROWSER_LOCATION,
			address: null,
			lat: null,
			lon: null,
			// how many hours ahead do we want to see the forecast?
			offset: 5,
			forecasts: [],
			loop: null,
			favorites: [],
			showFavorites: false,
		}
	},
	computed: {
		useFahrenheitLocale() {
			return ['en_US', 'en_MH', 'en_FM', 'en_PW', 'en_KY', 'en_LR'].includes(this.locale)
		},
		temperatureUnit() {
			return this.useFahrenheitLocale ? '°F' : '°C'
		},
		locationText() {
			return t('weather_status', 'More weather for {adr}', { adr: this.address })
		},
		temperature() {
			return this.getTemperature(this.forecasts, 0)
		},
		futureTemperature() {
			return this.getTemperature(this.forecasts, this.offset)
		},
		weatherCode() {
			return this.getWeatherCode(this.forecasts, 0)
		},
		futureWeatherCode() {
			return this.getWeatherCode(this.forecasts, this.offset)
		},
		weatherIcon() {
			return this.getWeatherIcon(this.weatherCode, this.loading)
		},
		futureWeatherIcon() {
			return this.getWeatherIcon(this.futureWeatherCode, this.loading)
		},
		/**
		 * The message displayed in the top right corner
		 *
		 * @return {string}
		 */
		currentWeatherMessage() {
			if (this.loading) {
				return t('weather_status', 'Loading weather')
			} else if (this.errorMessage) {
				return this.errorMessage
			} else {
				return this.getWeatherMessage(this.weatherCode, this.temperature)
			}
		},
		forecastMessage() {
			if (this.loading) {
				return t('weather_status', 'Loading weather')
			} else {
				return this.getWeatherMessage(this.futureWeatherCode, this.futureTemperature, true)
			}
		},
		weatherLinkTarget() {
			return 'https://www.windy.com/-Rain-thunder-rain?rain,' + this.lat + ',' + this.lon + ',11'
		},
		gotWeather() {
			return this.address && !this.errorMessage
		},
		addRemoveFavoriteIcon() {
			return this.currentAddressIsFavorite
				? 'icon-starred'
				: 'icon-star'
		},
		addRemoveFavoriteText() {
			return this.currentAddressIsFavorite
				? t('weather_status', 'Remove from favorites')
				: t('weather_status', 'Add as favorite')
		},
		currentAddressIsFavorite() {
			return this.favorites.find((f) => {
				return f === this.address
			})
		},
		toggleFavoritesIcon() {
			return this.showFavorites
				? 'icon-triangle-s'
				: 'icon-triangle-e'
		},
		displayedFavorites() {
			return this.showFavorites
				? this.favorites
				: []
		},
	},
	mounted() {
		this.initWeatherStatus()
	},
	methods: {
		async initWeatherStatus() {
			try {
				const loc = await network.getLocation()
				this.lat = loc.lat
				this.lon = loc.lon
				this.address = loc.address
				this.mode = loc.mode

				if (this.mode === MODE_BROWSER_LOCATION) {
					this.askBrowserLocation()
				} else if (this.mode === MODE_MANUAL_LOCATION) {
					this.startLoop()
				}
				const favs = await network.getFavorites()
				this.favorites = favs
			} catch (err) {
				if (err?.code === 'ECONNABORTED') {
					console.info('The weather status request was cancelled because the user navigates.')
					return
				}
				if (err.response && err.response.status === 401) {
					showError(t('weather_status', 'You are not logged in.'))
				} else {
					showError(t('weather_status', 'There was an error getting the weather status information.'))
				}
				console.error(err)
			}
		},
		startLoop() {
			clearInterval(this.loop)
			if (this.lat && this.lon) {
				this.loop = setInterval(() => this.getForecast(), 60 * 1000 * 60)
				this.getForecast()
			} else {
				this.loading = false
			}
		},
		askBrowserLocation() {
			this.loading = true
			this.errorMessage = ''
			if (navigator.geolocation && window.isSecureContext) {
				navigator.geolocation.getCurrentPosition((position) => {
					console.debug('browser location success')
					this.lat = position.coords.latitude
					this.lon = position.coords.longitude
					this.saveMode(MODE_BROWSER_LOCATION)
					this.mode = MODE_BROWSER_LOCATION
					this.saveLocation(this.lat, this.lon)
				},
				(error) => {
					console.debug('location permission refused')
					console.debug(error)
					this.saveMode(MODE_MANUAL_LOCATION)
					this.mode = MODE_MANUAL_LOCATION
					// fallback on what we have if possible
					if (this.lat && this.lon) {
						this.startLoop()
					} else {
						this.usePersonalAddress()
					}
				})
			} else {
				console.debug('no secure context!')
				this.saveMode(MODE_MANUAL_LOCATION)
				this.mode = MODE_MANUAL_LOCATION
				this.startLoop()
			}
		},
		async getForecast() {
			try {
				this.forecasts = await network.fetchForecast()
			} catch (err) {
				this.errorMessage = t('weather_status', 'No weather information found')
				console.debug(err)
			}
			this.loading = false
		},
		async setAddress(address) {
			this.loading = true
			this.errorMessage = ''
			try {
				const loc = await network.setAddress(address)
				if (loc.success) {
					this.lat = loc.lat
					this.lon = loc.lon
					this.address = loc.address
					this.mode = MODE_MANUAL_LOCATION
					this.startLoop()
				} else {
					this.errorMessage = t('weather_status', 'Location not found')
					this.loading = false
				}
			} catch (err) {
				if (err.response && err.response.status === 401) {
					showError(t('weather_status', 'You are not logged in.'))
				} else {
					showError(t('weather_status', 'There was an error setting the location address.'))
				}
				this.loading = false
			}
		},
		async saveLocation(lat, lon) {
			try {
				const loc = await network.setLocation(lat, lon)
				this.address = loc.address
				this.startLoop()
			} catch (err) {
				if (err.response && err.response.status === 401) {
					showError(t('weather_status', 'You are not logged in.'))
				} else {
					showError(t('weather_status', 'There was an error setting the location.'))
				}
				console.debug(err)
			}
		},
		async saveMode(mode) {
			try {
				await network.setMode(mode)
			} catch (err) {
				if (err.response && err.response.status === 401) {
					showError(t('weather_status', 'You are not logged in.'))
				} else {
					showError(t('weather_status', 'There was an error saving the mode.'))
				}
				console.debug(err)
			}
		},
		onBrowserLocationClick() {
			this.askBrowserLocation()
		},
		async usePersonalAddress() {
			this.loading = true
			try {
				const loc = await network.usePersonalAddress()
				this.lat = loc.lat
				this.lon = loc.lon
				this.address = loc.address
				this.mode = MODE_MANUAL_LOCATION
				this.startLoop()
			} catch (err) {
				if (err.response && err.response.status === 401) {
					showError(t('weather_status', 'You are not logged in.'))
				} else {
					showError(t('weather_status', 'There was an error using personal address.'))
				}
				console.debug(err)
				this.loading = false
			}
		},
		onAddressSubmit() {
			const newAddress = this.$refs.addressInput.$el.querySelector('input[type="text"]').value
			this.setAddress(newAddress)
		},
		getLocalizedTemperature(celcius) {
			return this.useFahrenheitLocale
				? (celcius * (9 / 5)) + 32
				: celcius
		},
		onAddRemoveFavoriteClick() {
			const currentIsFavorite = this.currentAddressIsFavorite
			if (currentIsFavorite) {
				const i = this.favorites.indexOf(currentIsFavorite)
				if (i !== -1) {
					this.favorites.splice(i, 1)
				}
			} else {
				this.favorites.push(this.address)
			}
			network.saveFavorites(this.favorites)
		},
		onFavoriteClick(e, favAddress) {
			// clicked on the icon
			if (e.target.classList.contains('action-button__icon')) {
				const i = this.favorites.indexOf(favAddress)
				if (i !== -1) {
					this.favorites.splice(i, 1)
				}
				network.saveFavorites(this.favorites)
			} else if (favAddress !== this.address) {
				// clicked on the text
				this.setAddress(favAddress)
			}
		},
		formatTime(time) {
			return moment(time).format('LT')
		},
		getTemperature(forecasts, offset = 0) {
			return forecasts.length > offset ? forecasts[offset].data.instant.details.air_temperature : ''
		},
		getWeatherCode(forecasts, offset = 0) {
			return forecasts.length > offset ? forecasts[offset].data.next_1_hours.summary.symbol_code : ''
		},
		getWeatherIcon(weatherCode, loading) {
			if (loading) {
				return 'icon-loading-small'
			} else {
				return 'icon-weather ' + (weatherCode && weatherCode in weatherOptions
					? weatherOptions[weatherCode].icon
					: 'icon-fair-day')
			}
		},
		getWeatherMessage(weatherCode, temperature, later = false) {
			return weatherCode && weatherCode in weatherOptions
				? weatherOptions[weatherCode].text(
					Math.round(this.getLocalizedTemperature(temperature)),
					this.temperatureUnit,
					later
				)
				: t('weather_status', 'Set location for weather')
		},
	},
}
</script>

<style lang="scss">
.icon-weather {
	background-size: 16px;
}
.icon-weather-status {
	background-image: url('./../img/app-dark.svg');
}
.icon-clearsky-day {
	background-image: url('./../img/sun.svg');
}
.icon-clearsky-night {
	background-image: url('./../img/moon.svg');
}
.icon-cloudy {
	background-image: url('./../img/cloud-cloud.svg');
}
.icon-fair-day {
	background-image: url('./../img/sun-small-cloud.svg');
}
.icon-fair-night {
	background-image: url('./../img/moon-small-cloud.svg');
}
.icon-partlycloudy-day {
	background-image: url('./../img/sun-cloud.svg');
}
.icon-partlycloudy-night {
	background-image: url('./../img/moon-cloud.svg');
}
.icon-fog {
	background-image: url('./../img/fog.svg');
}
.icon-lightrain {
	background-image: url('./../img/light-rain.svg');
}
.icon-rain {
	background-image: url('./../img/rain.svg');
}
.icon-heavyrain {
	background-image: url('./../img/heavy-rain.svg');
}
.icon-light-rainshowers-day {
	background-image: url('./../img/sun-cloud-light-rain.svg');
}
.icon-light-rainshowers-night {
	background-image: url('./../img/moon-cloud-light-rain.svg');
}
.icon-rainshowers-day {
	background-image: url('./../img/sun-cloud-rain.svg');
}
.icon-rainshowers-night {
	background-image: url('./../img/moon-cloud-rain.svg');
}
.icon-heavy-rainshowers-day {
	background-image: url('./../img/sun-cloud-heavy-rain.svg');
}
.icon-heavy-rainshowers-night {
	background-image: url('./../img/moon-cloud-heavy-rain.svg');
}
.icon-crosshair {
    background-color: var(--color-main-text);
    padding: 0 !important;
    mask: url(./../img/cross.svg) no-repeat;
    mask-size: 18px 18px;
    mask-position: center;
    -webkit-mask: url(./../img/cross.svg) no-repeat;
    -webkit-mask-size: 18px 18px;
    -webkit-mask-position: center;
    min-width: 44px !important;
    min-height: 44px !important;
}

li:not(.inline) .weather-status-menu-item {
	&__header {
		display: block;
		align-items: center;
		color: var(--color-main-text);
		padding: 10px 12px 5px 12px;
		box-sizing: border-box;
		opacity: 1;
		white-space: nowrap;
		width: 100%;
		text-align: center;
		max-width: 250px;
		text-overflow: ellipsis;
		min-width: 175px;
	}

	&__subheader {
		width: 100%;

		.trigger > .icon {
			background-color: var(--color-main-background);
			background-size: 16px;
			border: 0;
			border-radius: 0;
			font-weight: normal;
			padding-left: 40px;

			&:hover,
			&:focus {
				box-shadow: inset 4px 0 var(--color-primary-element);
			}
		}
	}
}

.inline .weather-status-menu-item__subheader {
	width: 100%;

	.trigger > .icon {
		background-size: 16px;
		border: 0;
		border-radius: var(--border-radius-pill);
		font-weight: normal;
		padding-left: 40px;

		&.icon-loading-small {
			&::after {
				left: 21px;
			}
		}
	}
}

li {
	list-style-type: none;
}
</style>