// 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.

///|
type BackendOwner

///|
extern "C" fn backend_new() -> BackendOwner = "moon_gamepad_backend_new"

///|
#borrow(owner)
extern "C" fn backend_poll(owner : BackendOwner) -> Unit = "moon_gamepad_backend_poll"

///|
#borrow(owner)
extern "C" fn backend_poll_timeout(
  owner : BackendOwner,
  timeout_ms : Int,
) -> Unit = "moon_gamepad_backend_poll_timeout"

///|
#borrow(owner)
extern "C" fn backend_gamepad_count(owner : BackendOwner) -> Int = "moon_gamepad_backend_gamepad_count"

///|
#borrow(owner)
extern "C" fn backend_last_gamepad_hint(owner : BackendOwner) -> Int = "moon_gamepad_backend_last_gamepad_hint"

///|
#borrow(owner)
extern "C" fn backend_next_event_bin(owner : BackendOwner) -> Bytes = "moon_gamepad_backend_next_event_bin"

///|
extern "C" fn backend_now_ms() -> Int64 = "moon_gamepad_now_ms"

///|
extern "C" fn backend_env_sdl_gamecontrollerconfig() -> String = "moon_gamepad_env_sdl_gamecontrollerconfig"

///|
extern "C" fn backend_sdl_platform_name() -> String = "moon_gamepad_sdl_platform_name"

///|
#borrow(owner)
extern "C" fn backend_name(owner : BackendOwner, id : Int) -> String = "moon_gamepad_backend_name"

///|
#borrow(owner)
extern "C" fn backend_uuid_simple(owner : BackendOwner, id : Int) -> String = "moon_gamepad_backend_uuid_simple"

///|
#borrow(owner)
extern "C" fn backend_vendor_id(owner : BackendOwner, id : Int) -> Int = "moon_gamepad_backend_vendor_id"

///|
#borrow(owner)
extern "C" fn backend_product_id(owner : BackendOwner, id : Int) -> Int = "moon_gamepad_backend_product_id"

///|
#borrow(owner)
extern "C" fn backend_is_connected(owner : BackendOwner, id : Int) -> Int = "moon_gamepad_backend_is_connected"

///|
#borrow(owner)
extern "C" fn backend_is_ff_supported(owner : BackendOwner, id : Int) -> Int = "moon_gamepad_backend_is_ff_supported"

///|
#borrow(owner)
extern "C" fn backend_power_info_bin(owner : BackendOwner, id : Int) -> Bytes = "moon_gamepad_backend_power_info_bin"

///|
#borrow(owner)
extern "C" fn backend_axes_bin(owner : BackendOwner, id : Int) -> Bytes = "moon_gamepad_backend_axes_bin"

///|
#borrow(owner)
extern "C" fn backend_buttons_bin(owner : BackendOwner, id : Int) -> Bytes = "moon_gamepad_backend_buttons_bin"

///|
#borrow(owner)
extern "C" fn backend_axis_info_bin(
  owner : BackendOwner,
  id : Int,
  code : Int,
) -> Bytes = "moon_gamepad_backend_axis_info_bin"

///|
#borrow(owner)
extern "C" fn backend_set_rumble(
  owner : BackendOwner,
  id : Int,
  strong : Double,
  weak : Double,
  duration_ms : Int,
) -> Int = "moon_gamepad_backend_set_rumble"

///|
pub struct NativeBackend {
  owner : BackendOwner
}

///|
pub fn NativeBackend::new() -> NativeBackend {
  { owner: backend_new() }
}

///|
pub fn NativeBackend::poll(self : NativeBackend) -> Unit {
  backend_poll(self.owner)
}

///|
pub fn NativeBackend::poll_timeout(
  self : NativeBackend,
  timeout_ms : Int,
) -> Unit {
  backend_poll_timeout(self.owner, timeout_ms)
}

///|
pub fn NativeBackend::gamepad_count(self : NativeBackend) -> Int {
  backend_gamepad_count(self.owner)
}

///|
pub fn NativeBackend::last_gamepad_hint(self : NativeBackend) -> Int {
  backend_last_gamepad_hint(self.owner)
}

///|
pub fn NativeBackend::next_event(self : NativeBackend) -> NativeEvent? {
  let b = backend_next_event_bin(self.owner)
  decode_native_event(b)
}

///|
pub fn runtime_now_ms() -> Int64 {
  backend_now_ms()
}

///|
pub fn runtime_env_sdl_gamecontrollerconfig() -> String {
  backend_env_sdl_gamecontrollerconfig()
}

///|
pub fn runtime_sdl_platform_name() -> String {
  backend_sdl_platform_name()
}

///|
pub fn NativeBackend::name(self : NativeBackend, id : Int) -> String {
  backend_name(self.owner, id)
}

///|
pub fn NativeBackend::uuid_simple(self : NativeBackend, id : Int) -> String {
  backend_uuid_simple(self.owner, id)
}

///|
pub fn NativeBackend::vendor_id(self : NativeBackend, id : Int) -> Int? {
  let v = backend_vendor_id(self.owner, id)
  if v < 0 {
    None
  } else {
    Some(v)
  }
}

///|
pub fn NativeBackend::product_id(self : NativeBackend, id : Int) -> Int? {
  let v = backend_product_id(self.owner, id)
  if v < 0 {
    None
  } else {
    Some(v)
  }
}

///|
pub fn NativeBackend::is_connected(self : NativeBackend, id : Int) -> Bool {
  backend_is_connected(self.owner, id) != 0
}

///|
pub fn NativeBackend::is_ff_supported(self : NativeBackend, id : Int) -> Bool {
  backend_is_ff_supported(self.owner, id) != 0
}

///|
pub fn NativeBackend::power_info(self : NativeBackend, id : Int) -> PowerInfo {
  let b = backend_power_info_bin(self.owner, id)
  if b.length() < 8 {
    return Unknown
  }
  let tag = read_i32_le(b, 0)
  let value = read_i32_le(b, 4)
  match tag {
    1 => Wired
    2 => Discharging(value)
    3 => Charging(value)
    4 => Charged
    _ => Unknown
  }
}

///|
fn read_i32_le(b : Bytes, off : Int) -> Int {
  let b0 = b[off + 0].to_int()
  let b1 = b[off + 1].to_int()
  let b2 = b[off + 2].to_int()
  let b3 = b[off + 3].to_int()
  b0 | (b1 << 8) | (b2 << 16) | (b3 << 24)
}

///|
fn decode_i32s(b : Bytes) -> Array[Int] {
  let out : Array[Int] = []
  if b.length() == 0 {
    return out
  }
  let n = b.length() / 4
  for i in 0.. Array[Int] {
  decode_i32s(backend_axes_bin(self.owner, id))
}

///|
pub fn NativeBackend::buttons(self : NativeBackend, id : Int) -> Array[Int] {
  decode_i32s(backend_buttons_bin(self.owner, id))
}

///|
pub fn NativeBackend::axis_info(
  self : NativeBackend,
  id : Int,
  code : Int,
) -> AxisInfo? {
  let b = backend_axis_info_bin(self.owner, id, code)
  if b.length() < 16 {
    return None
  }
  let present = read_i32_le(b, 0)
  if present == 0 {
    return None
  }
  let minv = read_i32_le(b, 4)
  let maxv = read_i32_le(b, 8)
  let dz = read_i32_le(b, 12)
  let deadzone : Int? = if dz < 0 { None } else { Some(dz) }
  Some(AxisInfo::new(minv, maxv, deadzone))
}

///|
pub fn NativeBackend::set_rumble(
  self : NativeBackend,
  id : Int,
  strong : Double,
  weak : Double,
  duration_ms : Int,
) -> Bool {
  backend_set_rumble(self.owner, id, strong, weak, duration_ms) != 0
}