<template>
<div class="dataGridWrapper" :class="{'selectable': selectable}">
    <div class="ctrlPanel" v-show="!loading">
        <div class="pageCtrl">
            <PageControl :id="`PGCTRL_${this.id}`" v-if="controlpages" :rows="pageRange" :default="defaultRows" @change="pageControlChange"/>
        </div>
        <div class="sortCtrl">
            <SortControl :id="`PGSORT_${this.id}`" v-if="sortable" :params="sortparams" :labels="sortlabels" v-model="sortKey" @input="sortEvent"/>
        </div>
        <div class="spacer"></div>
        <div class="filterCtrl" v-show="filterable">
            <input v-if="filterEvent == 'enter'" :placeholder="placeholder" v-model="filterKey" @keyup.enter="directFilterEvent"/>
            <input v-else-if="filterEvent == 'none'" :placeholder="placeholder" v-model="filterKey" @keyup="debouncedFilterEvent"/>
            <Icon :icon="['fabt', 'times']" class="icon clrIcon" v-if="filterKey.length" @click="clearFilter"/>
            <Icon :icon="['fabt', 'search']" class="icon" v-else/>
        </div>
    </div>
    <div v-show="!loading">
        <slot name="grid" :source="nodes"></slot>
    </div>
    <div class="paginationWrapper" v-show="!loading && source.length > 0">
        <div class="pageInfo">{{count}}</div>
        <div class="paginator">
            <ul v-show="this.totalPages > 1" class="pagination">
                <li :class="allowedChunkClass(-1)" @click.stop="setChunk(-1)" tabindex="1" title="First set"><Icon :icon="['fabt', 'angle-double-left']" @click.stop="setChunk(-1)"/></li>
                <li :class="allowedPageClass(this.page - 1)" @click.stop="prev" tabindex="1" title="Previous"><Icon :icon="['fabt', 'angle-left']" @click.stop="prev"/></li>
                <li :class="activeClass(page)" @click.stop="setPage(page)" v-for="page in pages" tabindex="1" :title="'Page ' + page" :key="page">{{page}}</li>
                <li :class="allowedPageClass(this.page + 1)" @click.stop="next" tabindex="1" title="Next"><Icon :icon="['fabt', 'angle-right']" @click.stop="next"/></li>
                <li :class="allowedChunkClass(1)" @click.stop="setChunk(1)" tabindex="1" title="Last set"><Icon :icon="['fabt', 'angle-double-right']" @click.stop="setChunk(1)"/></li>
            </ul>
        </div>
    </div>
    <SpinLoader v-if="loading && loader == 'spin'">{{loadingText}}</SpinLoader>
    <BarLoader v-if="loading && loader == 'bar'">{{loadingText}}</BarLoader>
</div>
</template>

<script>
import { debounce } from '../../utils/utils.js'
import PageControl from './PageControl'
import SortControl from './SortControl'
import SpinLoader from '../loaders/SpinLoader'
import BarLoader from '../loaders/BarLoader'
import { FontAwesomeIcon as Icon } from '@fortawesome/vue-fontawesome'

export default {
	name: 'Datatable',
	components: {
		PageControl,
		SortControl,
		SpinLoader,
		BarLoader,
		Icon
	},
	props: {
        /**
         * Unique identifier
         * @type {String}
        */
		id: {
			type: String,
			required: true
		},
        /**
         * ID of TABLE slot element
         * @type {String}
        */
		gridId: {
			type: String,
			required: true
		},
        /**
         * Data source to be used for table generation
         * @default []
         * @type {Array}
        */
		source: {
			type: Array,
			default: function () {
				return []
			}
		},
        /**
         * Ranges of pages to be shown for control
         * @default 5,10,20
         * @type {String}
        */
		pageRange: {
			type: String,
			default: '5,10,20'
		},
        /**
         * Flag to indicate if data is being loaded
         * @default false
         * @type {Boolean}
        */
		loading: {
			type: Boolean,
			default: false
		},
        /**
         * Type of loader to be used
         * @default 'spin'
         * @type {String}
        */
		loader: {
			type: String,
			default: 'spin',
			validate: function (val) {
				return ['spin', 'bar'].includes(val)
			}
		},
        /**
         * Loader message text
         * @default Fetching data ...
         * @type {String}
        */
		loadingText: {
			type: String,
			default: 'Fetching data ...'
		},
        /**
         * Flag to display sort control
         * @default true
         * @type {Boolean}
        */
		sortable: {
			type: Boolean,
			default: true
		},
        /**
         * Flag to display global filter box
         * @default true
         * @type {Boolean}
        */
		filterable: {
			type: Boolean,
			default: true
		},
        /**
         * Flag to display the No of pages control
         * @default true
         * @type Boolean
        */
		controlpages: {
			type: Boolean,
			default: true
		},
        /**
         * Key Event trigger for filter box
         * @default none
         * @type {String}
        */
		filterEvent: {
			type: String,
			default: 'none',
			validate: function (val) {
				return ['none', 'enter'].includes(val)
			}
		},
        /**
         * Placeholder for filter box
         * @default Enter keywords to filter
         * @type {String}
        */
		placeholder: {
			type: String,
			default: 'Enter keywords to filter'
		},
        /**
         * Flag to indicate if table rows can be selected
         * @default false
         * @type {Boolean}
        */
		selectable: {
			type: Boolean,
			default: false
		},
        /**
         * Parameters codes for sort control
         * @type {String}
        */
		sortparams: {
			type: String,
			validate: function (val) {
				return this.sortable && val && val.length
			}
		},
        /**
         * Parameters labels for sort control
         * @type {String}
        */
		sortlabels: {
			type: String,
			validate: function (val) {
				return this.sortable && val && val.length
			}
		},
        /**
        * No of pagination chunks
        * @default 5
        * @type {Number}
        */
		chunk: {
			default: 5,
			type: Number,
			validator: (value) => {
				return Number(value) > 2
			}
		},
        /**
         * Flag to keep pagination to stick to a page when rows change
         * @default false
         * @type {Boolean}
        */
		persistPage: {
			default: false,
			type: Boolean
		},
        /**
        * Function handler that needs to get called when sort controls are changed
        */
		onSort: {
			type: Function
		},
        /**
        * Function handler that needs to get called when filter controls are changed
        */
		onFilter: {
			type: Function
		}
	},
	created() {
		this.countText = this.$t('btgsvue.paginatorCount')
	},
	computed: {
		pages() {
			if (!this.records) {
				return []
			}
			return Array.apply(0, Array(this.pagesInCurrentChunk)).map((element, index) => { return index + this.paginationStart })
		},
		totalPages() {
			return this.records ? Math.ceil(this.records / this.perPage) : 1
		},
		totalChunks() {
			return Math.ceil(this.totalPages / this.chunk)
		},
		currentChunk() {
			return Math.ceil(this.page / this.chunk)
		},
		paginationStart() {
			return ((this.currentChunk - 1) * this.chunk) + 1
		},
		pagesInCurrentChunk() {
			return this.paginationStart + this.chunk <= this.totalPages ? this.chunk : this.totalPages - this.paginationStart + 1
		},
		count() {
			let from = ((this.page - 1) * this.perPage) + 1
			let to = this.page == this.totalPages ? this.records : from + this.perPage - 1
			let parts = this.countText.split('|')
			let i = Math.min(this.records == 1 ? 2 : this.totalPages == 1 ? 1 : 0, parts.length - 1)
			return parts[i].replace('[count]', this.records).replace('[from]', from).replace('[to]', to)
		}
	},
	data() {
		return {
			filterKey: '',
			sortKey: '',
			defaultRows: this.pageRange.split(',')[0],
			records: 0,
			page: 1,
			nodes: [],
			perPage: 10,
			countText: ''
		}
	},
	methods: {
		sortEvent() {
			this.$emit('sort', this.sortKey)
			if (this.onSort) {
				this.onSort(this.sortKey)
			}
		},
		directFilterEvent() {
			this.$emit('filter', this.filterKey)
			if (this.onFilter) {
				this.onFilter(this.filterKey)
			}
		},
		clearFilter() {
			this.filterKey = ''
			this.debouncedFilterEvent()
		},
		debouncedFilterEvent: debounce(function () {
			this.$emit('filter', this.filterKey)
			if (this.onFilter) {
				this.onFilter(this.filterKey)
			}
		}, 500),
		setPage(page) {
			if (this.allowedPage(page)) {
				this.paginate(page)
			}
		},
		next() {
			this.setPage(this.page + 1)
		},
		prev() {
			this.setPage(this.page - 1)
		},
		setChunk(direction) {
			this.setPage((((this.currentChunk - 1) + direction) * this.chunk) + 1)
		},
		allowedPage(page) {
			return page >= 1 && page <= this.totalPages
		},
		allowedChunk(direction) {
			return (direction == 1 && this.currentChunk < this.totalChunks) || (direction == -1 && this.currentChunk > 1)
		},
		allowedPageClass(direction) {
			return this.allowedPage(direction) ? '' : 'disabled'
		},
		allowedChunkClass(direction) {
			return this.allowedChunk(direction) ? '' : 'disabled'
		},
		activeClass(page) {
			return this.page == page ? 'active' : ''
		},
		paginate(page) {
			this.page = page
			this.showRows(page)
		},
		showRows(page) {
			let from = ((this.page - 1) * this.perPage) + 1
			let to = this.page == (this.totalPages) ? this.records : from + this.perPage - 1
			this.nodes = this.source.slice(from - 1, to)
		},
		prepNodes() {
			let parentNode = document.getElementById(this.gridId)
			if (Array.isArray(this.source)) {
				this.nodes = []
				this.records = this.source.length
				if (this.totalPages < this.page) {
					this.page = 1
				}
				this.showRows(this.page)
			}
			// Check if the table has required style classes
			if (parentNode && !parentNode.className.contains('gridTable')) {
				parentNode.className = parentNode.className ? parentNode.className + ' gridTable' : 'gridTable'
			}
		},
		pageControlChange(n) {
			if (n === 'ALL') {
				this.perPage = this.source.length
			} else {
				this.perPage = Number(n)
			}
			if (!this.persistPage) {
				this.page = 1
			}
			setTimeout(() => { this.prepNodes() }, 100)
		}
	},
	mounted() {
		// Init paginator
		this.perPage = Number(this.defaultRows)
		setTimeout(() => { this.prepNodes() }, 100)
	},
	watch: {
		source() {
			if (!this.persistPage) {
				this.page = 1
			}
			setTimeout(() => { this.prepNodes() }, 100)
		}
	}
}
</script>
