<template>
<div :class="wrapClass">
    <label v-if="mega">{{title}}<span class="rfo" v-if="rfo">*</span><span class="rfq" v-if="rfq">*</span><span class="aster" v-if="aster">*</span></label>
    <input :id="id" :class="hasError && !mega ? 'invalid' : 'text'" :type="inline ? 'hidden' : 'text'" :value="formattedValue" v-if="editable" :placeholder="placeholder" @click="showCalendar" readonly>
    <Icon v-if="editable && formattedValue == format" :icon="['fabt', 'calendar']" class="icon" @click.stop="showCalendar"/>
    <Icon v-if="editable && formattedValue != format && !hideClearButton" :icon="['fabt', 'times']" class="icon" @click.stop="setValue(null)"/>
    <span v-if="!editable" class="noneditable">{{value}}</span>
    <span v-show="editable && hasError" class="vldtn-error" v-html="message"></span>
    <!-- Day View -->
    <div class="calendar" v-if="showDayView" :style="calendarStyle">
        <header>
            <span @click="previousMonth" class="prev" :class="{ 'disabled' : previousMonthDisabled(currDate) }"></span>
            <span @click="showMonthCalendar" class="up">{{ currMonthName }} {{ currYear }}</span>
            <span @click="nextMonth" class="next" :class="{ 'disabled' : nextMonthDisabled(currDate) }"></span>
        </header>
        <div class="blocks day">
            <span class="cell day-header" v-for="d in translation.days" :key="'TDP'+d">{{ d }}</span>
            <span class="cell day blank" v-for="d in blankDays" :key="'BDP'+d"></span>
            <span class="cell day" v-for="day in days" track-by="timestamp" :class="{ 'selected':day.isSelected, 'disabled':day.isDisabled, 'current':day.isCurrent }" @click="selectDate(day)" :key="day.date">{{ day.date }}</span>
        </div>
    </div>
    <!-- Month View -->
    <div class="calendar" v-if="showMonthView" :style="calendarStyleSecondary">
        <header>
            <span @click="previousYear" class="prev" :class="{ 'disabled' : previousYearDisabled(currDate) }"></span>
            <span @click="showYearCalendar" class="up">{{ getYear() }}</span>
            <span @click="nextYear" class="next" :class="{ 'disabled' : nextYearDisabled(currDate) }"></span>
        </header>
        <div class="blocks month">
            <span class="cell month" v-for="month in months" track-by="timestamp" :class="{ 'selected': month.isSelected, 'disabled': month.isDisabled, 'current':month.isCurrent }" @click.stop="selectMonth(month)" :key="'M'+month.month">{{ month.month }}</span>
        </div>
    </div>
    <!-- Year View -->
    <div class="calendar" v-if="showYearView" :style="calendarStyleSecondary">
        <header>
            <span @click="previousDecade" class="prev" :class="{ 'disabled' : previousDecadeDisabled(currDate) }"></span>
            <span>{{ getDecade() }}</span>
            <span @click="nextDecade" class="next" :class="{ 'disabled' : nextMonthDisabled(currDate) }"></span>
        </header>
        <div class="blocks year">
            <span class="cell year" v-for="year in years" track-by="timestamp" :class="{ 'selected': year.isSelected, 'disabled': year.isDisabled, 'current':year.isCurrent }" @click.stop="selectYear(year)" :key="'Y'+year.year">{{ year.year }}</span>
        </div>
    </div>
</div>
</template>

<script>
import { isEmpty } from 'lodash'
import { FontAwesomeIcon as Icon } from '@fortawesome/vue-fontawesome'
import DateUtils from '../../utils/datepicker/DateUtils.js'
import DateLanguages from '../../utils/datepicker/DateLanguages.js'
export default {
	name: 'DatePicker',
	props: {
		// Unique identifier
		id: {
			type: String,
			required: true
		},
		// Format of date output
		format: {
			type: String,
			default: 'dd MMM yyyy'
		},
		// Localisation defined in DateLanguages.js file
		language: {
			type: String,
			default: 'en'
		},
		// Object parameter to disable controls on various dependable date fields
		disabled: {
			type: Object
		},
		// Placeholder text for input element
		placeholder: {
			type: String,
			default: 'dd MMM yyyy'
		},
		// Need to show as inline or on text
		inline: {
			type: Boolean
		},
		// Display type
		mega: {
			type: Boolean,
			default: false
		},
		// Data type of return value
		dataType: {
			type: String,
			default: 'object'
		},
		// Label text for mega display
		title: {
			default: 'Pick a date',
			type: String
		},
		// Value passed initially to the component
		value: {
			default: null
		},
		// Error message to be shown when invalid
		message: {
			default: '',
			type: String
		},
		// Boolean flag to indicate editable state or not. Displays label instead of input box. Do not confuse this with read only field.
		editable: {
			default: true,
			type: Boolean
		},
		// Required for order style
		rfo: {
			default: false,
			type: Boolean
		},
		// Required for Quote style
		rfq: {
			default: false,
			type: Boolean
		},
		// Show aster mark
		aster: {
			default: false,
			type: Boolean
		},
		// Toggle to show X button
		hideClearButton: {
			type: Boolean
		}
	},
	components: {
		Icon
	},
	$_veeValidate: {
		name() {
			return this.id
		},
		value() {
			return this.captureValue
		}
	},
	data() {
		return {
            /*
            * Vue cannot observe changes to a Date Object so date must be stored as a timestamp
            * {Number}
            */
			currDate: new Date().getTime(),
            /*
            * Selected Date
            * {Date}
            */
			selectedDate: null,
            /*
            * Flags to show calendar views
            * {Boolean}
            */
			showDayView: false,
			showMonthView: false,
			showYearView: false,
            /*
            * Helper arrays for names
            */
			translation: DateLanguages.translations[this.language],
			formattedValue: null,
			isoFormatted: null,
            /*
            * Positioning
            */
			calendarHeight: 0,
			captureValue: null
		}
	},
	computed: {
		hasError() {
			return !isEmpty(this.message)
		},
		wrapClass() {
			let cls = ['datepicker']
			if (this.mega) {
				cls.push('megaform')
			}
			if (this.hasError) {
				cls.push('error')
			}
			return cls.join(' ')
		},
		currMonthName() {
			const d = new Date(this.currDate)
			return DateUtils.getMonthNameAbbr(d.getMonth(), this.translation.months.abbr)
		},
		currYear() {
			const d = new Date(this.currDate)
			return d.getFullYear()
		},
        /**
         * Returns the day number of the week less one for the first of the current month
         * Used to show amount of empty cells before the first in the day calendar layout
         * @return {Number}
         */
		blankDays() {
			const d = new Date(this.currDate)
			let dObj = new Date(d.getFullYear(), d.getMonth(), 1, d.getHours(), d.getMinutes())
			return dObj.getDay()
		},
		days() {
			const d = new Date(this.currDate)
			let days = []
			// set up a new date object to the beginning of the current 'page'
			let dObj = new Date(d.getFullYear(), d.getMonth(), 1, d.getHours(), d.getMinutes())
			let daysInMonth = DateUtils.daysInMonth(dObj.getFullYear(), dObj.getMonth())
			for (let i = 0; i < daysInMonth; i++) {
				days.push({
					date: dObj.getDate(),
					timestamp: dObj.getTime(),
					isSelected: this.isSelectedDate(dObj),
					isDisabled: this.isDisabledDate(dObj),
					isCurrent: DateUtils.checkCurrent(dObj, 'DATE')
				})
				dObj.setDate(dObj.getDate() + 1)
			}
			return days
		},
		months() {
			const d = new Date(this.currDate)
			let months = []
			// set up a new date object to the beginning of the current 'page'
			let dObj = new Date(d.getFullYear(), 0, d.getDate(), d.getHours(), d.getMinutes())
			for (let i = 0; i < 12; i++) {
				months.push({
					month: DateUtils.getMonthName(i, this.translation.months.original),
					timestamp: dObj.getTime(),
					isSelected: this.isSelectedMonth(dObj),
					isDisabled: this.isDisabledMonth(dObj),
					isCurrent: DateUtils.checkCurrent(dObj, 'MONTH')
				})
				dObj.setMonth(dObj.getMonth() + 1)
			}
			return months
		},
		years() {
			const d = new Date(this.currDate)
			let years = []
			// set up a new date object to the beginning of the current 'page'
			let dObj = new Date(Math.floor(d.getFullYear() / 10) * 10, d.getMonth(), d.getDate(), d.getHours(), d.getMinutes())
			for (let i = 0; i < 10; i++) {
				years.push({
					year: dObj.getFullYear(),
					timestamp: dObj.getTime(),
					isSelected: this.isSelectedYear(dObj),
					isDisabled: this.isDisabledYear(dObj),
					isCurrent: DateUtils.checkCurrent(dObj, 'YEAR')
				})
				dObj.setFullYear(dObj.getFullYear() + 1)
			}
			return years
		},
		calendarStyle() {
			let styles = {}
			if (this.isOpen()) {
				let elSize = this.$el ? this.$el.getBoundingClientRect() : 0
				let heightNeeded = elSize.top + elSize.height + this.calendarHeight
				// if the calendar doesn't fit on the window without scrolling position it above the input
				if (heightNeeded > window.innerHeight) {
					styles = { 'bottom': elSize.height + 'px' }
				}
				if (this.isInline()) {
					styles.position = 'static'
				}
			}
			return styles
		},
		calendarStyleSecondary() { return (this.isInline()) ? { 'position': 'static' } : {} }
	},
	methods: {
		close() {
			this.showDayView = this.showMonthView = this.showYearView = false
		},
		isOpen() {
			return this.showDayView || this.showMonthView || this.showYearView
		},
		isInline() {
			return typeof this.inline !== 'undefined' && this.inline
		},
		showCalendar() {
			if (this.isInline()) {
				return false
			}
			(this.isOpen()) ? this.close() : this.showDayCalendar()
		},
		showDayCalendar() {
			this.close()
			this.showDayView = true
			this.$emit('opened')
		},
		showMonthCalendar() {
			this.close()
			this.showMonthView = true
		},
		showYearCalendar() {
			this.close()
			this.showYearView = true
		},
		setDate(timestamp) {
			this.selectedDate = new Date(timestamp)
			this.currDate = timestamp
			let d = new Date(timestamp)
			this.formattedValue = DateUtils.formatDate(d, this.format, this.translation)
			this.isoFormatted = DateUtils.formatDate(d, 'yyyy-MM-ddT00:00:00Z', this.translation)
			this.$emit('selected', d)
			this.emitInputEvents(d)
		},
        /**
         * @param {Object} day
         */
		selectDate(day) {
			if (day.isDisabled) {
				return false
			}
			this.setDate(day.timestamp)
			this.isInline() ? this.showDayCalendar() : this.close()
		},
        /**
         * @param {Object} month
         */
		selectMonth(month) {
			if (month.isDisabled) {
				return false
			}
			this.currDate = month.timestamp
			this.showDayCalendar()
		},
        /**
         * @param {Object} year
         */
		selectYear(year) {
			if (year.isDisabled) {
				return false
			}
			this.currDate = year.timestamp
			this.showMonthCalendar()
		},
        /**
         * @return {Number}
         */
		getMonth() {
			let d = new Date(this.currDate)
			return d.getMonth()
		},
        /**
         * @return {Number}
         */
		getYear() {
			let d = new Date(this.currDate)
			return d.getFullYear()
		},
        /**
         * @return {String}
         */
		getDecade() {
			let d = new Date(this.currDate)
			let sD = Math.floor(d.getFullYear() / 10) * 10
			return sD + '\'s'
		},
		previousMonth() {
			if (this.previousMonthDisabled()) {
				return false
			}
			let d = new Date(this.currDate)
			d.setMonth(d.getMonth() - 1)
			this.currDate = d.getTime()
		},
		previousMonthDisabled() {
			if (typeof this.disabled === 'undefined' || typeof this.disabled.to === 'undefined' || !this.disabled.to) {
				return false
			}
			let d = new Date(this.currDate)
			if (this.disabled.to.getMonth() >= d.getMonth() && this.disabled.to.getFullYear() >= d.getFullYear()) {
				return true
			}
			return false
		},
		nextMonth() {
			if (this.nextMonthDisabled()) {
				return false
			}
			let d = new Date(this.currDate)
			d.setMonth(d.getMonth() + 1)
			this.currDate = d.getTime()
		},
		nextMonthDisabled() {
			if (typeof this.disabled === 'undefined' || typeof this.disabled.from === 'undefined' || !this.disabled.from) {
				return false
			}
			let d = new Date(this.currDate)
			if (this.disabled.from.getMonth() <= d.getMonth() && this.disabled.from.getFullYear() <= d.getFullYear()) {
				return true
			}
			return false
		},
		previousYear() {
			if (this.previousYearDisabled()) {
				return false
			}
			let d = new Date(this.currDate)
			d.setYear(d.getFullYear() - 1)
			this.currDate = d.getTime()
		},
		previousYearDisabled() {
			if (typeof this.disabled === 'undefined' || typeof this.disabled.to === 'undefined' || !this.disabled.to) {
				return false
			}
			let d = new Date(this.currDate)
			if (this.disabled.to.getFullYear() >= d.getFullYear()) {
				return true
			}
			return false
		},
		nextYear() {
			if (this.nextYearDisabled()) {
				return false
			}
			let d = new Date(this.currDate)
			d.setYear(d.getFullYear() + 1)
			this.currDate = d.getTime()
		},
		nextYearDisabled() {
			if (typeof this.disabled === 'undefined' || typeof this.disabled.from === 'undefined' || !this.disabled.from) {
				return false
			}
			let d = new Date(this.currDate)
			if (this.disabled.from.getFullYear() <= d.getFullYear()) {
				return true
			}
			return false
		},
		previousDecade() {
			if (this.previousDecadeDisabled()) {
				return false
			}
			let d = new Date(this.currDate)
			d.setYear(d.getFullYear() - 10)
			this.currDate = d.getTime()
		},
		previousDecadeDisabled() {
			if (typeof this.disabled === 'undefined' || typeof this.disabled.to === 'undefined' || !this.disabled.to) {
				return false
			}
			let d = new Date(this.currDate)
			if (Math.floor(this.disabled.to.getFullYear() / 10) * 10 >= Math.floor(d.getFullYear() / 10) * 10) {
				return true
			}
			return false
		},
		nextDecade() {
			if (this.nextDecadeDisabled()) {
				return false
			}
			let d = new Date(this.currDate)
			d.setYear(d.getFullYear() + 10)
			this.currDate = d.getTime()
		},
		nextDecadeDisabled() {
			if (typeof this.disabled === 'undefined' || typeof this.disabled.from === 'undefined' || !this.disabled.from) {
				return false
			}
			let d = new Date(this.currDate)
			if (Math.ceil(this.disabled.from.getFullYear() / 10) * 10 <= Math.ceil(d.getFullYear() / 10) * 10) {
				return true
			}
			return false
		},
        /**
         * Whether a day is selected
         * @param {Date}
         * @return {Boolean}
         */
		isSelectedDate(dObj) {
			return this.selectedDate && this.selectedDate.toDateString() === dObj.toDateString()
		},
        /**
         * Whether a day is disabled
         * @param {Date}
         * @return {Boolean}
         */
		isDisabledDate(date) {
			let disabled = false
			if (typeof this.disabled === 'undefined') {
				return false
			}
			if (typeof this.disabled.dates !== 'undefined') {
				this.disabled.dates.forEach((d) => {
					if (date.toDateString() === d.toDateString()) {
						disabled = true
						return true
					}
				})
			}
			if (typeof this.disabled.to !== 'undefined' && this.disabled.to && date < this.disabled.to) {
				disabled = true
			}
			if (typeof this.disabled.from !== 'undefined' && this.disabled.from && date > this.disabled.from) {
				disabled = true
			}
			if (typeof this.disabled.days !== 'undefined' && this.disabled.days.indexOf(date.getDay()) !== -1) {
				disabled = true
			}
			return disabled
		},
        /**
         * Whether the selected date is in this month
         * @param {Date}
         * @return {Boolean}
         */
		isSelectedMonth(date) {
			return (this.selectedDate && this.selectedDate.getFullYear() === date.getFullYear() && this.selectedDate.getMonth() === date.getMonth())
		},
        /**
         * Whether a month is disabled
         * @param {Date}
         * @return {Boolean}
         */
		isDisabledMonth(date) {
			let disabled = false
			if (typeof this.disabled === 'undefined') {
				return false
			}
			if (typeof this.disabled.to !== 'undefined' && this.disabled.to) {
				if ((date.getMonth() < this.disabled.to.getMonth() && date.getFullYear() <= this.disabled.to.getFullYear()) || date.getFullYear() < this.disabled.to.getFullYear()) {
					disabled = true
				}
			}
			if (typeof this.disabled.from !== 'undefined' && this.disabled.from) {
				if (this.disabled.from && ((date.getMonth() > this.disabled.from.getMonth() && date.getFullYear() >= this.disabled.from.getFullYear()) || date.getFullYear() > this.disabled.from.getFullYear())) {
					disabled = true
				}
			}
			return disabled
		},
        /**
         * Whether a year is disabled
         * @param {Date}
         * @return {Boolean}
         */
		isSelectedYear(date) {
			return this.selectedDate && this.selectedDate.getFullYear() === date.getFullYear()
		},
        /**
         * Whether a month is disabled
         * @param {Date}
         * @return {Boolean}
         */
		isDisabledYear(date) {
			let disabled = false
			if (typeof this.disabled === 'undefined' || !this.disabled) {
				return false
			}
			if (typeof this.disabled.to !== 'undefined' && this.disabled.to) {
				if (date.getFullYear() < this.disabled.to.getFullYear()) {
					disabled = true
				}
			}
			if (typeof this.disabled.from !== 'undefined' && this.disabled.from) {
				if (date.getFullYear() > this.disabled.from.getFullYear()) {
					disabled = true
				}
			}
			return disabled
		},
        /**
         * Set the datepicker value
         * @param {Date|null} date
         */
		setValue(date) {
			if (!date) {
				const d = new Date()
				this.currDate = new Date(d.getFullYear(), d.getMonth(), 1).getTime()
				this.selectedDate = this.isoFormatted = null
				this.formattedValue = this.format
			} else {
				this.selectedDate = date
				this.currDate = new Date(date.getFullYear(), date.getMonth(), 1).getTime()
				this.formattedValue = DateUtils.formatDate(date, this.format, this.translation)
				this.isoFormatted = DateUtils.formatDate(date, 'yyyy-MM-ddT00:00:00Z', this.translation)
			}
			this.emitInputEvents(this.selectedDate)
		},
		emitInputEvents(d) {
			switch (this.dataType) {
				case 'object':
					this.captureValue = d
					this.$emit('input', d)
					// eslint-disable-next-line semi
					break;
				case 'string':
					this.captureValue = this.formattedValue
					this.$emit('input', this.formattedValue)
					// eslint-disable-next-line semi
					break;
				case 'isostring':
					this.captureValue = this.isoFormatted
					this.$emit('input', this.isoFormatted)
					// eslint-disable-next-line semi
					break;
				case 'number':
					this.captureValue = d.getTime()
					this.$emit('input', d.getTime())
					// eslint-disable-next-line semi
					break;
				default:
					this.captureValue = d
					this.$emit('input', d)
			}
		},
		init() {
			if (this.value) {
				if (typeof this.value == 'string') {
					this.setValue(new Date(this.value))
				} else {
					this.setValue(this.value)
				}
			}
			if (this.isInline()) {
				this.showDayCalendar()
			}
			if (!this.formattedValue) {
				this.formattedValue = this.format
			}
			this.$nextTick(() => {
				this.calendarHeight = this.$el.querySelector('.calendar') ? this.$el.querySelector('.calendar').getBoundingClientRect().height : 0
			})
		},
		onClickOutside(e) {
			if (this.$el && !this.$el.contains(e.target)) {
				(this.isInline()) ? this.showDayCalendar() : this.close()
			}
		}
	},
	beforeDestroy() {
		document.removeEventListener('click', this.onClickOutside)
		this.$events.off('datepicker|' + this.id)
	},
	mounted() {
		this.init()
		document.addEventListener('click', this.onClickOutside, false)
		this.$events.on('datepicker|' + this.id, (d) => {
			if (d.task.toLowerCase() === 'reset') {
				this.setValue()
				this.$emit('input', null)
			} else if (d.task.toLowerCase() === 'set') {
				this.setValue(d.val)
			}
		})
	},
	watch: {
		value(n, o) {
			if (typeof n == 'string') {
				if (new Date(this.currDate).toISOString() != n) {
					this.setValue(new Date(n))
				}
			} else {
				if (this.currDate != n) {
					this.setValue(n)
				}
			}
		}
	}
}
</script>
