<template>
<div :class="divClass">
    <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 ? 'display-time invalid' : 'display-time text'" type="text" v-model="displayTime" v-if="editable" :placeholder="placeholder" @click="toggleDropdown" readonly>
    <Icon v-if="editable && !hideClearButton && !showDropdown && showClearBtn" :icon="['fabt', 'times']" class="icon" @click="clearTime"/>
    <Icon v-if="editable && !hideClearButton && !showDropdown && !showClearBtn" :icon="['fabt', 'clock']" class="icon" @click="toggleDropdown"/>
    <span v-if="!editable" class="noneditable">{{value}}</span>
    <span v-show="editable && hasError" class="vldtn-error" v-html="message"></span>
    <div class="dropdown" v-show="showDropdown">
        <div class="select-list">
            <ul class="hours">
                <li class="hint" v-text="hourType"></li>
                <li v-for="(hr, idx) in hours" v-text="hr" :class="{active: hour === hr}" @click.stop="select('hour', hr)" :key="idx"></li>
            </ul>
            <ul class="minutes">
                <li class="hint" v-text="minuteType"></li>
                <li v-for="(m, idx) in minutes" v-text="m" :class="{active: minute === m}" @click.stop="select('minute', m)" :key="idx"></li>
            </ul>
            <ul class="seconds" v-if="secondType">
                <li class="hint" v-text="secondType"></li>
                <li v-for="(s, idx) in seconds" v-text="s" :class="{active: second === s}" @click.stop="select('second', s)" :key="idx"></li>
            </ul>
            <ul class="apms" v-if="apmType">
                <li class="hint" v-text="apmType"></li>
                <li v-for="(a, idx) in apms" v-text="a" :class="{active: apm === a}" @click.stop="select('apm', a)" :key="idx"></li>
            </ul>
        </div>
    </div>
</div>
</template>

<script>
import { isEmpty } from 'lodash'
import { FontAwesomeIcon as Icon } from '@fortawesome/vue-fontawesome'

const CONFIG = {
	HOUR_TOKENS: ['HH', 'H', 'hh', 'h', 'kk', 'k'],
	MINUTE_TOKENS: ['mm', 'm'],
	SECOND_TOKENS: ['ss', 's'],
	APM_TOKENS: ['A', 'a']
}

/**
* Generates timepicker controls
*/
export default {
	name: 'Timepicker',
	props: {
		// Unique identifier
		id: {
			type: String
		},
		// Initial value
		value: {
			type: Object
		},
		// Label text for mega display
		title: {
			default: 'Pick a time',
			type: String
		},
		// Toggle to show X button
		hideClearButton: {
			type: Boolean
		},
		// Formatting of timestamp output
		format: {
			type: String,
			default: 'HH:mm:ss'
		},
		// Intervals in which the minute control needs to be shown
		minuteInterval: {
			type: String,
			default: '1'
		},
		// Intervals in which the second control needs to be shown
		secondInterval: {
			type: String,
			default: '1'
		},
		// Display type
		mega: {
			type: Boolean,
			default: false
		},
		// 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
		},
		// Error message attribute
		message: {
			default: '',
			type: String
		},
		// Placeholder text for input element
		placeholder: {
			type: String,
			default: 'HH:mm:ss'
		}
	},
	data() {
		return {
			hours: [],
			minutes: [],
			seconds: [],
			apms: [],
			showDropdown: false,
			muteWatch: false,
			hourType: 'HH',
			minuteType: 'mm',
			secondType: '',
			apmType: '',
			hour: '',
			minute: '',
			second: '',
			apm: '',
			fullValues: undefined
		}
	},
	$_veeValidate: {
		name() {
			return this.id
		},
		value() {
			return this.displayTime
		}
	},
	components: {
		Icon
	},
	computed: {
		divClass() {
			let cls = ['time-picker']
			if (this.mega) {
				cls.push('megaform')
			}
			if (this.hasError) {
				cls.push('error')
			}
			return cls.join(' ')
		},
		hasError() {
			return !isEmpty(this.message)
		},
		displayTime() {
			let formatString = this.format || 'HH:mm'
			if (this.hour) {
				formatString = formatString.replace(new RegExp(this.hourType, 'g'), this.hour)
			}
			if (this.minute) {
				formatString = formatString.replace(new RegExp(this.minuteType, 'g'), this.minute)
			}
			if (this.second && this.secondType) {
				formatString = formatString.replace(new RegExp(this.secondType, 'g'), this.second)
			}
			if (this.apm && this.apmType) {
				formatString = formatString.replace(new RegExp(this.apmType, 'g'), this.apm)
			}
			return formatString
		},
		showClearBtn() {
			if ((this.hour && this.hour !== '') || (this.minute && this.minute !== '')) {
				return true
			}
			return false
		}
	},
	methods: {
		formatValue(type, i) {
			switch (type) {
				case 'H':
				case 'm':
				case 's':
					return String(i)
				case 'HH':
				case 'mm':
				case 'ss':
					return i < 10 ? `0${i}` : String(i)
				case 'h':
				case 'k':
					return String(i + 1)
				case 'hh':
				case 'kk':
					return (i + 1) < 10 ? `0${i + 1}` : String(i + 1)
				default:
					return ''
			}
		},
		checkAcceptingType(validValues, formatString, fallbackValue) {
			if (!validValues || !formatString || !formatString.length) { return '' }
			for (let i = 0; i < validValues.length; i++) {
				if (formatString.indexOf(validValues[i]) > -1) {
					return validValues[i]
				}
			}
			return fallbackValue || ''
		},
		renderFormat(newFormat) {
			newFormat = newFormat || this.format
			if (!newFormat || !newFormat.length) {
				newFormat = 'HH:mm'
			}
			this.hourType = this.checkAcceptingType(CONFIG.HOUR_TOKENS, newFormat, 'HH')
			this.minuteType = this.checkAcceptingType(CONFIG.MINUTE_TOKENS, newFormat, 'mm')
			this.secondType = this.checkAcceptingType(CONFIG.SECOND_TOKENS, newFormat)
			this.apmType = this.checkAcceptingType(CONFIG.APM_TOKENS, newFormat)
			this.renderHoursList()
			this.renderList('minute')
			if (this.secondType) {
				this.renderList('second')
			}
			if (this.apmType) {
				this.renderApmList()
			}
			const self = this
			this.$nextTick(() => {
				self.readValues()
			})
		},
		renderHoursList() {
			const hoursCount = (this.hourType === 'h' || this.hourType === 'hh') ? 12 : 24
			this.hours = []
			for (let i = 0; i < hoursCount; i++) {
				this.hours.push(this.formatValue(this.hourType, i))
			}
		},
		renderList(listType, interval) {
			if (listType === 'second') {
				interval = interval || Number(this.secondInterval)
			} else if (listType === 'minute') {
				interval = interval || Number(this.minuteInterval)
			} else {
				return
			}
			if (interval === 0) {
				interval = 60
			} else if (interval > 60) {
				console.warn('`' + listType + '-interval` should be less than 60. Current value is', interval)
				interval = 1
			} else if (interval < 1) {
				console.warn('`' + listType + '-interval` should be NO less than 1. Current value is', interval)
				interval = 1
			} else if (!interval) {
				interval = 1
			}
			if (listType === 'minute') {
				this.minutes = []
			} else {
				this.seconds = []
			}
			for (let i = 0; i < 60; i += interval) {
				if (listType === 'minute') {
					this.minutes.push(this.formatValue(this.minuteType, i))
				} else {
					this.seconds.push(this.formatValue(this.secondType, i))
				}
			}
		},
		renderApmList() {
			this.apms = []
			if (!this.apmType) { return }
			this.apms = this.apmType === 'A' ? ['AM', 'PM'] : ['am', 'pm']
		},
		readValues() {
			if (!this.value || this.muteWatch) { return }
			const timeValue = JSON.parse(JSON.stringify(this.value || {}))
			const values = Object.keys(timeValue)
			if (values.length === 0) { return }
			if (values.indexOf(this.hourType) > -1) {
				this.hour = timeValue[this.hourType]
			}
			if (values.indexOf(this.minuteType) > -1) {
				this.minute = timeValue[this.minuteType]
			}
			if (values.indexOf(this.secondType) > -1) {
				this.second = timeValue[this.secondType]
			} else {
				this.second = 0
			}
			if (values.indexOf(this.apmType) > -1) {
				this.apm = timeValue[this.apmType]
			}
			this.fillValues()
		},
		fillValues() {
			if (this.displayTime) {
				let fullValues = {}
				const baseHour = this.hour
				const baseHourType = this.hourType
				const hourValue = baseHour || baseHour === 0 ? Number(baseHour) : ''
				const baseOnTwelveHours = this.isTwelveHours(baseHourType)
				const apmValue = (baseOnTwelveHours && this.apm) ? String(this.apm).toLowerCase() : false
				CONFIG.HOUR_TOKENS.forEach((token) => {
					if (token === baseHourType) {
						fullValues[token] = baseHour
						return
					}
					let value
					let apm
					switch (token) {
						case 'H':
						case 'HH':
							if (!String(hourValue).length) {
								fullValues[token] = ''
								return
							} else if (baseOnTwelveHours) {
								if (apmValue === 'pm') {
									value = hourValue < 12 ? hourValue + 12 : hourValue
								} else {
									value = hourValue % 12
								}
							} else {
								value = hourValue % 24
							}
							fullValues[token] = (token === 'HH' && value < 10) ? `0${value}` : String(value)
							break
						case 'k':
						case 'kk':
							if (!String(hourValue).length) {
								fullValues[token] = ''
								return
							} else if (baseOnTwelveHours) {
								if (apmValue === 'pm') {
									value = hourValue < 12 ? hourValue + 12 : hourValue
								} else {
									value = hourValue === 12 ? 24 : hourValue
								}
							} else {
								value = hourValue === 0 ? 24 : hourValue
							}
							fullValues[token] = (token === 'kk' && value < 10) ? `0${value}` : String(value)
							break
						case 'h':
						case 'hh':
							if (apmValue) {
								value = hourValue
								apm = apmValue || 'am'
							} else {
								if (!String(hourValue).length) {
									fullValues[token] = ''
									fullValues.a = ''
									fullValues.A = ''
									return
								} else if (hourValue > 11) {
									apm = 'pm'
									value = hourValue === 12 ? 12 : hourValue % 12
								} else {
									if (baseOnTwelveHours) {
										apm = ''
									} else {
										apm = 'am'
									}
									value = hourValue % 12 === 0 ? 12 : hourValue
								}
							}
							fullValues[token] = (token === 'hh' && value < 10) ? `0${value}` : String(value)
							fullValues.a = apm
							fullValues.A = apm.toUpperCase()
							break
					}
				})
				if (this.minute || this.minute === 0) {
					const minuteValue = Number(this.minute)
					fullValues.m = String(minuteValue)
					fullValues.mm = minuteValue < 10 ? `0${minuteValue}` : String(minuteValue)
				} else {
					fullValues.m = ''
					fullValues.mm = ''
				}
				if (this.second || this.second === 0) {
					const secondValue = Number(this.second)
					fullValues.s = String(secondValue)
					fullValues.ss = secondValue < 10 ? `0${secondValue}` : String(secondValue)
				} else {
					fullValues.s = ''
					fullValues.ss = ''
				}
				this.fullValues = fullValues
				this.updateTimeValue(fullValues)
				this.$emit('change', fullValues)
				this.$emit('input', fullValues)
			}
		},
		updateTimeValue(fullValues) {
			this.muteWatch = true
			const self = this
			const baseTimeValue = JSON.parse(JSON.stringify(this.value || {}))
			let timeValue = {}
			Object.keys(baseTimeValue).forEach((key) => {
				timeValue[key] = fullValues[key]
			})
			this.$emit('input', timeValue)
			this.$nextTick(() => {
				self.muteWatch = false
			})
		},
		isTwelveHours(token) {
			return token === 'h' || token === 'hh'
		},
		toggleDropdown() {
			this.showDropdown = !this.showDropdown
		},
		select(type, value) {
			if (type === 'hour') {
				this.hour = value
			} else if (type === 'minute') {
				this.minute = value
			} else if (type === 'second') {
				this.second = value
			} else if (type === 'apm') {
				this.apm = value
			}
		},
		clearTime() {
			this.hour = ''
			this.minute = ''
			this.second = ''
			this.apm = ''
		},
		onClickOutside(e) {
			if (this.$el && !this.$el.contains(e.target)) {
				this.showDropdown = false
			}
		}
	},
	beforeDestroy() {
		document.removeEventListener('click', this.onClickOutside)
		this.$events.off('timepicker|' + this.id)
	},
	mounted() {
		this.renderFormat()
		document.addEventListener('click', this.onClickOutside, false)
		this.$events.on('timepicker|' + this.id, (d) => {
			if (d.task.toLowerCase() === 'reset') {
				this.clearTime()
			}
		})
	},
	watch: {
		format: 'renderFormat',
		minuteInterval(newInteval) {
			this.renderList('minute', Number(newInteval))
		},
		secondInterval(newInteval) {
			this.renderList('second', Number(newInteval))
		},
		value: 'readValues',
		displayTime: 'fillValues'
	}
}
</script>
