const styleMapping = {
    y: {
        t: 'top',
        m: 'marginTop',
        b: 'bottom',
    },
    x: {
        l: 'left',
        m: 'marginLeft',
        r: 'right',
    },
};

function addEvents(events) {
    events.forEach((cb, eventName) => {
        document.documentElement.addEventListener(eventName, cb);
    });
}

function removeEvents(events) {
    events.forEach((cb, eventName) => {
        document.documentElement.removeEventListener(eventName, cb);
    });
}

export default {
    name: 'vue-drag-resize',

    emits: ['clicked', 'dragging', 'dragstop', 'resizing', 'resizestop', 'activated', 'deactivated'],

    props: {
        stickSize: {
            type: Number, default: 8,
        },
        parentScaleX: {
            type: Number, default: 1,
        },
        parentScaleY: {
            type: Number, default: 1,
        },
        isActive: {
            type: Boolean, default: false,
        },
        preventActiveBehavior: {
            type: Boolean, default: false,
        },
        isDraggable: {
            type: Boolean, default: true,
        },
        isResizable: {
            type: Boolean, default: true,
        },
        aspectRatio: {
            type: Boolean, default: false,
        },
        parentLimitation: {
            type: Boolean, default: false,
        },
        snapToGrid: {
            type: Boolean, default: false,
        },
        gridX: {
            type: Number,
            default: 50,
            validator(val) {
                return val >= 0;
            },
        },
        gridY: {
            type: Number,
            default: 50,
            validator(val) {
                return val >= 0;
            },
        },
        parentW: {
            type: Number,
            default: 0,
            validator(val) {
                return val >= 0;
            },
        },
        parentH: {
            type: Number,
            default: 0,
            validator(val) {
                return val >= 0;
            },
        },
        // w: {
        //     type: [String, Number],
        //     default: 200,
        //     validator(val) {
        //         return (typeof val === 'string') ? val === 'auto' : val >= 0;
        //     },
        // },
        // h: {
        //     type: [String, Number],
        //     default: 200,
        //     validator(val) {
        //         return (typeof val === 'string') ? val === 'auto' : val >= 0;
        //     },
        // },
        minw: {
            type: Number,
            default: 50,
            validator(val) {
                return val >= 0;
            },
        },
        minh: {
            type: Number,
            default: 50,
            validator(val) {
                return val >= 0;
            },
        },
        // x: {
        //     type: Number,
        //     default: 0,
        //     validator(val) {
        //         return typeof val === 'number';
        //     },
        // },
        // y: {
        //     type: Number,
        //     default: 0,
        //     validator(val) {
        //         return typeof val === 'number';
        //     },
        // },
        z: {
            type: [String, Number],
            default: 'auto',
            validator(val) {
                return (typeof val === 'string') ? val === 'auto' : val >= 0;
            },
        },
        dragHandle: {
            type: String,
            default: null,
        },
        dragCancel: {
            type: String,
            default: null,
        },
        sticks: {
            type: Array,
            default() {
                return ['tl', 'tm', 'tr', 'mr', 'br', 'bm', 'bl', 'ml'];
            },
        },
        axis: {
            type: String,
            default: 'both',
            validator(val) {
                return ['x', 'y', 'both', 'none'].indexOf(val) !== -1;
            },
        },
        contentClass: {
            type: String,
            required: false,
            default: '',
        },
        layout: {
            required: true
        },
        resize: {
            type: Boolean,
            required: false,
            default: true,
        }
    },

    data() {
        return {
            fixAspectRatio: null,
            active: null,
            zIndex: null,
            parentWidth: null,
            parentHeight: null,
            left: null,
            top: null,
            right: null,
            bottom: null,
            minHeight: null,
        };
    },

    beforeCreate() {
        this.stickDrag = false;
        this.bodyDrag = false;
        this.dimensionsBeforeMove = { pointerX: 0, pointerY: 0, x: 0, y: 0, w: 0, h: 0 };
        this.limits = {
            left: { min: null, max: null },
            right: { min: null, max: null },
            top: { min: null, max: null },
            bottom: { min: null, max: null },
        };

        this.currentStick = null;
    },

    mounted() {
        this.initResize()
        window.addEventListener('resize', this.initResize)
        this.domEvents = new Map([
            ['mousemove', this.move],
            ['mouseup', this.up],
            ['mouseleave', this.up],
            ['mousedown', this.deselect],
            ['touchmove', this.move],
            ['touchend', this.up],
            ['touchcancel', this.up],
            ['touchstart', this.up],
        ]);

        addEvents(this.domEvents);

        if (this.dragHandle) {
            [...this.$el.querySelectorAll(this.dragHandle)].forEach((dragHandle) => {
                dragHandle.setAttribute('data-drag-handle', this._uid);
            });
        }

        if (this.dragCancel) {
            [...this.$el.querySelectorAll(this.dragCancel)].forEach((cancelHandle) => {
                cancelHandle.setAttribute('data-drag-cancel', this._uid);
            });
        }
    },

    beforeDestroy() {
        removeEvents(this.domEvents);
        window.removeEventListener('resize', this.initResize)
    },

    methods: {
        initResize(notEmit) {
            this.parentElement = this.$el.parentNode;
            this.parentWidth = this.parentW ? this.parentW : this.parentElement.clientWidth;
            this.parentHeight = this.parentH ? this.parentH : this.parentElement.clientHeight;
            if (this.layout.left || !this.layout.right) {
                this.left = this.layout.left || 0;
            } else {
                this.left = this.parentWidth - (this.layout.width || 0) - (this.layout.right || 0)
            }
            if (this.layout.top || !this.layout.bottom) {
                this.top = this.layout.top || 0;
            } else {
                this.top = this.parentHeight - (this.layout.height || 0) - (this.layout.right || 0)
            }
            this.right = this.parentWidth - (this.layout.width === 'auto' ? this.$refs.container.scrollWidth : this.layout.width) - this.left;
            this.bottom = this.parentHeight - (this.layout.height === 'auto' ? this.$refs.container.scrollHeight : this.layout.height) - this.top;
            if (this.layout.width === 'max') {
                this.left = 0
                this.right = 0
            }
            if (this.layout.height === 'max') {
                this.top = 0;
                this.bottom = 0;
            }
            if (this.right < 0) {
                this.left += this.right
                this.right = 0
            }
            if (this.left < 0) {
                this.right += this.left
                this.left = 0
            }
            if (this.bottom < 0) {
                this.top += this.bottom
                this.bottom = 0
            }
            if (this.top < 0) {
                this.bottom += this.top
                this.top = 0
            }
            if (!notEmit) {
                this.$emit('resizing', this.rect);
            }
        },
        deselect() {
            if (this.preventActiveBehavior) {
                return;
            }
            this.active = false;
        },

        move(ev) {
            if (!this.stickDrag && !this.bodyDrag) {
                return;
            }

            ev.stopPropagation();

            const pageX = typeof ev.pageX !== 'undefined' ? ev.pageX : ev.touches[0].pageX;
            const pageY = typeof ev.pageY !== 'undefined' ? ev.pageY : ev.touches[0].pageY;

            const { dimensionsBeforeMove } = this;

            const delta = {
                x: (dimensionsBeforeMove.pointerX - pageX) / this.parentScaleX,
                y: (dimensionsBeforeMove.pointerY - pageY) / this.parentScaleY,
            };

            if (this.stickDrag) {
                this.stickMove(delta);
            }

            if (this.bodyDrag) {
                if (this.axis === 'x') {
                    delta.y = 0;
                } else if (this.axis === 'y') {
                    delta.x = 0;
                } else if (this.axis === 'none') {
                    return;
                }
                this.bodyMove(delta);
            }
        },

        up(ev) {
            if (this.stickDrag) {
                this.stickUp(ev);
            } else if (this.bodyDrag) {
                this.bodyUp(ev);
            }
        },

        bodyDown(ev) {
            if (['INPUT', 'TEXTAREA', 'CANVAS'].includes(ev.target.nodeName) || ev.target.className.includes('el-slider')) {
                return;
            }
            this.initResize()
            const { target, button } = ev;

            if (!this.preventActiveBehavior) {
                this.active = true;
            }

            if (button && button !== 0) {
                return;
            }

            this.$emit('clicked', ev);

            if (!this.active) {
                return;
            }

            if (this.dragHandle && target.getAttribute('data-drag-handle') !== this._uid.toString()) {
                return;
            }

            if (this.dragCancel && target.getAttribute('data-drag-cancel') === this._uid.toString()) {
                return;
            }

            if (typeof ev.stopPropagation !== 'undefined') {
                ev.stopPropagation();
            }

            if (typeof ev.preventDefault !== 'undefined') {
                ev.preventDefault();
            }

            if (this.isDraggable) {
                this.bodyDrag = true;
            }

            const pointerX = typeof ev.pageX !== 'undefined' ? ev.pageX : ev.touches[0].pageX;
            const pointerY = typeof ev.pageY !== 'undefined' ? ev.pageY : ev.touches[0].pageY;

            this.saveDimensionsBeforeMove({ pointerX, pointerY });

            if (this.parentLimitation) {
                this.limits = this.calcDragLimitation();
            }
        },

        bodyMove(delta) {
            const { dimensionsBeforeMove, parentWidth, parentHeight, gridX, gridY, width, height } = this;

            let newTop = dimensionsBeforeMove.top - delta.y;
            let newBottom = dimensionsBeforeMove.bottom + delta.y;
            let newLeft = dimensionsBeforeMove.left - delta.x;
            let newRight = dimensionsBeforeMove.right + delta.x;

            if (this.snapToGrid) {
                let alignTop = true;
                let alignLeft = true;

                let diffT = newTop - Math.floor(newTop / gridY) * gridY;
                let diffB = (parentHeight - newBottom) - Math.floor((parentHeight - newBottom) / gridY) * gridY;
                let diffL = newLeft - Math.floor(newLeft / gridX) * gridX;
                let diffR = (parentWidth - newRight) - Math.floor((parentWidth - newRight) / gridX) * gridX;

                if (diffT > (gridY / 2)) {
                    diffT -= gridY;
                }
                if (diffB > (gridY / 2)) {
                    diffB -= gridY;
                }
                if (diffL > (gridX / 2)) {
                    diffL -= gridX;
                }
                if (diffR > (gridX / 2)) {
                    diffR -= gridX;
                }

                if (Math.abs(diffB) < Math.abs(diffT)) {
                    alignTop = false;
                }
                if (Math.abs(diffR) < Math.abs(diffL)) {
                    alignLeft = false;
                }

                newTop -= (alignTop ? diffT : diffB);
                newBottom = parentHeight - height - newTop;
                newLeft -= (alignLeft ? diffL : diffR);
                newRight = parentWidth - width - newLeft;
            }

            ({
                newLeft: this.left,
                newRight: this.right,
                newTop: this.top,
                newBottom: this.bottom,
            } = this.rectCorrectionByLimit({ newLeft, newRight, newTop, newBottom }));

            this.$emit('dragging', this.rect);
        },

        bodyUp() {
            this.bodyDrag = false;
            this.$emit('dragging', this.rect);
            this.$emit('dragstop', this.rect);

            this.dimensionsBeforeMove = { pointerX: 0, pointerY: 0, x: 0, y: 0, w: 0, h: 0 };

            this.limits = {
                left: { min: null, max: null },
                right: { min: null, max: null },
                top: { min: null, max: null },
                bottom: { min: null, max: null },
            };
        },

        stickDown(stick, ev, force = false) {
            if ((!this.isResizable || !this.active) && !force) {
                return;
            }

            this.stickDrag = true;

            const pointerX = typeof ev.pageX !== 'undefined' ? ev.pageX : ev.touches[0].pageX;
            const pointerY = typeof ev.pageY !== 'undefined' ? ev.pageY : ev.touches[0].pageY;

            this.saveDimensionsBeforeMove({ pointerX, pointerY });

            this.currentStick = stick;

            this.limits = this.calcResizeLimits();
        },

        saveDimensionsBeforeMove({ pointerX, pointerY }) {
            this.dimensionsBeforeMove.pointerX = pointerX;
            this.dimensionsBeforeMove.pointerY = pointerY;

            this.dimensionsBeforeMove.left = this.left;
            this.dimensionsBeforeMove.right = this.right;
            this.dimensionsBeforeMove.top = this.top;
            this.dimensionsBeforeMove.bottom = this.bottom;

            this.dimensionsBeforeMove.width = this.width;
            this.dimensionsBeforeMove.height = this.height;

            this.aspectFactor = this.width / this.height;
        },

        stickMove(delta) {
            const {
                currentStick,
                dimensionsBeforeMove,
                gridY,
                gridX,
                snapToGrid,
                parentHeight,
                parentWidth,
            } = this;

            let newTop = dimensionsBeforeMove.top;
            let newBottom = dimensionsBeforeMove.bottom;
            let newLeft = dimensionsBeforeMove.left;
            let newRight = dimensionsBeforeMove.right;
            switch(currentStick[0]) {
                case 'b':
                    newBottom = dimensionsBeforeMove.bottom + delta.y;

                    if (snapToGrid) {
                        newBottom = parentHeight - Math.round((parentHeight - newBottom) / gridY) * gridY;
                    }

                    break;

                case 't':
                    newTop = dimensionsBeforeMove.top - delta.y;

                    if (snapToGrid) {
                        newTop = Math.round(newTop / gridY) * gridY;
                    }

                    break;
                default:
                    break;
            }

            switch(currentStick[1]) {
                case 'r':
                    newRight = dimensionsBeforeMove.right + delta.x;

                    if (snapToGrid) {
                        newRight = parentWidth - Math.round((parentWidth - newRight) / gridX) * gridX;
                    }

                    break;

                case 'l':
                    newLeft = dimensionsBeforeMove.left - delta.x;

                    if (snapToGrid) {
                        newLeft = Math.round(newLeft / gridX) * gridX;
                    }

                    break;
                default:
                    break;
            }

            ({
                newLeft,
                newRight,
                newTop,
                newBottom,
            } = this.rectCorrectionByLimit({ newLeft, newRight, newTop, newBottom }));

            if (this.aspectRatio) {
                ({
                    newLeft,
                    newRight,
                    newTop,
                    newBottom,
                } = this.rectCorrectionByAspectRatio({ newLeft, newRight, newTop, newBottom }));
            }

            this.left = newLeft;
            this.right = newRight;
            this.top = newTop;
            this.bottom = newBottom;

            this.$emit('resizing', this.rect);
        },

        stickUp() {
            this.stickDrag = false;
            this.dimensionsBeforeMove = {
                pointerX: 0,
                pointerY: 0,
                x: 0,
                y: 0,
                w: 0,
                h: 0,
            };
            this.limits = {
                left: { min: null, max: null },
                right: { min: null, max: null },
                top: { min: null, max: null },
                bottom: { min: null, max: null },
            };

            this.$emit('resizing', this.rect);
            this.$emit('resizestop', this.rect);
        },

        calcDragLimitation() {
            const { parentWidth, parentHeight } = this;

            return {
                left: { min: 0, max: parentWidth - this.width },
                right: { min: 0, max: parentWidth - this.width },
                top: { min: 0, max: parentHeight - this.height },
                bottom: { min: 0, max: parentHeight - this.height },
            };
        },

        calcResizeLimits() {
            const { aspectFactor, width, height, bottom, top, left, right } = this;
            let { minh: minHeight, minw: minWidth } = this;

            const parentLim = this.parentLimitation ? 0 : null;

            if (this.aspectRatio) {
                if (minWidth / minHeight > aspectFactor) {
                    minHeight = minWidth / aspectFactor;
                } else {
                    minWidth = aspectFactor * minHeight;
                }
            }

            const limits = {
                left: { min: parentLim, max: left + (width - minWidth) },
                right: { min: parentLim, max: right + (width - minWidth) },
                top: { min: parentLim, max: top + (height - minHeight) },
                bottom: { min: parentLim, max: bottom + (height - minHeight) },
            };

            if (this.aspectRatio) {
                const aspectLimits = {
                    left: {
                        min: left - (Math.min(top, bottom) * aspectFactor) * 2,
                        max: left + ((((height - minHeight) / 2) * aspectFactor) * 2),
                    },
                    right: {
                        min: right - (Math.min(top, bottom) * aspectFactor) * 2,
                        max: right + ((((height - minHeight) / 2) * aspectFactor) * 2),
                    },
                    top: {
                        min: top - (Math.min(left, right) / aspectFactor) * 2,
                        max: top + ((((width - minWidth) / 2) / aspectFactor) * 2),
                    },
                    bottom: {
                        min: bottom - (Math.min(left, right) / aspectFactor) * 2,
                        max: bottom + ((((width - minWidth) / 2) / aspectFactor) * 2),
                    },
                };

                if (this.currentStick[0] === 'm') {
                    limits.left = {
                        min: Math.max(limits.left.min, aspectLimits.left.min),
                        max: Math.min(limits.left.max, aspectLimits.left.max),
                    };
                    limits.right = {
                        min: Math.max(limits.right.min, aspectLimits.right.min),
                        max: Math.min(limits.right.max, aspectLimits.right.max),
                    };

                } else if (this.currentStick[1] === 'm') {
                    limits.top = {
                        min: Math.max(limits.top.min, aspectLimits.top.min),
                        max: Math.min(limits.top.max, aspectLimits.top.max),
                    };
                    limits.bottom = {
                        min: Math.max(limits.bottom.min, aspectLimits.bottom.min),
                        max: Math.min(limits.bottom.max, aspectLimits.bottom.max),
                    };
                }
            }

            return limits;
        },

        sideCorrectionByLimit(limit, current) {
            let value = current;

            if (limit.min !== null && current < limit.min) {
                value = limit.min;
            } else if (limit.max !== null && limit.max < current) {
                value = limit.max;
            }

            return value;
        },

        rectCorrectionByLimit(rect) {
            const { limits } = this;
            let { newRight, newLeft, newBottom, newTop } = rect;

            newLeft = this.sideCorrectionByLimit(limits.left, newLeft);
            newRight = this.sideCorrectionByLimit(limits.right, newRight);
            newTop = this.sideCorrectionByLimit(limits.top, newTop);
            newBottom = this.sideCorrectionByLimit(limits.bottom, newBottom);

            return {
                newLeft,
                newRight,
                newTop,
                newBottom,
            };
        },

        rectCorrectionByAspectRatio(rect) {
            let { newLeft, newRight, newTop, newBottom } = rect;
            const { parentWidth, parentHeight, currentStick, aspectFactor, dimensionsBeforeMove } = this;

            let newWidth = parentWidth - newLeft - newRight;
            let newHeight = parentHeight - newTop - newBottom;

            if (currentStick[1] === 'm') {
                const deltaHeight = newHeight - dimensionsBeforeMove.height;

                newLeft -= (deltaHeight * aspectFactor) / 2;
                newRight -= (deltaHeight * aspectFactor) / 2;
            } else if (currentStick[0] === 'm') {
                const deltaWidth = newWidth - dimensionsBeforeMove.width;

                newTop -= (deltaWidth / aspectFactor) / 2;
                newBottom -= (deltaWidth / aspectFactor) / 2;
            } else if (newWidth / newHeight > aspectFactor) {
                newWidth = aspectFactor * newHeight;

                if (currentStick[1] === 'l') {
                    newLeft = parentWidth - newRight - newWidth;
                } else {
                    newRight = parentWidth - newLeft - newWidth;
                }
            } else {
                newHeight = newWidth / aspectFactor;

                if (currentStick[0] === 't') {
                    newTop = parentHeight - newBottom - newHeight;
                } else {
                    newBottom = parentHeight - newTop - newHeight;
                }
            }

            return { newLeft, newRight, newTop, newBottom };
        },
    },

    computed: {
        positionStyle() {
            return {
                top: this.top + 'px',
                left: this.left + 'px',
                zIndex: this.zIndex,
            };
        },

        sizeStyle(){
            // let width = 0;
            // if (this.layout.width === 'auto') {
            //     width = 'auto'
            // } else if (this.layout.width === 'max') {
            //     width = this.parentWidth + 'px'
            // } else {
            //     width = this.width + 'px'
            // }
            // let height = 0
            // if (this.layout.height === 'auto') {
            //     height = 'auto'
            // } else if (this.layout.height === 'max') {
            //     height = this.parentHeight + 'px'
            // } else {
            //     height = this.height + 'px'
            // }
            return {
                width: this.layout.width == 'auto' ? 'auto' : this.width + 'px',
                height: this.layout.height == 'auto' ? 'auto' : this.height + 'px'
            };
        },

        vdrStick() {
            return (stick) => {
                const stickStyle = {
                    width: `${this.stickSize / this.parentScaleX}px`,
                    height: `${this.stickSize / this.parentScaleY}px`,
                };
                stickStyle[styleMapping.y[stick[0]]] = `${this.stickSize / this.parentScaleX / -2}px`;
                stickStyle[styleMapping.x[stick[1]]] = `${this.stickSize / this.parentScaleX / -2}px`;
                return stickStyle;
            };
        },

        width() {
            return this.parentWidth - this.left - this.right;
        },

        height() {
            return this.parentHeight - this.top - this.bottom;
        },

        rect() {
            return {
                left: Math.round(this.left),
                top: Math.round(this.top),
                width: Math.round(this.width),
                height: Math.round(this.height),
            };
        },
    },

    watch: {
        active(isActive) {
            if (isActive) {
                this.$emit('activated');
            } else {
                this.$emit('deactivated');
            }
        },

        isActive: {
            immediate: true,
            handler(val) {
                this.active = val;
            },
        },

        z: {
            immediate: true,
            handler(val) {
                if (val >= 0 || val === 'auto') {
                    this.zIndex = val;
                }
            },
        },

        layout: {
            handler(newVal, oldVal) {

                if (this.stickDrag || this.bodyDrag || (newVal.width === oldVal.width && newVal.height === oldVal.height && newVal.left === oldVal.left && newVal.top === oldVal.top)) {
                    return;
                }
                // this.top = newVal.top
                // this.left = newVal.left
                // this.right = this.parentWidth - this.left - newVal.width
                // this.bottom = this.parentHeight - this.top - newVal.height
                this.initResize(true)
                this.$nextTick(() => {
                    this.stickDrag = false;
                    this.bodyDrag = false;
                    this.dimensionsBeforeMove = {pointerX: 0, pointerY: 0, x: 0, y: 0, w: 0, h: 0,};
                    this.limits = {
                        left: { min: null, max: null },
                        right: { min: null, max: null },
                        top: { min: null, max: null },
                        bottom: { min: null, max: null },
                    };
                    this.$emit('dragging', newVal);
                    this.$emit('resizing', newVal);
                    this.$emit('dragstop', newVal);
                    this.$emit('resizestop', newVal);
                });
            },
            deep: true
        },

        parentW(val) {
            this.right = val - this.width - this.left;
            this.parentWidth = val;
        },

        parentH(val) {
            this.bottom = val - this.height - this.top;
            this.parentHeight = val;
        },
    },
};
