class_name PlayerInput extends Node

enum Device {
    Unknown,
    KeyboardMouse,
    Gamepad
}

class Action:
    static var Run = 'run'
    static var Interact = 'interact'
    static var Ready = 'ready'
    static var Fire = 'fire'

signal walk
signal run
signal interact
signal ready_weapon
signal unready_weapon
signal fire

var last_known_device: Device = Device.Unknown

var _is_running: bool = false
var is_running: bool:
    get: return _is_running
    set(value):
        if _is_running != value:
            _is_running = value
            if value: run.emit()
            else: walk.emit()

var _is_interacting: bool = false
var is_interacting: bool:
    get: return _is_interacting
    set(value):
        if _is_interacting != value:
            _is_interacting = value
            if value: interact.emit()

var _is_weapon_ready: bool = false
var is_weapon_ready: bool:
    get: return _is_weapon_ready
    set(value):
        if _is_weapon_ready != value:
            _is_weapon_ready = value
            if value: ready_weapon.emit()
            else: unready_weapon.emit()

var _is_firing: bool = false
var is_firing: bool:
    get: return _is_firing
    set(value):
        if _is_firing != value:
            _is_firing = value
            if value: fire.emit()

var movement_dir: Vector2:
  get: return Input.get_vector(
      'move_left', 'move_right',
      'move_up', 'move_down'
    ).normalized()

var _mouse_position: Vector2:
    get:
        var viewport = get_viewport()
        var mouse_pos = viewport.get_mouse_position()
        var cam = viewport.get_camera_3d()
        var space_state = cam.get_world_3d().direct_space_state
        var origin = cam.project_ray_origin(mouse_pos)
        var end = origin + (cam.project_ray_normal(mouse_pos) * cam.far)
        var query = PhysicsRayQueryParameters3D.create(origin, end)

        var result = space_state.intersect_ray(query)
        var position = result.get('position') as Vector3
        return Vector2(position.x, position.z)

var _analog_dir: Vector2:
    get: return Input.get_vector(
        'look_left', 'look_right',
        'look_up', 'look_down'
    ).normalized()

func _process(_delta: float) -> void:
    is_running = Input.is_action_pressed(Action.Run)

func _get_device(event: InputEvent) -> Device:
    if event is InputEventMouse or event is InputEventKey:
        return Device.KeyboardMouse
    elif event is InputEventJoypadButton or event is InputEventJoypadMotion:
        return Device.Gamepad
    else:
        return Device.Unknown

func _input(event: InputEvent) -> void:
    last_known_device = _get_device(event)

    if event.is_action_pressed(Action.Interact):
        is_interacting = true

    if event.is_action_released(Action.Interact):
        is_interacting = false

    if event.is_action_pressed(Action.Ready):
        is_weapon_ready = true

    if event.is_action_released(Action.Ready):
        is_weapon_ready = false

    if event.is_action_pressed(Action.Fire):
        is_firing = true

    if event.is_action_released(Action.Fire):
        is_firing = false

func next_velocity(speed: float, dir: Vector2 = movement_dir) -> Vector3:
  return Vector3(dir.x, 0, dir.y) * speed

func get_look_target(position: Vector3) -> Option:
    match last_known_device:
        Device.KeyboardMouse:
            var pos = _mouse_position
            return Option.some(Vector3(pos.x, position.y, pos.y))
        Device.Gamepad:
            var pos = _analog_dir
            if pos.is_zero_approx():
                return Option.none
            return Option.some(Vector3(pos.x, 0, pos.y) + position)
        _: return Option.none
