/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the Source EULA. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

import * as dom from 'vs/base/browser/dom';
import { DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle';

export interface IPointerMoveCallback {
	(event: PointerEvent): void;
}

export interface IOnStopCallback {
	(browserEvent?: PointerEvent | KeyboardEvent): void;
}

export class GlobalPointerMoveMonitor implements IDisposable {

	private readonly _hooks = new DisposableStore();
	private _pointerMoveCallback: IPointerMoveCallback | null = null;
	private _onStopCallback: IOnStopCallback | null = null;

	public dispose(): void {
		this.stopMonitoring(false);
		this._hooks.dispose();
	}

	public stopMonitoring(invokeStopCallback: boolean, browserEvent?: PointerEvent | KeyboardEvent): void {
		if (!this.isMonitoring()) {
			// Not monitoring
			return;
		}

		// Unhook
		this._hooks.clear();
		this._pointerMoveCallback = null;
		const onStopCallback = this._onStopCallback;
		this._onStopCallback = null;

		if (invokeStopCallback && onStopCallback) {
			onStopCallback(browserEvent);
		}
	}

	public isMonitoring(): boolean {
		return !!this._pointerMoveCallback;
	}

	public startMonitoring(
		initialElement: Element,
		pointerId: number,
		initialButtons: number,
		pointerMoveCallback: IPointerMoveCallback,
		onStopCallback: IOnStopCallback
	): void {
		if (this.isMonitoring()) {
			this.stopMonitoring(false);
		}
		this._pointerMoveCallback = pointerMoveCallback;
		this._onStopCallback = onStopCallback;

		let eventSource: Element | Window = initialElement;

		try {
			initialElement.setPointerCapture(pointerId);
			this._hooks.add(toDisposable(() => {
				try {
					initialElement.releasePointerCapture(pointerId);
				} catch (err) {
					// See https://github.com/microsoft/vscode/issues/161731
					//
					// `releasePointerCapture` sometimes fails when being invoked with the exception:
					//     DOMException: Failed to execute 'releasePointerCapture' on 'Element':
					//     No active pointer with the given id is found.
					//
					// There's no need to do anything in case of failure
				}
			}));
		} catch (err) {
			// See https://github.com/microsoft/vscode/issues/144584
			// See https://github.com/microsoft/vscode/issues/146947
			// `setPointerCapture` sometimes fails when being invoked
			// from a `mousedown` listener on macOS and Windows
			// and it always fails on Linux with the exception:
			//     DOMException: Failed to execute 'setPointerCapture' on 'Element':
			//     No active pointer with the given id is found.
			// In case of failure, we bind the listeners on the window
			eventSource = window;
		}

		this._hooks.add(dom.addDisposableListener(
			eventSource,
			dom.EventType.POINTER_MOVE,
			(e) => {
				if (e.buttons !== initialButtons) {
					// Buttons state has changed in the meantime
					this.stopMonitoring(true);
					return;
				}

				e.preventDefault();
				this._pointerMoveCallback!(e);
			}
		));

		this._hooks.add(dom.addDisposableListener(
			eventSource,
			dom.EventType.POINTER_UP,
			(e: PointerEvent) => this.stopMonitoring(true)
		));
	}
}
