// 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 GilrsError {
  NotImplemented
  InvalidAxisToBtn
}

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

///|
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 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,
    power_info: PowerInfo::Unknown,
    axes: [],
    buttons: [],
    axis_info: [],
    deadzones: [],
    have_sent_nonzero_for_axis: Array::make(6, false),
  }
}

///|
pub struct Gilrs {
  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
  backend : NativeBackend?
}

///|
pub fn Gilrs::new_mock(
  gamepad_count : Int,
  update_state? : Bool = true,
  default_filters? : Bool = true,
) -> Gilrs {
  let data : Array[GamepadData] = []
  for _ in 0.. Gilrs {
  {
    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,
    backend: Some(NativeBackend::new()),
  }
}

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

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

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

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

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

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

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

///|
pub fn Gilrs::insert_event(self : Gilrs, ev : Event) -> Unit {
  self.events.push(ev)
}

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

///|
fn Gilrs::set_connected(self : Gilrs, 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 Gilrs::apply_identity_mapping(self : Gilrs, 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 Gilrs::refresh_gamepad_data_on_connected(
  self : Gilrs,
  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 = PowerInfo::Unknown
      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) => {
      let parsed : Result[Mapping, Error] = try? Mapping::parse_sdl_mapping(
        line,
        self.gamepads_data[id].buttons,
        self.gamepads_data[id].axes,
      )
      match parsed {
        Ok(m) => self.gamepads_data[id].mapping = m
        Err(_) => ()
      }
    }
  }
}

///|
fn Gilrs::push_native_event(self : Gilrs, 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 {
    NativeEventTag::Connected => {
      self.refresh_gamepad_data_on_connected(id, existed_before)
      self.insert_event(Event::at(gid, EventType::Connected, ne.time_ms))
    }
    NativeEventTag::Disconnected => {
      self.set_connected(id, false)
      self.insert_event(Event::at(gid, EventType::Disconnected, ne.time_ms))
    }
    NativeEventTag::ButtonPressed => {
      let code = ne.code
      match self.axis_or_btn_name(gid, code) {
        Some(AxisOrBtn::Btn(btn)) => {
          self.insert_event(
            Event::at(gid, EventType::ButtonPressed(btn, code), ne.time_ms),
          )
          self.insert_event(
            Event::at(gid, EventType::ButtonChanged(btn, 1.0, code), ne.time_ms),
          )
        }
        Some(AxisOrBtn::Axis(axis)) =>
          self.insert_event(
            Event::at(gid, EventType::AxisChanged(axis, 1.0, code), ne.time_ms),
          )
        None => {
          self.insert_event(
            Event::at(
              gid,
              EventType::ButtonPressed(Button::Unknown, code),
              ne.time_ms,
            ),
          )
          self.insert_event(
            Event::at(
              gid,
              EventType::ButtonChanged(Button::Unknown, 1.0, code),
              ne.time_ms,
            ),
          )
        }
      }
    }
    NativeEventTag::ButtonReleased => {
      let code = ne.code
      match self.axis_or_btn_name(gid, code) {
        Some(AxisOrBtn::Btn(btn)) => {
          self.insert_event(
            Event::at(gid, EventType::ButtonReleased(btn, code), ne.time_ms),
          )
          self.insert_event(
            Event::at(gid, EventType::ButtonChanged(btn, 0.0, code), ne.time_ms),
          )
        }
        Some(AxisOrBtn::Axis(axis)) =>
          self.insert_event(
            Event::at(gid, EventType::AxisChanged(axis, 0.0, code), ne.time_ms),
          )
        None => {
          self.insert_event(
            Event::at(
              gid,
              EventType::ButtonReleased(Button::Unknown, code),
              ne.time_ms,
            ),
          )
          self.insert_event(
            Event::at(
              gid,
              EventType::ButtonChanged(Button::Unknown, 0.0, code),
              ne.time_ms,
            ),
          )
        }
      }
    }
    NativeEventTag::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(AxisOrBtn::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, EventType::ButtonPressed(btn, code), ne.time_ms),
            )
            self.insert_event(
              Event::at(
                gid,
                EventType::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, EventType::ButtonReleased(btn, code), ne.time_ms),
            )
            self.insert_event(
              Event::at(
                gid,
                EventType::ButtonChanged(btn, val, code),
                ne.time_ms,
              ),
            )
          } else {
            self.insert_event(
              Event::at(
                gid,
                EventType::ButtonChanged(btn, val, code),
                ne.time_ms,
              ),
            )
          }
        }
        Some(AxisOrBtn::Axis(axis)) => {
          let val = axis_value(info, raw_val, axis)
          self.insert_event(
            Event::at(gid, EventType::AxisChanged(axis, val, code), ne.time_ms),
          )
        }
        None => {
          let val = axis_value(info, raw_val, Axis::Unknown)
          self.insert_event(
            Event::at(
              gid,
              EventType::AxisChanged(Axis::Unknown, val, code),
              ne.time_ms,
            ),
          )
        }
      }
    }
    NativeEventTag::ButtonChanged => {
      // Legacy tag: treat backend value as already normalized.
      let code = ne.code
      match self.axis_or_btn_name(gid, code) {
        Some(AxisOrBtn::Btn(btn)) =>
          self.insert_event(
            Event::at(
              gid,
              EventType::ButtonChanged(btn, ne.value, code),
              ne.time_ms,
            ),
          )
        Some(AxisOrBtn::Axis(axis)) =>
          self.insert_event(
            Event::at(
              gid,
              EventType::AxisChanged(axis, ne.value, code),
              ne.time_ms,
            ),
          )
        None => ()
      }
    }
  }
}

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

///|
pub fn Gilrs::next_event(self : Gilrs) -> Event? {
  let jitter_filter = Jitter::new()
  while true {
    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)
    }
    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 e.is_dropped() {
          continue
        } else {
          if self.update_state {
            self.update(e)
          }
          return Some(e)
        }
      None => return None
    }
  } nobreak {
    None
  }
}

///|
pub fn Gilrs::next_event_blocking(self : Gilrs, timeout_ms : Int64?) -> Event? {
  let jitter_filter = Jitter::new()
  let mut timeout2 = timeout_ms
  while true {
    if self.events_head >= self.events.length() {
      match self.backend {
        None => ()
        Some(b) => {
          let t : Int = match timeout2 {
            None => -1
            Some(ms) =>
              if ms <= 0L {
                0
              } else if ms >= 0x7FFF_FFFFL {
                0x7FFF_FFFF
              } else {
                ms.to_int()
              }
          }
          b.poll_timeout(t)
          match b.next_event() {
            None => ()
            Some(ne) => self.push_native_event(ne)
          }
        }
      }
      timeout2 = match timeout2 {
        None => None
        Some(_) => Some(0L)
      }
    }
    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)
    }
    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 e.is_dropped() {
          continue
        } else {
          if self.update_state {
            self.update(e)
          }
          return Some(e)
        }
      None => return None
    }
  } nobreak {
    None
  }
}

///|
pub fn Gilrs::update(self : Gilrs, 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() {
    EventType::ButtonPressed(_, code) =>
      data.state.set_btn_pressed(code, true, counter, time)
    EventType::ButtonReleased(_, code) =>
      data.state.set_btn_pressed(code, false, counter, time)
    EventType::ButtonRepeated(_, code) =>
      data.state.set_btn_repeating(code, counter, time)
    EventType::ButtonChanged(_, value, code) =>
      data.state.set_btn_value(code, value, counter, time)
    EventType::AxisChanged(_, value, code) =>
      data.state.set_axis_value(code, value, counter, time)
    EventType::Connected => data.connected = true
    EventType::Disconnected => data.connected = false
    EventType::Dropped | EventType::ForceFeedbackEffectCompleted => ()
  }
}

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

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

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

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

///|
pub fn Gilrs::set_mapping_data(
  self : Gilrs,
  id : GamepadId,
  mapping_data : MappingData,
  name? : String? = None,
) -> String raise MappingError {
  let i = id.value()
  if i < 0 || i >= self.gamepads_data.length() {
    raise MappingError::NotConnected
  }
  if !self.gamepads_data[i].connected {
    raise MappingError::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 Gilrs::set_mapping_data_strict(
  self : Gilrs,
  id : GamepadId,
  mapping_data : MappingData,
  name? : String? = None,
) -> String raise MappingError {
  if mapping_data.button(Button::C) is Some(_) ||
    mapping_data.button(Button::Z) is Some(_) ||
    mapping_data.axis(Axis::LeftZ) is Some(_) ||
    mapping_data.axis(Axis::RightZ) is Some(_) {
    raise MappingError::NotSdl2Compatible
  }
  self.set_mapping_data(id, mapping_data, name~)
}

///|
pub fn Gilrs::set_mapping(
  self : Gilrs,
  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 Gilrs::mapping(self : Gilrs, 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 Gilrs::axis_or_btn_name(
  self : Gilrs,
  id : GamepadId,
  code : Code,
) -> AxisOrBtn? {
  match self.mapping(id) {
    None => None
    Some(m) => m.map(code)
  }
}

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

///|
pub fn Gilrs::button_code(self : Gilrs, id : GamepadId, btn : Button) -> Code? {
  match self.mapping(id) {
    None => None
    Some(m) => m.map_rev(AxisOrBtn::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 Gilrs::deadzone(self : Gilrs, 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 => None
    Some(j) => {
      let (_, t) = dz[j]
      Some(t)
    }
  }
}

///|
pub struct GilrsBuilder {
  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 GilrsBuilder::new() -> GilrsBuilder {
  {
    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: false,
    mapping_inputs: [],
  }
}

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

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

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

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

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

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

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

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

///|
pub fn GilrsBuilder::build(self : GilrsBuilder) -> Gilrs {
  let gilrs = if self.use_native_backend {
    Gilrs::new_native(
      update_state=self.update_state,
      default_filters=self.default_filters,
    )
  } else {
    Gilrs::new_mock(
      self.mock_gamepad_count,
      update_state=self.update_state,
      default_filters=self.default_filters,
    )
  }
  for s in self.mapping_inputs {
    gilrs.load_mappings(s)
  }
  if self.included_mappings {
    gilrs.mappings.add_included_mappings()
  }
  if self.env_mappings {
    gilrs.mappings.add_env_mappings()
  }
  gilrs.axis_to_btn_pressed = self.axis_to_btn_pressed
  gilrs.axis_to_btn_released = self.axis_to_btn_released
  gilrs
}

///|
pub fn Gilrs::new() -> Gilrs {
  GilrsBuilder::new().with_native_backend(true).build()
}

///|
pub struct Gamepad {
  gilrs : Gilrs
  id : GamepadId
}

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

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

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

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

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

///|
pub fn Gamepad::is_connected(self : Gamepad) -> Bool {
  self.gilrs.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.gilrs.gamepads_data.length() {
    ""
  } else {
    self.gilrs.gamepads_data[i].name
  }
}

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

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

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

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

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

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

///|
pub fn Gamepad::buttons(self : Gamepad) -> Array[Code] {
  let i = self.id.value()
  if i < 0 || i >= self.gilrs.gamepads_data.length() {
    []
  } else {
    self.gilrs.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.gilrs.gamepads_data.length() {
    return None
  }
  for pair in self.gilrs.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.gilrs.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 {
  let code = match self.axis_code(axis) {
    Some(code) => Some(code)
    None => default_axis_code(axis)
  }
  match code {
    None => 0.0
    Some(code) => self.state().value(code)
  }
}

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

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

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

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

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

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

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

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

///|
pub fn Gamepad::set_listener_position(
  self : Gamepad,
  position : (Double, Double, Double),
) -> Unit raise FfError {
  ignore(position)
  if !self.is_connected() {
    raise FfError::Disconnected(self.id)
  } else if !self.is_ff_supported() {
    raise FfError::FfNotSupported(self.id)
  } else {
    ()
  }
}