const DEFAULT_OPTIONS = {
    touchStart: function () {},
    touchMove: function () {},
    touchEnd: function () {},
    touchCancel: function () {},
    multipointStart: function () {},
    multipointEnd: function () {},
    tap: function () {},
    doubleTap: function () {},
    longTap: function () {},
    singleTap: function () {},
    rotate: function () {},
    pinch: function () {},
    pressMove: function () {},
    swipe: function () {}
};
export default class MinaTouch {
    constructor(_page, name, option = {}) {
        this.preV = {
            x: null,
            y: null
        };
        this.pinchStartLen = null;
        this.scale = 1;
        this.isDoubleTap = false;
        this.delta = null;
        this.last = null;
        this.now = null;
        this.tapTimeout = null;
        this.singleTapTimeout = null;
        this.longTapTimeout = null;
        this.swipeTimeout = null;
        this.x1 = this.x2 = this.y1 = this.y2 = null;
        this.preTapPosition = {
            x: null,
            y: null
        };
        this.lastZoom = 1;
        this.tempZoom = 1;

        try {
            if (this._checkBeforeCreate(_page, name)) {
                this._name = name;
                this._option = { ...DEFAULT_OPTIONS, ...option };
                _page[name] = this;

                this._bindFunc(_page);
            }
        } catch (error) {
            console.error(error);
        }
    }

    _checkBeforeCreate(_page, name) {
        if (!_page || !name) {
            throw new Error('MinaTouch实例化时，必须传入page对象和引用名');
        }

        if (_page[name]) {
            throw new Error('MinaTouch实例化error： ' + name + ' 已经存在page中');
        }

        return true;
    }

    _bindFunc(_page) {
        let funcNames = ['start', 'move', 'end', 'cancel'];

        for (let funcName of funcNames) {
            _page[this._name + '.' + funcName] = this[funcName].bind(this);
        }
    }

    start(evt) {
        if (!evt.touches) {
            return;
        }

        this.now = Date.now();

        if (evt.touches[0].pageX == null) {
            this.x1 = evt.touches[0].x;
        } else {
            this.x1 = evt.touches[0].pageX;
        }

        if (evt.touches[0].pageY == null) {
            this.y1 = evt.touches[0].y;
        } else {
            this.y1 = evt.touches[0].pageY;
        }

        this.delta = this.now - (this.last || this.now);

        this._option.touchStart(evt);

        if (this.preTapPosition.x !== null) {
            this.isDoubleTap = this.delta > 0 && this.delta <= 250 && Math.abs(this.preTapPosition.x - this.x1) < 30 && Math.abs(this.preTapPosition.y - this.y1) < 30;
        }

        this.preTapPosition.x = this.x1;
        this.preTapPosition.y = this.y1;
        this.last = this.now;
        let preV = this.preV;
        let len = evt.touches.length;
        if (len > 1) {
            this._cancelLongTap();

            this._cancelSingleTap();

            let otx = evt.touches[1].pageX == null ? evt.touches[1].x : evt.touches[1].pageX;
            let oty = evt.touches[1].pageY == null ? evt.touches[1].y : evt.touches[1].pageY;
            let v = {
                x: otx - this.x1,
                y: oty - this.y1
            };
            preV.x = v.x;
            preV.y = v.y;
            this.pinchStartLen = getLen(preV);

            this._option.multipointStart(evt);
        }

        this.longTapTimeout = setTimeout(
            function () {
                evt.type = 'longTap';

                this._option.longTap(evt);
            }.bind(this),
            750
        );
    }

    move(evt) {
        if (!evt.touches) {
            return;
        }

        let preV = this.preV;
        let len = evt.touches.length;
        let currentX = evt.touches[0].pageX == null ? evt.touches[0].x : evt.touches[0].pageX;
        let currentY = evt.touches[0].pageY == null ? evt.touches[0].y : evt.touches[0].pageY;
        this.isDoubleTap = false;

        if (len > 1) {
			// #ifdef MP-TOUTIAO
			if(!this.toutuaoMultipointStart){
				this._cancelLongTap();
				
				this._cancelSingleTap();
				
				let otx = evt.touches[1].pageX == null ? evt.touches[1].x : evt.touches[1].pageX;
				let oty = evt.touches[1].pageY == null ? evt.touches[1].y : evt.touches[1].pageY;
				let v = {
				    x: otx - currentX,
				    y: oty - currentX
				};
				preV.x = v.x;
				preV.y = v.y;
				this.pinchStartLen = getLen(preV);
				
				this._option.multipointStart(evt);
				this.toutuaoMultipointStart = true
			}
			// #endif
            let otx = evt.touches[1].pageX == null ? evt.touches[1].x : evt.touches[1].pageX;
            let oty = evt.touches[1].pageY == null ? evt.touches[1].y : evt.touches[1].pageY;
            let v = {
                x: otx - currentX,
                y: oty - currentY
            };
            if (preV.x !== null) {
                if (this.pinchStartLen > 0) {
                    evt.singleZoom = getLen(v) / this.pinchStartLen;
                    evt.zoom = evt.singleZoom * this.lastZoom;
                    this.tempZoom = evt.zoom;
                    evt.type = 'pinch';

                    this._option.pinch(evt);
                }

                evt.angle = getRotateAngle(v, preV);
                evt.type = 'rotate';

                this._option.rotate(evt);
            }

            preV.x = v.x;
            preV.y = v.y;
        } else {
            if (this.x2 !== null) {
                evt.deltaX = currentX - this.x2;
                evt.deltaY = currentY - this.y2;
            } else {
                evt.deltaX = 0;
                evt.deltaY = 0;
            }

            this._option.pressMove(evt);
        }

        this._option.touchMove(evt);

        this._cancelLongTap();

        this.x2 = currentX;
        this.y2 = currentY;

        if (len > 1) {
            // evt.preventDefault();
        }
    }

    end(evt) {
        if (!evt.changedTouches) {
            return;
        }

        this._cancelLongTap();

        let self = this;
        evt.direction = this._swipeDirection(this.x1, this.x2, this.y1, this.y2); //在结束钩子都加入方向判断，但触发swipe瞬时必须位移大于30

        if (evt.touches.length < 2) {
            this.lastZoom = this.tempZoom;

            this._option.multipointEnd(evt);
			this.toutuaoMultipointStart = false
        }

        this._option.touchEnd(evt); //swipe

        if ((this.x2 && Math.abs(this.x1 - this.x2) > 30) || (this.y2 && Math.abs(this.y1 - this.y2) > 30)) {
            // evt.direction = this._swipeDirection(this.x1, this.x2, this.y1, this.y2);
            this.swipeTimeout = setTimeout(function () {
                evt.type = 'swipe';

                self._option.swipe(evt);
            }, 0);
        } else {
            this.tapTimeout = setTimeout(function () {
                evt.type = 'tap';

                self._option.tap(evt); // trigger double tap immediately

                if (self.isDoubleTap) {
                    evt.type = 'doubleTap';

                    self._option.doubleTap(evt);

                    clearTimeout(self.singleTapTimeout);
                    self.isDoubleTap = false;
                }
            }, 0);

            if (!self.isDoubleTap) {
                self.singleTapTimeout = setTimeout(function () {
                    self._option.singleTap(evt);
                }, 250);
            }
        }

        this.preV.x = 0;
        this.preV.y = 0;
        this.scale = 1;
        this.pinchStartLen = null;
        this.x1 = this.x2 = this.y1 = this.y2 = null;
    }

    cancel(evt) {
        clearTimeout(this.singleTapTimeout);
        clearTimeout(this.tapTimeout);
        clearTimeout(this.longTapTimeout);
        clearTimeout(this.swipeTimeout);

        this._option.touchCancel(evt);
    }

    _cancelLongTap() {
        clearTimeout(this.longTapTimeout);
    }

    _cancelSingleTap() {
        clearTimeout(this.singleTapTimeout);
    }

    _swipeDirection(x1, x2, y1, y2) {
        return Math.abs(x1 - x2) >= Math.abs(y1 - y2) ? (x1 - x2 > 0 ? 'Left' : 'Right') : y1 - y2 > 0 ? 'Up' : 'Down';
    }

    destroy() {
        if (this.singleTapTimeout) {
            clearTimeout(this.singleTapTimeout);
        }

        if (this.tapTimeout) {
            clearTimeout(this.tapTimeout);
        }

        if (this.longTapTimeout) {
            clearTimeout(this.longTapTimeout);
        }

        if (this.swipeTimeout) {
            clearTimeout(this.swipeTimeout);
        }

        this._option.rotate = null;
        this._option.touchStart = null;
        this._option.multipointStart = null;
        this._option.multipointEnd = null;
        this._option.pinch = null;
        this._option.swipe = null;
        this._option.tap = null;
        this._option.doubleTap = null;
        this._option.longTap = null;
        this._option.singleTap = null;
        this._option.pressMove = null;
        this._option.touchMove = null;
        this._option.touchEnd = null;
        this._option.touchCancel = null;
        this.preV =
            this.pinchStartLen =
            this.scale =
            this.isDoubleTap =
            this.delta =
            this.last =
            this.now =
            this.tapTimeout =
            this.singleTapTimeout =
            this.longTapTimeout =
            this.swipeTimeout =
            this.x1 =
            this.x2 =
            this.y1 =
            this.y2 =
            this.preTapPosition =
            this.rotate =
            this.touchStart =
            this.multipointStart =
            this.multipointEnd =
            this.pinch =
            this.swipe =
            this.tap =
            this.doubleTap =
            this.longTap =
            this.singleTap =
            this.pressMove =
            this.touchMove =
            this.touchEnd =
            this.touchCancel =
                null;
        return null;
    }
}

function getLen(v) {
    return Math.sqrt(v.x * v.x + v.y * v.y);
}

function dot(v1, v2) {
    return v1.x * v2.x + v1.y * v2.y;
}

function getAngle(v1, v2) {
    let mr = getLen(v1) * getLen(v2);

    if (mr === 0) {
        return 0;
    }

    let r = dot(v1, v2) / mr;

    if (r > 1) {
        r = 1;
    }

    return Math.acos(r);
}

function cross(v1, v2) {
    return v1.x * v2.y - v2.x * v1.y;
}

function getRotateAngle(v1, v2) {
    let angle = getAngle(v1, v2);

    if (cross(v1, v2) > 0) {
        angle *= -1;
    }

    return (angle * 180) / Math.PI;
}
