import lime.ui.GamepadAxis;
import lime.ui.GamepadButton;
import lime.ui.KeyCode;
import lime.ui.KeyModifier;
import openfl.Assets;
import openfl.display.Sprite;
import openfl.events.Event;
import openfl.geom.Point;
import openfl.media.SoundChannel;
import openfl.media.SoundTransform;

class State extends Sprite {
	static final GAMEPAD_THRESHOLD:Float = 0.25;

	var gamepadMovement:Point = new Point();
	var gamepadDirection:Point = new Point();

	var inputKeys:Map<KeyCode, Bool> = [];

	var inputMode:InputMode = KEYBOARD;

	var gamepadButtons:Map<GamepadButton, Bool> = [];

	var playingSounds:Map<String, Array<SoundChannel>> = [];

	public function new() {
		super();
	}

	public function inputMovementX():Float {
		switch (inputMode) {
			case KEYBOARD:
				return keyMovementX();
			case GAMEPAD:
				return gamepadMovement.x;
		}
	}

	public function inputMovementY():Float {
		switch (inputMode) {
			case KEYBOARD:
				return keyMovementY();
			case GAMEPAD:
				return gamepadMovement.y;
		}
	}

	public function inputDirectionX():Float {
		switch (inputMode) {
			case KEYBOARD:
				return keyDirectionX();
			case GAMEPAD:
				return gamepadDirection.x;
		}
	}

	public function keyMovementX():Float {
		return (inputKeys[A] ? -1 : 0) + (inputKeys[D] ? 1 : 0);
	}

	public function keyMovementY():Float {
		return (inputKeys[W] ? 1 : 0) + (inputKeys[S] ? -1 : 0);
	}

	public function keyDirectionX():Float {
		return (inputKeys[LEFT] ? -1 : 0) + (inputKeys[RIGHT] ? 1 : 0);
	}

	public function keyDown(keyCode:KeyCode, keyModifier:KeyModifier) {
		inputMode = KEYBOARD;
		inputKeys[keyCode] = true;
	}

	public function keyUp(keyCode:KeyCode, keyModifier:KeyModifier) {
		inputMode = KEYBOARD;
		inputKeys[keyCode] = false;
	}

	public function gamepadAxis(axis:GamepadAxis, value:Float) {
		inputMode = GAMEPAD;

		if (Math.abs(value) < GAMEPAD_THRESHOLD)
			value = 0;

		switch (axis) {
			case LEFT_X:
				gamepadMovement.x = value;
			case LEFT_Y:
				gamepadMovement.y = -value;
			case RIGHT_X:
				gamepadDirection.x = value;
			case _:
		}
	}

	public function gamepadButtonDown(button:GamepadButton) {
		gamepadButtons[button] = true;
	}

	public function gamepadButtonUp(button:GamepadButton) {
		gamepadButtons[button] = false;
	}

	public function update(delta:Float) {}

	public function render() {}

	var pausedSounds:Map<String, Array<{position:Float, transform:SoundTransform}>>;

	public function pauseSounds() {
		pausedSounds = [];

		for (asset => channels in playingSounds) {
			pausedSounds[asset] = [
				for (channel in channels)
					({
						position:channel.position, transform:channel.soundTransform
					})
			];

			for (channel in channels)
				channel.stop();
		}

		playingSounds = [];
	}

	public function resumeSounds() {
		if (pausedSounds != null) {
			for (asset => data in pausedSounds) {
				for (d in data) {
					playSound(asset, d.position, d.transform);
				}
			}

			pausedSounds = null;
		}
	}

	public function playSound(asset:String, ?position:Float, ?loops:Int = 0, ?transform:SoundTransform):SoundChannel {
		// trace('Play sound: ${asset}', transform == null ? '[default]' : '[${transform.volume}, ${transform.pan}]');

		var channel = Assets.getSound(asset).play(position, loops, transform);

		if (!playingSounds.exists(asset))
			playingSounds[asset] = [channel];
		else
			playingSounds[asset].push(channel);

		channel.addEventListener(Event.SOUND_COMPLETE, (event:Event) -> {
			playingSounds[asset].remove(cast event.target);
		});

		return channel;
	}

	public function stopSound(channel:SoundChannel) {
		for (asset => channels in playingSounds) {
			for (c in channels) {
				if (c == channel) {
					c.stop();
					playingSounds[asset].remove(c);
				}
			}
		}
	}
}
