import { InputSource } from '../input.js';

const PASSIVE = {
		passive: false
};
const KEY_CODES = {
		A: 0,
		B: 1,
		C: 2,
		D: 3,
		E: 4,
		F: 5,
		G: 6,
		H: 7,
		I: 8,
		J: 9,
		K: 10,
		L: 11,
		M: 12,
		N: 13,
		O: 14,
		P: 15,
		Q: 16,
		R: 17,
		S: 18,
		T: 19,
		U: 20,
		V: 21,
		W: 22,
		X: 23,
		Y: 24,
		Z: 25,
		'0': 26,
		'1': 27,
		'2': 28,
		'3': 29,
		'4': 30,
		'5': 31,
		'6': 32,
		'7': 33,
		'8': 34,
		'9': 35,
		UP: 36,
		DOWN: 37,
		LEFT: 38,
		RIGHT: 39,
		SPACE: 40,
		SHIFT: 41,
		CTRL: 42
};
const KEY_COUNT = Object.keys(KEY_CODES).length;
const array = Array(KEY_COUNT).fill(0);
class KeyboardMouseSource extends InputSource {
		static{
				this.keyCode = KEY_CODES;
		}
		constructor({ pointerLock = false } = {}){
				super({
						key: Array(KEY_COUNT).fill(0),
						button: [
								0,
								0,
								0
						],
						mouse: [
								0,
								0
						],
						wheel: [
								0
						]
				}), this._pointerId = -1, this._keyMap = new Map(), this._keyPrev = Array(KEY_COUNT).fill(0), this._keyNow = Array(KEY_COUNT).fill(0), this._button = Array(3).fill(0);
				this._pointerLock = pointerLock ?? false;
				const { keyCode } = KeyboardMouseSource;
				for(let i = 0; i < 26; i++){
						const code = `Key${String.fromCharCode('A'.charCodeAt(0) + i)}`;
						this._keyMap.set(code, keyCode.A + i);
				}
				for(let i = 0; i < 10; i++){
						const code = `Digit${i}`;
						this._keyMap.set(code, keyCode['0'] + i);
				}
				this._keyMap.set('ArrowUp', keyCode.UP);
				this._keyMap.set('ArrowDown', keyCode.DOWN);
				this._keyMap.set('ArrowLeft', keyCode.LEFT);
				this._keyMap.set('ArrowRight', keyCode.RIGHT);
				this._keyMap.set('Space', keyCode.SPACE);
				this._keyMap.set('ShiftLeft', keyCode.SHIFT);
				this._keyMap.set('ShiftRight', keyCode.SHIFT);
				this._keyMap.set('ControlLeft', keyCode.CTRL);
				this._keyMap.set('ControlRight', keyCode.CTRL);
				this._onWheel = this._onWheel.bind(this);
				this._onPointerDown = this._onPointerDown.bind(this);
				this._onPointerMove = this._onPointerMove.bind(this);
				this._onPointerUp = this._onPointerUp.bind(this);
				this._onContextMenu = this._onContextMenu.bind(this);
				this._onKeyDown = this._onKeyDown.bind(this);
				this._onKeyUp = this._onKeyUp.bind(this);
		}
		_onWheel(event) {
				event.preventDefault();
				this.deltas.wheel.append([
						event.deltaY
				]);
		}
		_onPointerDown(event) {
				if (event.pointerType !== 'mouse') {
						return;
				}
				if (this._pointerLock) {
						if (document.pointerLockElement !== this._element) {
								this._element?.requestPointerLock();
						}
				} else {
						this._element?.setPointerCapture(event.pointerId);
				}
				this._clearButtons();
				this._button[event.button] = 1;
				this.deltas.button.append(this._button);
				if (this._pointerId !== -1) {
						return;
				}
				this._pointerId = event.pointerId;
		}
		_onPointerMove(event) {
				if (event.pointerType !== 'mouse') {
						return;
				}
				if (event.target !== this._element) {
						return;
				}
				if (this._pointerLock) {
						if (document.pointerLockElement !== this._element) {
								return;
						}
				} else {
						if (this._pointerId !== event.pointerId) {
								return;
						}
				}
				this.deltas.mouse.append([
						event.movementX,
						event.movementY
				]);
		}
		_onPointerUp(event) {
				if (event.pointerType !== 'mouse') {
						return;
				}
				if (!this._pointerLock) {
						this._element?.releasePointerCapture(event.pointerId);
				}
				this._clearButtons();
				this.deltas.button.append(this._button);
				if (this._pointerId !== event.pointerId) {
						return;
				}
				this._pointerId = -1;
		}
		_onContextMenu(event) {
				event.preventDefault();
		}
		_onKeyDown(event) {
				if (this._pointerLock && document.pointerLockElement !== this._element) {
						return;
				}
				event.stopPropagation();
				this._setKey(event.code, 1);
		}
		_onKeyUp(event) {
				event.stopPropagation();
				this._setKey(event.code, 0);
		}
		_clearButtons() {
				for(let i = 0; i < this._button.length; i++){
						if (this._button[i] === 1) {
								this._button[i] = -1;
								continue;
						}
						this._button[i] = 0;
				}
		}
		_setKey(code, value) {
				if (!this._keyMap.has(code)) {
						return;
				}
				this._keyNow[this._keyMap.get(code) ?? 0] = value;
		}
		attach(element) {
				super.attach(element);
				this._element = element;
				this._element.addEventListener('wheel', this._onWheel, PASSIVE);
				this._element.addEventListener('pointerdown', this._onPointerDown);
				this._element.addEventListener('pointermove', this._onPointerMove);
				this._element.addEventListener('pointerup', this._onPointerUp);
				this._element.addEventListener('pointercancel', this._onPointerUp);
				this._element.addEventListener('pointerleave', this._onPointerUp);
				this._element.addEventListener('contextmenu', this._onContextMenu);
				window.addEventListener('keydown', this._onKeyDown, false);
				window.addEventListener('keyup', this._onKeyUp, false);
		}
		detach() {
				if (!this._element) {
						return;
				}
				this._element.removeEventListener('wheel', this._onWheel, PASSIVE);
				this._element.removeEventListener('pointerdown', this._onPointerDown);
				this._element.removeEventListener('pointermove', this._onPointerMove);
				this._element.removeEventListener('pointerup', this._onPointerUp);
				this._element.removeEventListener('pointercancel', this._onPointerUp);
				this._element.removeEventListener('pointerleave', this._onPointerUp);
				this._element.removeEventListener('contextmenu', this._onContextMenu);
				window.removeEventListener('keydown', this._onKeyDown, false);
				window.removeEventListener('keyup', this._onKeyUp, false);
				this._keyNow.fill(0);
				this._keyPrev.fill(0);
				super.detach();
		}
		read() {
				for(let i = 0; i < array.length; i++){
						array[i] = this._keyNow[i] - this._keyPrev[i];
						this._keyPrev[i] = this._keyNow[i];
				}
				this.deltas.key.append(array);
				return super.read();
		}
}

export { KeyboardMouseSource };
