// Copyright 2025 International Digital Economy Academy
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

///|
suberror GilError {
  NotImplemented
  InvalidAxisToBtn
}

///|
let _gilrs_error_keepalive : Array[GilError] = [NotImplemented]

///|
pub fn GilError::is_invalid_axis_to_btn(self : GilError) -> Bool {
  match self {
    InvalidAxisToBtn => true
    _ => false
  }
}

///|
pub struct GamepadData {
  state : GamepadState
  mut connected : Bool
  mut mapping : Mapping
  mut name : String
  mut uuid : Uuid
  mut vendor_id : Int?
  mut product_id : Int?
  mut ff_supported : Bool
  mut listener_position : (Double, Double, Double)
  mut power_info : PowerInfo
  mut axes : Array[Code]
  mut buttons : Array[Code]
  mut axis_info : Array[(Code, AxisInfo)]
  deadzones : Array[(Code, Double)]
  have_sent_nonzero_for_axis : Array[Bool]
}

///|
fn GamepadData::new() -> GamepadData {
  {
    state: GamepadState::new(),
    connected: true,
    mapping: Mapping::new(),
    name: "",
    uuid: Uuid::nil(),
    vendor_id: None,
    product_id: None,
    ff_supported: false,
    listener_position: (0.0, 0.0, 0.0),
    power_info: Unknown,
    axes: [],
    buttons: [],
    axis_info: [],
    deadzones: [],
    have_sent_nonzero_for_axis: Array::make(6, false),
  }
}

///|
const FF_TICK_DURATION_MS : Int64 = 50L

///|
priv struct FfMagnitude {
  strong : Int
  weak : Int
}

///|
priv enum FfEffectState {
  Playing(Int)
  Stopped
}

///|
struct FfEffectSource {
  token : Int
  base_effects : Array[BaseEffect]
  mut devices : Array[Int]
  mut repeat_mode : FfRepeat
  mut distance_model : DistanceModel
  mut position : (Double, Double, Double)
  mut gain : Double
  mut state : FfEffectState
  strong : Int
  weak : Int
}

///|
pub struct Gil {
  mut counter : Int64
  update_state : Bool
  mut default_filters : Bool
  mut axis_to_btn_pressed : Double
  mut axis_to_btn_released : Double
  events : Array[Event]
  mut events_head : Int
  gamepads_data : Array[GamepadData]
  mappings : MappingDb
  mut now_ms : Int64
  mut next_effect_token : Int
  ff_tick_base_ms : Int64
  mut ff_tick : Int
  mut ff_dirty : Bool
  ff_effects : Array[FfEffectSource]
  mut ff_events : Array[Event]
  mut ff_events_head : Int
  backend : NativeBackend?
}

///|
pub fn Gil::new_mock(
  gamepad_count : Int,
  update_state? : Bool = true,
  default_filters? : Bool = true,
) -> Gil {
  let data : Array[GamepadData] = []
  for _ in 0.. Gil {
  {
    counter: 0L,
    update_state,
    default_filters,
    axis_to_btn_pressed: 0.75,
    axis_to_btn_released: 0.65,
    events: [],
    events_head: 0,
    gamepads_data: [],
    mappings: MappingDb::new(),
    now_ms: 0L,
    next_effect_token: 1,
    ff_tick_base_ms: runtime_now_ms(),
    ff_tick: 0,
    ff_dirty: false,
    ff_effects: [],
    ff_events: [],
    ff_events_head: 0,
    backend: Some(NativeBackend::new()),
  }
}

///|
pub fn Gil::load_mappings(self : Gil, s : String) -> Unit {
  self.mappings.insert(s)
}

///|
pub fn Gil::set_axis_to_btn(
  self : Gil,
  pressed : Double,
  released : Double,
) -> Unit raise GilError {
  if pressed <= released ||
    pressed < 0.0 ||
    pressed > 1.0 ||
    released < 0.0 ||
    released > 1.0 {
    raise InvalidAxisToBtn
  }
  self.axis_to_btn_pressed = pressed
  self.axis_to_btn_released = released
}

///|
pub fn Gil::with_default_filters(self : Gil, enabled : Bool) -> Gil {
  self.default_filters = enabled
  self
}

///|
pub fn Gil::default_filters_enabled(self : Gil) -> Bool {
  self.default_filters
}

///|
pub fn Gil::update_state_enabled(self : Gil) -> Bool {
  self.update_state
}

///|
pub fn Gil::set_time(self : Gil, now_ms : Int64) -> Unit {
  self.now_ms = now_ms
}

///|
pub fn Gil::time(self : Gil) -> Int64 {
  self.now_ms
}

///|
fn contains_id(ids : Array[Int], id : Int) -> Bool {
  for x in ids {
    if x == id {
      return true
    }
  }
  false
}

///|
fn Gil::new_effect_token(self : Gil) -> Int {
  let token = self.next_effect_token
  if token >= 0x7FFF_FFFF {
    self.next_effect_token = 1
  } else {
    self.next_effect_token = token + 1
  }
  token
}

///|
fn u16_saturating_add(lhs : Int, rhs : Int) -> Int {
  let sum = lhs + rhs
  if sum >= 65535 {
    65535
  } else if sum <= 0 {
    0
  } else {
    sum
  }
}

///|
fn u16_scale(magnitude : Int, factor : Double) -> Int {
  if factor <= 0.0 || Double::is_nan(factor) {
    return 0
  }
  let x = magnitude.to_double() * factor
  if x <= 0.0 || Double::is_nan(x) {
    0
  } else if x >= 65535.0 {
    65535
  } else {
    x.to_int()
  }
}

///|
fn u16_from_amp(amp : Double) -> Int {
  if amp <= 0.0 || Double::is_nan(amp) {
    0
  } else if amp >= 1.0 {
    65535
  } else {
    (amp * 65535.0).to_int()
  }
}

///|
fn amp_from_u16(magnitude : Int) -> Double {
  clamp(magnitude.to_double() / 65535.0, 0.0, 1.0)
}

///|
fn Gil::ff_find_effect_idx(self : Gil, token : Int) -> Int? {
  for i in 0.. Bool {
  for x in devices {
    if x == id {
      return true
    }
  }
  false
}

///|
fn ff_repeat_max_ticks(repeat : FfRepeat) -> Int? {
  match repeat {
    Infinitely => None
    For(ms) => {
      let q = Ticks::from_ms(ms)
      let ticks = (q.as_ms() / FF_TICK_DURATION_MS).to_int()
      Some(ticks)
    }
  }
}

///|
fn Gil::ff_take_next_event(self : Gil) -> Event? {
  if self.ff_events_head >= self.ff_events.length() {
    self.ff_events = []
    self.ff_events_head = 0
    return None
  }
  let ev = self.ff_events[self.ff_events_head]
  self.ff_events_head = self.ff_events_head + 1
  Some(ev)
}

///|
fn Gil::ff_push_event(self : Gil, ev : Event) -> Unit {
  self.ff_events.push(ev)
}

///|
fn Gil::ff_now_tick(self : Gil, now_ms : Int64) -> Int {
  let elapsed = now_ms - self.ff_tick_base_ms
  if elapsed <= 0L {
    0
  } else {
    (elapsed / FF_TICK_DURATION_MS).to_int()
  }
}

///|
fn Gil::ff_has_active_effect(self : Gil) -> Bool {
  for eff in self.ff_effects {
    match eff.state {
      Playing(_) => return true
      Stopped => ()
    }
  }
  false
}

///|
fn Gil::ff_next_tick_wait_ms(self : Gil, now_ms : Int64) -> Int {
  let tick = self.ff_now_tick(now_ms)
  let next_tick_at = self.ff_tick_base_ms +
    (tick.to_int64() + 1L) * FF_TICK_DURATION_MS
  let wait = next_tick_at - now_ms
  if wait <= 0L {
    0
  } else if wait >= 0x7FFF_FFFFL {
    0x7FFF_FFFF
  } else {
    wait.to_int()
  }
}

///|
fn Gil::ff_upsert_effect_from_handle(self : Gil, effect : Effect) -> Unit {
  let token = effect.effect_token
  let devices : Array[Int] = []
  for gid in effect.gamepads {
    let id = gid.value()
    if id >= 0 && !contains_id(devices, id) {
      devices.push(id)
    }
  }
  let base_effects = effect.base_effects.copy()
  let strong_u16 = u16_from_amp(effect.strong)
  let weak_u16 = u16_from_amp(effect.weak)
  let mut state = FfEffectState::Stopped
  match effect.playing_since_ms {
    None => ()
    Some(_) => state = Playing(self.ff_tick)
  }
  match self.ff_find_effect_idx(token) {
    None =>
      self.ff_effects.push({
        token,
        base_effects,
        devices,
        repeat_mode: effect.repeat_mode,
        distance_model: effect.distance_model,
        position: effect.position,
        gain: effect.gain,
        state,
        strong: strong_u16,
        weak: weak_u16,
      })
    Some(idx) =>
      self.ff_effects[idx] = {
        token,
        base_effects,
        devices,
        repeat_mode: effect.repeat_mode,
        distance_model: effect.distance_model,
        position: effect.position,
        gain: effect.gain,
        state,
        strong: strong_u16,
        weak: weak_u16,
      }
  }
}

///|
fn Gil::ff_set_effect_gamepads(
  self : Gil,
  token : Int,
  gamepads : Array[GamepadId],
) -> Unit {
  match self.ff_find_effect_idx(token) {
    None => ()
    Some(idx) => {
      let devices : Array[Int] = []
      for gid in gamepads {
        let id = gid.value()
        if id >= 0 && !contains_id(devices, id) {
          devices.push(id)
        }
      }
      self.ff_effects[idx].devices = devices
    }
  }
  self.ff_dirty = true
}

///|
fn Gil::ff_set_effect_repeat(
  self : Gil,
  token : Int,
  repeat : FfRepeat,
) -> Unit {
  match self.ff_find_effect_idx(token) {
    None => ()
    Some(idx) => self.ff_effects[idx].repeat_mode = repeat
  }
  self.ff_dirty = true
}

///|
fn Gil::ff_set_effect_distance_model(
  self : Gil,
  token : Int,
  model : DistanceModel,
) -> Unit {
  match self.ff_find_effect_idx(token) {
    None => ()
    Some(idx) => self.ff_effects[idx].distance_model = model
  }
  self.ff_dirty = true
}

///|
fn Gil::ff_set_effect_position(
  self : Gil,
  token : Int,
  position : (Double, Double, Double),
) -> Unit {
  match self.ff_find_effect_idx(token) {
    None => ()
    Some(idx) => self.ff_effects[idx].position = position
  }
  self.ff_dirty = true
}

///|
fn Gil::ff_set_effect_gain(self : Gil, token : Int, gain : Double) -> Unit {
  match self.ff_find_effect_idx(token) {
    None => ()
    Some(idx) => self.ff_effects[idx].gain = gain
  }
  self.ff_dirty = true
}

///|
fn Gil::ff_play_effect(self : Gil, token : Int, tick : Int) -> Unit {
  match self.ff_find_effect_idx(token) {
    None => ()
    Some(idx) => self.ff_effects[idx].state = Playing(tick)
  }
  self.ff_dirty = true
}

///|
fn Gil::ff_stop_effect(self : Gil, token : Int) -> Unit {
  match self.ff_find_effect_idx(token) {
    None => ()
    Some(idx) => self.ff_effects[idx].state = Stopped
  }
  self.ff_dirty = true
}

///|
fn distance(
  a : (Double, Double, Double),
  b : (Double, Double, Double),
) -> Double {
  let (ax, ay, az) = a
  let (bx, by, bz) = b
  let dx = bx - ax
  let dy = by - ay
  let dz = bz - az
  Double::sqrt(dx * dx + dy * dy + dz * dz)
}

///|
fn Gil::ff_combine_base_effects(
  self : Gil,
  idx : Int,
  tick : Int,
  actor_pos : (Double, Double, Double),
  now_ms : Int64,
) -> FfMagnitude {
  let mut rel_ticks = 0
  match self.ff_effects[idx].state {
    Stopped => return { strong: 0, weak: 0 }
    Playing(since_tick) => {
      rel_ticks = tick - since_tick
      if rel_ticks < 0 {
        rel_ticks = 0
      }
    }
  }

  match ff_repeat_max_ticks(self.ff_effects[idx].repeat_mode) {
    None => ()
    Some(max_dur) =>
      if rel_ticks > max_dur {
        self.ff_effects[idx].state = Stopped
        for id in self.ff_effects[idx].devices {
          self.ff_push_event(
            Event::at(GamepadId::new(id), ForceFeedbackEffectCompleted, now_ms),
          )
        }
        return { strong: 0, weak: 0 }
      }
  }

  let dist = distance(self.ff_effects[idx].position, actor_pos)
  let attenuation = self.ff_effects[idx].distance_model.attenuation(dist) *
    self.ff_effects[idx].gain
  if attenuation < 0.05 {
    return { strong: 0, weak: 0 }
  }

  let mut strong = self.ff_effects[idx].strong
  let mut weak = self.ff_effects[idx].weak
  for effect in self.ff_effects[idx].base_effects {
    match effect.magnitude_at(rel_ticks) {
      Strong(magnitude) => strong = u16_saturating_add(strong, magnitude)
      Weak(magnitude) => weak = u16_saturating_add(weak, magnitude)
    }
  }
  { strong: u16_scale(strong, attenuation), weak: u16_scale(weak, attenuation) }
}

///|
fn Gil::ff_tick_update(self : Gil, now_ms : Int64, force : Bool) -> Unit {
  match self.backend {
    None => ()
    Some(b) => {
      let tick = self.ff_now_tick(now_ms)
      let should_tick = force || self.ff_dirty || tick != self.ff_tick
      if !should_tick {
        return
      }
      self.ff_tick = tick
      self.ff_dirty = false

      for dev_id in 0.. Unit {
  self.events.push(ev)
}

///|
fn Gil::apply_connection_event(self : Gil, ev : Event) -> Unit {
  let id = ev.id().value()
  match ev.event() {
    Connected => self.set_connected(id, true)
    Disconnected => self.set_connected(id, false)
    _ => ()
  }
}

///|
fn Gil::ensure_gamepad_data(self : Gil, id : Int) -> Unit {
  if id < 0 {
    return
  }
  while self.gamepads_data.length() <= id {
    self.gamepads_data.push(GamepadData::new())
  }
}

///|
fn Gil::set_connected(self : Gil, id : Int, connected : Bool) -> Unit {
  self.ensure_gamepad_data(id)
  if id < 0 || id >= self.gamepads_data.length() {
    return
  }
  self.gamepads_data[id].connected = connected
}

///|
fn Gil::apply_identity_mapping(self : Gil, id : Int) -> Unit {
  self.ensure_gamepad_data(id)
  if id < 0 || id >= self.gamepads_data.length() {
    return
  }
  let data = self.gamepads_data[id]
  let m = Mapping::new_default()
  for code in data.buttons {
    match axis_or_btn_from_code(code) {
      None => ()
      Some(el) => m.insert(code, el)
    }
  }
  for code in data.axes {
    match axis_or_btn_from_code(code) {
      None => ()
      Some(el) => m.insert(code, el)
    }
  }
  data.mapping = m
}

///|
fn find_axis_info(infos : Array[(Code, AxisInfo)], code : Code) -> AxisInfo? {
  for pair in infos {
    let (c, info) = pair
    if c == code {
      return Some(info)
    }
  }
  None
}

///|
fn Gil::refresh_gamepad_data_on_connected(
  self : Gil,
  id : Int,
  existed_before : Bool,
) -> Unit {
  if id < 0 || id >= self.gamepads_data.length() {
    return
  }
  let data = GamepadData::new()
  data.connected = true

  if existed_before {
    let prev = self.gamepads_data[id]
    data.name = prev.name
    data.uuid = prev.uuid
    data.vendor_id = prev.vendor_id
    data.product_id = prev.product_id
    data.ff_supported = prev.ff_supported
    data.power_info = prev.power_info
    data.axes = prev.axes
    data.buttons = prev.buttons
    data.axis_info = prev.axis_info
  }

  match self.backend {
    None => ()
    Some(b) => {
      data.name = b.name(id)
      let uuid_s = b.uuid_simple(id)
      let uuid = Uuid::parse(uuid_s) catch { _ => Uuid::nil() }
      data.uuid = uuid
      data.vendor_id = b.vendor_id(id)
      data.product_id = b.product_id(id)
      data.ff_supported = b.is_ff_supported(id)
      data.power_info = b.power_info(id)
      data.axes = b.axes(id)
      data.buttons = b.buttons(id)
      let infos : Array[(Code, AxisInfo)] = []
      for c in data.axes {
        match b.axis_info(id, c) {
          None => ()
          Some(info) => infos.push((c, info))
        }
      }
      data.axis_info = infos
    }
  }

  self.gamepads_data[id] = data
  self.apply_identity_mapping(id)
  let uuid = self.gamepads_data[id].uuid
  match self.mappings.get(uuid) {
    None => ()
    Some(line) =>
      try {
        let parsed = Mapping::parse_sdl_mapping(
          line,
          self.gamepads_data[id].buttons,
          self.gamepads_data[id].axes,
        )
        self.gamepads_data[id].mapping = parsed
      } catch {
        _ => ()
      }
  }
}

///|
fn Gil::finish_gamepads_creation(self : Gil) -> Unit {
  match self.backend {
    None => ()
    Some(b) => {
      let hint = b.last_gamepad_hint()
      for id in 0..= 0 && id < self.gamepads_data.length()
        self.ensure_gamepad_data(id)
        self.refresh_gamepad_data_on_connected(id, existed_before)
        self.set_connected(id, b.is_connected(id))
      }
    }
  }
}

///|
fn Gil::push_native_event(self : Gil, ne : NativeEvent) -> Unit {
  let id = ne.id
  let existed_before = id >= 0 && id < self.gamepads_data.length()
  self.ensure_gamepad_data(id)
  let gid = GamepadId::new(id)
  match ne.tag {
    Connected => {
      self.refresh_gamepad_data_on_connected(id, existed_before)
      self.insert_event(Event::at(gid, Connected, ne.time_ms))
    }
    Disconnected => {
      match self.backend {
        None => ()
        Some(b) => {
          let _ = b.set_rumble(id, 0.0, 0.0, 0)
        }
      }
      self.set_connected(id, false)
      self.insert_event(Event::at(gid, Disconnected, ne.time_ms))
    }
    ButtonPressed => {
      let code = ne.code
      match self.axis_or_btn_name(gid, code) {
        Some(Btn(btn)) => {
          self.insert_event(
            Event::at(gid, ButtonPressed(btn, code), ne.time_ms),
          )
          self.insert_event(
            Event::at(gid, ButtonChanged(btn, 1.0, code), ne.time_ms),
          )
        }
        Some(Axis(axis)) =>
          self.insert_event(
            Event::at(gid, AxisChanged(axis, 1.0, code), ne.time_ms),
          )
        None => {
          self.insert_event(
            Event::at(gid, ButtonPressed(Unknown, code), ne.time_ms),
          )
          self.insert_event(
            Event::at(gid, ButtonChanged(Unknown, 1.0, code), ne.time_ms),
          )
        }
      }
    }
    ButtonReleased => {
      let code = ne.code
      match self.axis_or_btn_name(gid, code) {
        Some(Btn(btn)) => {
          self.insert_event(
            Event::at(gid, ButtonReleased(btn, code), ne.time_ms),
          )
          self.insert_event(
            Event::at(gid, ButtonChanged(btn, 0.0, code), ne.time_ms),
          )
        }
        Some(Axis(axis)) =>
          self.insert_event(
            Event::at(gid, AxisChanged(axis, 0.0, code), ne.time_ms),
          )
        None => {
          self.insert_event(
            Event::at(gid, ButtonReleased(Unknown, code), ne.time_ms),
          )
          self.insert_event(
            Event::at(gid, ButtonChanged(Unknown, 0.0, code), ne.time_ms),
          )
        }
      }
    }
    AxisChanged => {
      if id < 0 || id >= self.gamepads_data.length() {
        return
      }
      let code = ne.code
      let info = match find_axis_info(self.gamepads_data[id].axis_info, code) {
        None => AxisInfo::new(0, 1, None)
        Some(i) => i
      }
      let raw_val = ne.value.to_int()
      match self.axis_or_btn_name(gid, code) {
        Some(Btn(btn)) => {
          let val = btn_value(info, raw_val)
          if val >= self.axis_to_btn_pressed &&
            !self.gamepads_data[id].state.is_pressed(code) {
            self.insert_event(
              Event::at(gid, ButtonPressed(btn, code), ne.time_ms),
            )
            self.insert_event(
              Event::at(gid, ButtonChanged(btn, val, code), ne.time_ms),
            )
          } else if val <= self.axis_to_btn_released &&
            self.gamepads_data[id].state.is_pressed(code) {
            self.insert_event(
              Event::at(gid, ButtonReleased(btn, code), ne.time_ms),
            )
            self.insert_event(
              Event::at(gid, ButtonChanged(btn, val, code), ne.time_ms),
            )
          } else {
            self.insert_event(
              Event::at(gid, ButtonChanged(btn, val, code), ne.time_ms),
            )
          }
        }
        Some(Axis(axis)) => {
          let val = axis_value(info, raw_val, axis)
          self.insert_event(
            Event::at(gid, AxisChanged(axis, val, code), ne.time_ms),
          )
        }
        None => {
          let val = axis_value(info, raw_val, Unknown)
          self.insert_event(
            Event::at(gid, AxisChanged(Unknown, val, code), ne.time_ms),
          )
        }
      }
    }
    ButtonChanged => {
      // Legacy tag: treat backend value as already normalized.
      let code = ne.code
      match self.axis_or_btn_name(gid, code) {
        Some(Btn(btn)) =>
          self.insert_event(
            Event::at(gid, ButtonChanged(btn, ne.value, code), ne.time_ms),
          )
        Some(Axis(axis)) =>
          self.insert_event(
            Event::at(gid, AxisChanged(axis, ne.value, code), ne.time_ms),
          )
        None => ()
      }
    }
  }
}

///|
pub fn Gil::poll(self : Gil) -> Unit {
  match self.backend {
    None => ()
    Some(b) => {
      b.poll()
      match b.next_event() {
        None => ()
        Some(ne) => self.push_native_event(ne)
      }
    }
  }
}

///|
fn clamp_blocking_timeout(timeout_ms : Int64?) -> Int {
  match timeout_ms {
    None => -1
    Some(ms) =>
      if ms <= 0L {
        0
      } else if ms >= 0x7FFF_FFFFL {
        0x7FFF_FFFF
      } else {
        ms.to_int()
      }
  }
}

///|
fn min_timeout(lhs : Int, rhs : Int) -> Int {
  if lhs < 0 {
    rhs
  } else if rhs < 0 {
    lhs
  } else if lhs < rhs {
    lhs
  } else {
    rhs
  }
}

///|
pub fn Gil::next_event(self : Gil) -> Event? {
  let jitter_filter = Jitter::new()
  while true {
    let now = runtime_now_ms()
    self.ff_tick_update(now, false)
    match self.ff_take_next_event() {
      None => ()
      Some(ev) => {
        if self.update_state {
          self.update(ev)
        }
        return Some(ev)
      }
    }
    if self.events_head >= self.events.length() {
      self.poll()
    }
    let raw : Event? = if self.events_head >= self.events.length() {
      None
    } else {
      let ev = self.events[self.events_head]
      self.events_head = self.events_head + 1
      Some(ev)
    }
    match raw {
      None => ()
      Some(ev) => self.apply_connection_event(ev)
    }
    let mut ev : Event? = raw
    if self.default_filters {
      ev = filter_ev(ev, axis_dpad_to_button, self)
      ev = filter_ev(ev, fn(ev, g) { jitter_filter.filter(ev, g) }, self)
      ev = filter_ev(ev, deadzone, self)
    }
    match ev {
      Some(e) =>
        if self.default_filters && e.is_dropped() {
          continue
        } else {
          if self.update_state {
            self.update(e)
          }
          return Some(e)
        }
      None => return None
    }
  } nobreak {
    None
  }
}

///|
pub fn Gil::next_event_blocking(self : Gil, timeout_ms : Int64?) -> Event? {
  let jitter_filter = Jitter::new()
  while true {
    let now = runtime_now_ms()
    self.ff_tick_update(now, false)
    match self.ff_take_next_event() {
      None => ()
      Some(ev) => {
        if self.update_state {
          self.update(ev)
        }
        return Some(ev)
      }
    }
    if self.events_head >= self.events.length() {
      match self.backend {
        None => ()
        Some(b) => {
          let base_timeout = clamp_blocking_timeout(timeout_ms)
          let t = if self.ff_has_active_effect() {
            min_timeout(base_timeout, self.ff_next_tick_wait_ms(now))
          } else {
            base_timeout
          }
          b.poll_timeout(t)
          match b.next_event() {
            None => ()
            Some(ne) => self.push_native_event(ne)
          }
        }
      }
    }
    let raw : Event? = if self.events_head >= self.events.length() {
      None
    } else {
      let ev = self.events[self.events_head]
      self.events_head = self.events_head + 1
      Some(ev)
    }
    match raw {
      None => ()
      Some(ev) => self.apply_connection_event(ev)
    }
    let mut ev : Event? = raw
    if self.default_filters {
      ev = filter_ev(ev, axis_dpad_to_button, self)
      ev = filter_ev(ev, fn(ev, g) { jitter_filter.filter(ev, g) }, self)
      ev = filter_ev(ev, deadzone, self)
    }
    match ev {
      Some(e) =>
        if self.default_filters && e.is_dropped() {
          continue
        } else {
          if self.update_state {
            self.update(e)
          }
          return Some(e)
        }
      None => return None
    }
  } nobreak {
    None
  }
}

///|
pub fn Gil::update(self : Gil, event : Event) -> Unit {
  let id = event.id().value()
  if id < 0 || id >= self.gamepads_data.length() {
    return
  }
  let counter = self.counter
  let time = event.time()
  let data = self.gamepads_data[id]
  match event.event() {
    ButtonPressed(_, code) =>
      data.state.set_btn_pressed(code, true, counter, time)
    ButtonReleased(_, code) =>
      data.state.set_btn_pressed(code, false, counter, time)
    ButtonRepeated(_, code) => data.state.set_btn_repeating(code, counter, time)
    ButtonChanged(_, value, code) =>
      data.state.set_btn_value(code, value, counter, time)
    AxisChanged(_, value, code) =>
      data.state.set_axis_value(code, value, counter, time)
    Connected | Disconnected | Dropped | ForceFeedbackEffectCompleted => ()
  }
}

///|
pub fn Gil::inc(self : Gil) -> Unit {
  if self.counter == 0x3FFF_FFFF_FFFF_FFFFL {
    self.counter = 0L
  } else {
    self.counter = self.counter + 1L
  }
}

///|
pub fn Gil::counter(self : Gil) -> Int64 {
  self.counter
}

///|
pub fn Gil::reset_counter(self : Gil) -> Unit {
  self.counter = 0L
}

///|
pub fn Gil::state(self : Gil, id : GamepadId) -> GamepadState? {
  let i = id.value()
  if i < 0 || i >= self.gamepads_data.length() {
    None
  } else {
    Some(self.gamepads_data[i].state)
  }
}

///|
pub fn Gil::is_connected(self : Gil, id : GamepadId) -> Bool {
  let i = id.value()
  if i < 0 || i >= self.gamepads_data.length() {
    false
  } else {
    self.gamepads_data[i].connected
  }
}

///|
pub fn Gil::set_mapping_data(
  self : Gil,
  id : GamepadId,
  mapping_data : MappingData,
  name? : String? = None,
) -> String raise MappingError {
  let i = id.value()
  if i < 0 || i >= self.gamepads_data.length() {
    raise NotConnected
  }
  if !self.gamepads_data[i].connected {
    raise NotConnected
  }
  let mapping_name = match name {
    Some(s) => s
    None => self.gamepads_data[i].name
  }
  let (mapping, sdl_line) = Mapping::from_data(
    mapping_data,
    self.gamepads_data[i].buttons,
    self.gamepads_data[i].axes,
    mapping_name,
    self.gamepads_data[i].uuid,
  )
  self.gamepads_data[i].mapping = mapping
  sdl_line
}

///|
pub fn Gil::set_mapping_data_strict(
  self : Gil,
  id : GamepadId,
  mapping_data : MappingData,
  name? : String? = None,
) -> String raise MappingError {
  if mapping_data.button(C) is Some(_) ||
    mapping_data.button(Z) is Some(_) ||
    mapping_data.axis(LeftZ) is Some(_) ||
    mapping_data.axis(RightZ) is Some(_) {
    raise NotSdl2Compatible
  }
  self.set_mapping_data(id, mapping_data, name~)
}

///|
pub fn Gil::set_mapping(self : Gil, id : GamepadId, mapping : Mapping) -> Unit {
  let i = id.value()
  if i < 0 || i >= self.gamepads_data.length() {
    return
  }
  self.gamepads_data[i].mapping = mapping
}

///|
pub fn Gil::mapping(self : Gil, id : GamepadId) -> Mapping? {
  let i = id.value()
  if i < 0 || i >= self.gamepads_data.length() {
    None
  } else {
    Some(self.gamepads_data[i].mapping)
  }
}

///|
pub fn Gil::axis_or_btn_name(
  self : Gil,
  id : GamepadId,
  code : Code,
) -> AxisOrBtn? {
  match self.mapping(id) {
    None => None
    Some(m) => m.map(code)
  }
}

///|
pub fn Gil::axis_code(self : Gil, id : GamepadId, axis : Axis) -> Code? {
  match self.mapping(id) {
    None => None
    Some(m) => m.map_rev(Axis(axis))
  }
}

///|
pub fn Gil::button_code(self : Gil, id : GamepadId, btn : Button) -> Code? {
  match self.mapping(id) {
    None => None
    Some(m) => m.map_rev(Btn(btn))
  }
}

///|
fn find_deadzone_idx(deadzones : Array[(Code, Double)], code : Code) -> Int? {
  for i in 0.. Unit {
  let i = id.value()
  if i < 0 || i >= self.gamepads_data.length() {
    return
  }
  let dz = self.gamepads_data[i].deadzones
  match find_deadzone_idx(dz, code) {
    None => dz.push((code, threshold))
    Some(j) => dz[j] = (code, threshold)
  }
}

///|
pub fn Gil::deadzone(self : Gil, id : GamepadId, code : Code) -> Double? {
  let i = id.value()
  if i < 0 || i >= self.gamepads_data.length() {
    return None
  }
  let dz = self.gamepads_data[i].deadzones
  match find_deadzone_idx(dz, code) {
    None => {
      for pair in self.gamepads_data[i].axis_info {
        let (c, info) = pair
        if c != code {
          continue
        }
        let range_i64 = info.max.to_int64() - info.min.to_int64()
        if range_i64 <= 0L {
          return Some(0.0)
        }
        match info.deadzone {
          Some(d) =>
            return Some(
              clamp(d.to_double() / range_i64.to_double() * 2.0, 0.0, 1.0),
            )
          None => return Some(0.1)
        }
      }
      None
    }
    Some(j) => {
      let (_, t) = dz[j]
      Some(t)
    }
  }
}

///|
pub struct GilBuilder {
  mut update_state : Bool
  mut default_filters : Bool
  mut axis_to_btn_pressed : Double
  mut axis_to_btn_released : Double
  mut env_mappings : Bool
  mut included_mappings : Bool
  mut mock_gamepad_count : Int
  mut use_native_backend : Bool
  mapping_inputs : Array[String]
}

///|
pub fn GilBuilder::new() -> GilBuilder {
  {
    update_state: true,
    default_filters: true,
    axis_to_btn_pressed: 0.75,
    axis_to_btn_released: 0.65,
    env_mappings: true,
    included_mappings: true,
    mock_gamepad_count: 0,
    use_native_backend: true,
    mapping_inputs: [],
  }
}

///|
pub fn GilBuilder::set_update_state(self : GilBuilder, v : Bool) -> GilBuilder {
  self.update_state = v
  self
}

///|
pub fn GilBuilder::with_default_filters(
  self : GilBuilder,
  v : Bool,
) -> GilBuilder {
  self.default_filters = v
  self
}

///|
pub fn GilBuilder::with_mock_gamepad_count(
  self : GilBuilder,
  n : Int,
) -> GilBuilder {
  self.mock_gamepad_count = n
  self
}

///|
pub fn GilBuilder::with_native_backend(
  self : GilBuilder,
  v : Bool,
) -> GilBuilder {
  self.use_native_backend = v
  self
}

///|
pub fn GilBuilder::add_mappings(
  self : GilBuilder,
  mappings : String,
) -> GilBuilder {
  self.mapping_inputs.push(mappings)
  self
}

///|
pub fn GilBuilder::set_axis_to_btn(
  self : GilBuilder,
  pressed : Double,
  released : Double,
) -> GilBuilder {
  self.axis_to_btn_pressed = pressed
  self.axis_to_btn_released = released
  self
}

///|
pub fn GilBuilder::add_env_mappings(
  self : GilBuilder,
  env_mappings : Bool,
) -> GilBuilder {
  self.env_mappings = env_mappings
  self
}

///|
pub fn GilBuilder::add_included_mappings(
  self : GilBuilder,
  included_mappings : Bool,
) -> GilBuilder {
  self.included_mappings = included_mappings
  self
}

///|
pub fn GilBuilder::build(self : GilBuilder) -> Gil raise GilError {
  if self.axis_to_btn_pressed <= self.axis_to_btn_released ||
    self.axis_to_btn_pressed < 0.0 ||
    self.axis_to_btn_pressed > 1.0 ||
    self.axis_to_btn_released < 0.0 ||
    self.axis_to_btn_released > 1.0 {
    raise InvalidAxisToBtn
  }
  let use_native_backend = self.use_native_backend &&
    self.mock_gamepad_count <= 0
  let gil = if use_native_backend {
    Gil::new_native(
      update_state=self.update_state,
      default_filters=self.default_filters,
    )
  } else {
    Gil::new_mock(
      self.mock_gamepad_count,
      update_state=self.update_state,
      default_filters=self.default_filters,
    )
  }
  for s in self.mapping_inputs {
    gil.load_mappings(s)
  }
  if self.included_mappings {
    gil.mappings.add_included_mappings()
  }
  if self.env_mappings {
    gil.mappings.add_env_mappings()
  }
  gil.axis_to_btn_pressed = self.axis_to_btn_pressed
  gil.axis_to_btn_released = self.axis_to_btn_released
  gil.finish_gamepads_creation()
  gil
}

///|
pub fn Gil::new() -> Gil {
  GilBuilder::new().with_native_backend(true).build() catch {
    _ => Gil::new_native()
  }
}

///|
pub struct Gamepad {
  gil : Gil
  id : GamepadId
}

///|
pub enum MappingSource {
  SdlMappings
  Driver
  None
}

///|
let _mapping_source_keepalive : Array[MappingSource] = [None]

///|
pub fn Gil::gamepad(self : Gil, id : GamepadId) -> Gamepad? {
  let i = id.value()
  if i < 0 || i >= self.gamepads_data.length() {
    None
  } else {
    Some({ gil: self, id })
  }
}

///|
pub fn Gil::connected_gamepad(self : Gil, id : GamepadId) -> Gamepad? {
  match self.gamepad(id) {
    None => None
    Some(gp) => if gp.is_connected() { Some(gp) } else { None }
  }
}

///|
pub fn Gil::gamepads(self : Gil) -> Array[(GamepadId, Gamepad)] {
  let out : Array[(GamepadId, Gamepad)] = []
  for i in 0.. GamepadId {
  self.id
}

///|
pub fn Gamepad::is_connected(self : Gamepad) -> Bool {
  self.gil.is_connected(self.id)
}

///|
pub fn Gamepad::name(self : Gamepad) -> String {
  match self.map_name() {
    Some(map_name) => map_name
    None => self.os_name()
  }
}

///|
pub fn Gamepad::map_name(self : Gamepad) -> String? {
  match self.mapping() {
    None => None
    Some(m) => if m.is_default() { None } else { Some(m.name()) }
  }
}

///|
pub fn Gamepad::os_name(self : Gamepad) -> String {
  let i = self.id.value()
  if i < 0 || i >= self.gil.gamepads_data.length() {
    ""
  } else {
    self.gil.gamepads_data[i].name
  }
}

///|
pub fn Gamepad::mapping_source(self : Gamepad) -> MappingSource {
  match self.mapping() {
    Some(m) => if m.is_default() { Driver } else { SdlMappings }
    None => None
  }
}

///|
pub fn Gamepad::uuid(self : Gamepad) -> Uuid {
  let i = self.id.value()
  if i < 0 || i >= self.gil.gamepads_data.length() {
    Uuid::nil()
  } else {
    self.gil.gamepads_data[i].uuid
  }
}

///|
pub fn Gamepad::vendor_id(self : Gamepad) -> Int? {
  let i = self.id.value()
  if i < 0 || i >= self.gil.gamepads_data.length() {
    None
  } else {
    self.gil.gamepads_data[i].vendor_id
  }
}

///|
pub fn Gamepad::product_id(self : Gamepad) -> Int? {
  let i = self.id.value()
  if i < 0 || i >= self.gil.gamepads_data.length() {
    None
  } else {
    self.gil.gamepads_data[i].product_id
  }
}

///|
pub fn Gamepad::power_info(self : Gamepad) -> PowerInfo {
  let i = self.id.value()
  if i < 0 || i >= self.gil.gamepads_data.length() {
    Unknown
  } else {
    self.gil.gamepads_data[i].power_info
  }
}

///|
pub fn Gamepad::axes(self : Gamepad) -> Array[Code] {
  let i = self.id.value()
  if i < 0 || i >= self.gil.gamepads_data.length() {
    []
  } else {
    self.gil.gamepads_data[i].axes.copy()
  }
}

///|
pub fn Gamepad::buttons(self : Gamepad) -> Array[Code] {
  let i = self.id.value()
  if i < 0 || i >= self.gil.gamepads_data.length() {
    []
  } else {
    self.gil.gamepads_data[i].buttons.copy()
  }
}

///|
pub fn Gamepad::axis_info(self : Gamepad, code : Code) -> AxisInfo? {
  let i = self.id.value()
  if i < 0 || i >= self.gil.gamepads_data.length() {
    return None
  }
  for pair in self.gil.gamepads_data[i].axis_info {
    let (c, info) = pair
    if c == code {
      return Some(info)
    }
  }
  None
}

///|
pub fn Gamepad::state(self : Gamepad) -> GamepadState {
  match self.gil.state(self.id) {
    None => GamepadState::new()
    Some(s) => s
  }
}

///|
pub fn Gamepad::is_pressed(self : Gamepad, btn : Button) -> Bool {
  let code = match self.button_code(btn) {
    Some(code) => Some(code)
    None => default_button_code(btn)
  }
  match code {
    None => false
    Some(code) => self.state().is_pressed(code)
  }
}

///|
pub fn Gamepad::value(self : Gamepad, axis : Axis) -> Double {
  match self.axis_code(axis) {
    None => 0.0
    Some(code) => self.state().value(code)
  }
}

///|
pub fn Gamepad::button_data(self : Gamepad, btn : Button) -> ButtonData? {
  match self.button_code(btn) {
    None => None
    Some(code) => self.state().button_data(code)
  }
}

///|
pub fn Gamepad::axis_data(self : Gamepad, axis : Axis) -> AxisData? {
  match self.axis_code(axis) {
    None => None
    Some(code) => self.state().axis_data(code)
  }
}

///|
pub fn Gamepad::axis_code(self : Gamepad, axis : Axis) -> Code? {
  self.gil.axis_code(self.id, axis)
}

///|
pub fn Gamepad::button_code(self : Gamepad, btn : Button) -> Code? {
  self.gil.button_code(self.id, btn)
}

///|
pub fn Gamepad::axis_or_btn_name(self : Gamepad, code : Code) -> AxisOrBtn? {
  self.gil.axis_or_btn_name(self.id, code)
}

///|
pub fn Gamepad::mapping(self : Gamepad) -> Mapping? {
  self.gil.mapping(self.id)
}

///|
pub fn Gamepad::deadzone(self : Gamepad, code : Code) -> Double? {
  self.gil.deadzone(self.id, code)
}

///|
pub fn Gamepad::is_ff_supported(self : Gamepad) -> Bool {
  let i = self.id.value()
  if i < 0 || i >= self.gil.gamepads_data.length() {
    false
  } else {
    self.gil.gamepads_data[i].ff_supported
  }
}

///|
pub fn Gamepad::set_listener_position(
  self : Gamepad,
  position : (Double, Double, Double),
) -> Unit raise FfError {
  if !self.is_connected() {
    raise Disconnected(self.id)
  } else if !self.is_ff_supported() {
    raise FfNotSupported(self.id)
  } else {
    let i = self.id.value()
    if i >= 0 && i < self.gil.gamepads_data.length() {
      self.gil.gamepads_data[i].listener_position = position
      self.gil.ff_tick_update(runtime_now_ms(), true)
    }
  }
}