class DragData {
    constructor () {
        this.data = {}
    }

    new (key) {
        if (!this.data[key]) {
            this.data[key] = {
                className: '',
                List: [],
                KEY_MAP: {}
            }
        }
        return this.data[key]
    }

    get (key) {
        return this.data[key]
    }
}

const $dragging = {
    listeners: {
    },
    $on (event, func) {
        const events = this.listeners[event]
        if (!events) {
            this.listeners[event] = []
        }
        this.listeners[event].push(func)
    },
    $once (event, func) {
        const vm = this
        function on (...args) {
            vm.$off(event, on)
            func.apply(vm, args)
        }
        this.$on(event, on)
    },
    $off (event, func) {
        const events = this.listeners[event]
        if (!func || !events) {
            this.listeners[event] = []
            return
        }
        this.listeners[event] = this.listeners[event].filter(i => i !== func)
    },
    $emit (event, context) {
        const events = this.listeners[event]
        if (events && events.length > 0) {
            events.forEach(func => {
                func(context)
            })
        }
    }
}
const _ = {
    on (el, type, fn) {
        el.addEventListener(type, fn)
    },
    off (el, type, fn) {
        el.removeEventListener(type, fn)
    },
    addClass (el, cls) {
        if(arguments.length < 2) {
            el.classList.add(cls)
        } else {
            for (let i = 1, len = arguments.length; i < len; i++) {
                el.classList.add(arguments[i])
            }
        }
    },
    removeClass (el, cls) {
        if(arguments.length < 2) {
            el.classList.remove(cls)
        } else {
            for (let i = 1, len = arguments.length; i < len; i++) {
                el.classList.remove(arguments[i])
            }
        }
    }
}

export default function (Vue, options) {
    const isPreVue = Vue.version.split('.')[0] === '1'
    const dragData = new DragData()
    let isSwap = false
    let Current = null

    function handleDragStart(e) {
        const el = getBlockEl(e.target)
        const key = el.getAttribute('drag_group')
        const drag_key = el.getAttribute('drag_key')
        const comb = el.getAttribute('comb')
        const DDD = dragData.new(key)
        const item = DDD.KEY_MAP[drag_key]
        const index = DDD.List.indexOf(item)
        const groupArr = DDD.List.filter(item => item[comb])
        _.addClass(el, 'dragging')

        if (e.dataTransfer) {
            e.dataTransfer.effectAllowed = 'move'
            e.dataTransfer.setData('text', JSON.stringify(item))
        }

        Current = {
            index,
            item,
            el,
            group: key,
            groupArr
        }
    }

    function handleDragOver(e) {
        if (e.preventDefault) {
            e.preventDefault()
        }
        return false
    }

    function handleDragEnter(e) {
        let el
        if (e.type === 'touchmove') {
            e.stopPropagation()
            e.preventDefault()
            el = getOverElementFromTouch(e)
            el = getBlockEl(el)
        } else {
            el = getBlockEl(e.target)
        }

        if (!el || !Current) return

        const key = el.getAttribute('drag_group')
        if (key !== Current.group || !Current.el || !Current.item || el === Current.el) return
        const drag_key = el.getAttribute('drag_key')
        const DDD = dragData.new(key)
        const item = DDD.KEY_MAP[drag_key]

        if (item === Current.item) return

        const indexTo = DDD.List.indexOf(item)
        const indexFrom = DDD.List.indexOf(Current.item)

        swapArrayElements(DDD.List, indexFrom, indexTo)

        Current.groupArr.forEach(item => {
            if (item != Current.item) {
                DDD.List.splice(DDD.List.indexOf(item), 1)
            }
        })

        let targetIndex = DDD.List.indexOf(Current.item)
        if (Current.groupArr.length) {
            DDD.List.splice(targetIndex, 1, ...Current.groupArr)
        }

        Current.index = indexTo
        isSwap = true
        $dragging.$emit('dragged', {
            draged: Current.item,
            to: item,
            value: DDD.value,
            group: key
        })
    }

    function handleDragLeave(e) {
        _.removeClass(getBlockEl(e.target), 'drag-over', 'drag-enter')
    }

    function handleDrag (e) {
    }

    function handleDragEnd (e) {
        const el = getBlockEl(e.target)
        _.removeClass(el, 'dragging', 'drag-over', 'drag-enter')
        Current = null
        // if (isSwap) {
        isSwap = false
        const group = el.getAttribute('drag_group')
        $dragging.$emit('dragend', { group })
        // }
    }

    function handleDrop(e) {
        e.preventDefault()
        if (e.stopPropagation) {
            e.stopPropagation()
        }
        return false
    }

    function getBlockEl (el) {
        if (!el) return
        while (el.parentNode) {
            if (el.getAttribute && el.getAttribute('drag_block')) {
                return el
                break
            } else {
                el = el.parentNode
            }
        }
    }

    function swapArrayElements (items, indexFrom, indexTo) {
        let item = items[indexTo]
        if (isPreVue) {
            items.$set(indexTo, items[indexFrom])
            items.$set(indexFrom, item)
        } else {
            Vue.set(items, indexTo, items[indexFrom])
            Vue.set(items, indexFrom, item)
        }
        return items
    }

    function getOverElementFromTouch (e) {
        const touch = e.touches[0]
        const el = document.elementFromPoint(touch.clientX, touch.clientY)
        return el
    }

    function addDragItem (el, binding, vnode) {
        const item = binding.value.item
        const list = binding.value.list
        const DDD = dragData.new(binding.value.group)

        const drag_key = isPreVue? binding.value.key : vnode.key
        DDD.value = binding.value
        DDD.className = binding.value.className
        DDD.KEY_MAP[drag_key] = item
        if (list && DDD.List !== list) {
            DDD.List = list
        }
        el.setAttribute('draggable', 'true')
        el.setAttribute('drag_group', binding.value.group)
        el.setAttribute('drag_block', binding.value.group)
        el.setAttribute('drag_key', drag_key)
        el.setAttribute('comb', binding.value.comb)

        _.on(el, 'dragstart', handleDragStart)
        _.on(el, 'dragenter', handleDragEnter)
        _.on(el, 'dragover', handleDragOver)
        _.on(el, 'drag', handleDrag)
        _.on(el, 'dragleave', handleDragLeave)
        _.on(el, 'dragend', handleDragEnd)
        _.on(el, 'drop', handleDrop)

        _.on(el, 'touchstart', handleDragStart)
        _.on(el, 'touchmove', handleDragEnter)
        _.on(el, 'touchend', handleDragEnd)
    }

    function removeDragItem (el, binding, vnode) {
        const DDD = dragData.new(binding.value.group)
        const drag_key = isPreVue? binding.value.key : vnode.key
        DDD.KEY_MAP[drag_key] = undefined
        _.off(el, 'dragstart', handleDragStart)
        _.off(el, 'dragenter', handleDragEnter)
        _.off(el, 'dragover', handleDragOver)
        _.off(el, 'drag', handleDrag)
        _.off(el, 'dragleave', handleDragLeave)
        _.off(el, 'dragend', handleDragEnd)
        _.off(el, 'drop', handleDrop)

        _.off(el, 'touchstart', handleDragStart)
        _.off(el, 'touchmove', handleDragEnter)
        _.off(el, 'touchend', handleDragEnd)
    }

    Vue.prototype.$dragging = $dragging
    if (!isPreVue) {
        Vue.directive('dragging', {
            bind: addDragItem,
            update(el, binding, vnode) {
                const DDD = dragData.new(binding.value.group)
                const item = binding.value.item
                const list = binding.value.list

                const drag_key = vnode.key
                const old_item = DDD.KEY_MAP[drag_key]
                if (item && old_item !== item) {
                    DDD.KEY_MAP[drag_key] = item
                }
                if (list && DDD.List !== list) {
                    DDD.List = list
                }
            },
            unbind : removeDragItem
        })
    } else {
        Vue.directive('dragging', {
            update (newValue, oldValue) {
                addDragItem(this.el, {
                    modifiers: this.modifiers,
                    arg: this.arg,
                    value: newValue,
                    oldValue: oldValue
                })
            },
            unbind (newValue, oldValue) {
                removeDragItem(this.el, {
                    modifiers: this.modifiers,
                    arg: this.arg,
                    value: newValue?newValue:{group: this.el.getAttribute('drag_group')},
                    oldValue: oldValue
                })
            }
        })
    }
}