/*
 * Copyright (C) 2016-2020 Kano Computing Ltd.
 * License: http://www.gnu.org/licenses/gpl-2.0.txt GNU General Public License v2
 */
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
    return c > 3 && r && Object.defineProperty(target, key, r), r;
};
import { Part } from '../../part.js';
import { part, property, component } from '../../decorators.js';
import { subscribeDOM, EventEmitter } from '@kano/common/index.js';
import { PartComponent } from '../../component.js';
class TouchComponent extends PartComponent {
    constructor() {
        super(...arguments);
        this.touchStart = new EventEmitter();
        this.touchMove = new EventEmitter();
        this.touchEnd = new EventEmitter();
    }
}
__decorate([
    property({ type: EventEmitter, value: new EventEmitter(), noReset: true })
], TouchComponent.prototype, "touchStart", void 0);
__decorate([
    property({ type: EventEmitter, value: new EventEmitter(), noReset: true })
], TouchComponent.prototype, "touchMove", void 0);
__decorate([
    property({ type: EventEmitter, value: new EventEmitter(), noReset: true })
], TouchComponent.prototype, "touchEnd", void 0);
let TouchPart = class TouchPart extends Part {
    constructor() {
        super();
        this._scale = 1;
        this._width = 0;
        this._height = 0;
        this._rect = null;
        this._dx = 0;
        this._dy = 0;
        this._ongoingTouches = [];
        this._lastMoveEvent = null;
        this.core = this._components.get('core');
    }
    /**
     * Gets the array index of an on-going touch.
     *
     * @param id
     *   The identifier of the Touch object.
     */
    _findOngoingTouch(touch) {
        return this._ongoingTouches.findIndex(ongoingTouch => ongoingTouch.id === touch.identifier);
    }
    _addOngoingTouch(touch) {
        if (!this._rect) {
            return;
        }
        // Adjust the touch position by making the coordinates relative to the top left corner of the output
        // Also applies the scale
        const x = Math.max(0, Math.min(this._width, (touch.clientX - this._rect.left) * this._scale));
        const y = Math.max(0, Math.min(this._height, (touch.clientY - this._rect.top) * this._scale));
        // The value that will be stored
        const touchValue = { id: touch.identifier, x: x, y: y, dx: 0, dy: 0 };
        const existingIndex = this._findOngoingTouch(touch);
        // If a touch with this id already exists, update it.
        if (existingIndex >= 0) {
            // Calculate dx and dy
            touchValue.dx = touchValue.x - this._ongoingTouches[existingIndex].x;
            touchValue.dy = touchValue.y - this._ongoingTouches[existingIndex].y;
            this._ongoingTouches[existingIndex] = touchValue;
        }
        else {
            // Otherwise add it to the array.
            this._ongoingTouches.push(touchValue);
        }
    }
    _removeOngoingTouch(touch) {
        let touchIndex = this._findOngoingTouch(touch);
        if (touchIndex >= 0) {
            this._ongoingTouches.splice(touchIndex, 1);
        }
    }
    onInstall(context) {
        // Listen to the resize event ot update the rect and scale
        context.dom.onDidResize(() => {
            this.resize(context);
        });
        subscribeDOM(context.dom.root, 'touchstart', (e) => {
            // Add new touches to the ongoing touches list.
            const touches = e.changedTouches;
            for (let touch of touches) {
                this._addOngoingTouch(touch);
            }
            this.core.touchStart.fire();
        }, this, this.subscriptions);
        subscribeDOM(context.dom.root, 'touchmove', (e) => {
            e.preventDefault();
            const touches = e.changedTouches;
            for (let touch of touches) {
                this._addOngoingTouch(touch);
            }
            this.core.touchMove.fire();
        }, this, this.subscriptions);
        subscribeDOM(context.dom.root, 'touchend', (e) => {
            // Remove all ended touches from ongoing touches list.
            const touches = e.changedTouches;
            for (let touch of touches) {
                this._removeOngoingTouch(touch);
            }
            // Fire touch end when no ongoing touches left.
            this.core.touchEnd.fire();
        }, this, this.subscriptions);
        subscribeDOM(context.dom.root, 'touchcancel', (e) => {
            const touches = e.changedTouches;
            for (let touch of touches) {
                this._removeOngoingTouch(touch);
            }
            this.core.touchEnd.fire();
        }, this, this.subscriptions);
        // Trigger an initial resize to populate the scale and rect
        this.resize(context);
        // On mouse down fire the down event. Also starts listening to the mouse up event.
        // The mouse events are added to the ongoingTouch array with an identifier of -2
        // the identifier is set as negative to distinguish it from genuine touch events, also avoiding -1 because of findIndex()
        subscribeDOM(context.dom.root, 'mousedown', (e) => {
            this._addOngoingTouch(Object.assign(e, { identifier: -2 }));
            const moveSub = subscribeDOM(context.dom.root, 'mousemove', (e) => {
                this._removeOngoingTouch(Object.assign(e, { identifier: -2 }));
                this._addOngoingTouch(Object.assign(e, { identifier: -2 }));
                this.core.touchMove.fire();
            }, this, this.subscriptions);
            const upSub = subscribeDOM(window, 'mouseup', () => {
                if (moveSub) {
                    moveSub.dispose();
                }
                upSub.dispose();
                this._removeOngoingTouch(Object.assign(e, { identifier: -2 }));
                this.core.touchEnd.fire();
            });
            this.core.touchStart.fire();
        }, this, this.subscriptions);
    }
    resize(context) {
        this._rect = context.dom.root.getBoundingClientRect();
        this._width = context.visuals.width;
        this._height = context.visuals.height;
        this._scale = context.visuals.width / this._rect.width;
    }
    onTouchStart(callback) {
        return this.core.touchStart.event(callback, null, this.userSubscriptions);
    }
    onTouchEnd(callback) {
        return this.core.touchEnd.event(callback, null, this.userSubscriptions);
    }
    onTouchMove(callback) {
        return this.core.touchMove.event(callback, null, this.userSubscriptions);
    }
    get firstDx() {
        if (this._ongoingTouches.length == 0) {
            return 0;
        }
        return this._ongoingTouches[0].dx;
    }
    get firstDy() {
        if (this._ongoingTouches.length == 0) {
            return 0;
        }
        return this._ongoingTouches[0].dy;
    }
    // Returns the first point's x
    get firstX() {
        return this._ongoingTouches.length ? this._ongoingTouches[0].x : null;
    }
    // Returns the first point's y
    get firstY() {
        return this._ongoingTouches.length ? this._ongoingTouches[0].y : null;
    }
    // Iterate through all points. Exposes x and y of each points through callback arguments
    forEach(cb) {
        this._ongoingTouches.forEach(({ x, y }) => cb(x, y));
    }
};
__decorate([
    component(TouchComponent)
], TouchPart.prototype, "core", void 0);
TouchPart = __decorate([
    part('touch')
], TouchPart);
export { TouchPart };
//# sourceMappingURL=touch.js.map