///|
pub let page_vendor : Int = 0xFF00

///|
pub let page_sensor : Int = 0x0020

///|
pub let usage_accel : Int = 3

///|
pub let usage_gyro : Int = 9

///|
pub let usage_als : Int = 4

///|
pub let usage_lid : Int = 138

///|
pub let imu_report_len : Int = 22

///|
pub let imu_decimation : Int = 8

///|
pub let imu_data_offset : Int = 6

///|
pub let als_report_len : Int = 122

///|
pub let lid_report_len : Int = 3

///|
pub let report_buf_size : Int = 4096

///|
pub let report_interval_us : Int = 1000

///|
pub let ring_cap : Int = 8000

///|
pub let ring_entry : Int = 12

///|
pub let shm_header : Int = 16

///|
pub let shm_size : Int = shm_header + ring_cap * ring_entry

///|
pub let snap_header : Int = 8

///|
pub let accel_scale : Double = 65536.0

///|
pub let gyro_scale : Double = 65536.0

///|
pub let shm_name_accel : String = "vib_detect_shm"

///|
pub let shm_name_gyro : String = "vib_detect_shm_gyro"

///|
pub let shm_name_als : String = "vib_detect_shm_als"

///|
pub let shm_name_lid : String = "vib_detect_shm_lid"

///|
pub let als_size : Int = snap_header + als_report_len

///|
pub let lid_size : Int = snap_header + 4

///|
#borrow(report)
extern "c" fn parse_imu_x_ffi(report : Bytes) -> Int = "asa_parse_imu_x"

///|
#borrow(report)
extern "c" fn parse_imu_y_ffi(report : Bytes) -> Int = "asa_parse_imu_y"

///|
#borrow(report)
extern "c" fn parse_imu_z_ffi(report : Bytes) -> Int = "asa_parse_imu_z"

///|
#borrow(report)
extern "c" fn parse_lid_angle_ffi(report : Bytes) -> Float = "asa_parse_lid_angle"

///|
pub fn parse_imu_report(report : Bytes) -> (Int, Int, Int) {
  (parse_imu_x_ffi(report), parse_imu_y_ffi(report), parse_imu_z_ffi(report))
}

///|
pub fn parse_lid_angle(report : Bytes) -> Float? {
  let angle = parse_lid_angle_ffi(report)
  if angle < 0.0 {
    None
  } else {
    Some(angle)
  }
}

///|
extern "c" fn get_euid_ffi() -> Int = "asa_geteuid"

///|
extern "c" fn run_sensord_ffi() -> Int = "asa_run_sensord"

///|
pub fn run_sensord() -> Unit raise {
  guard get_euid_ffi() == 0 else {
    fail("sensord requires root privileges, run with: sudo moon run cmd/main")
  }
  let rc = run_sensord_ffi()
  guard rc == 0 else { fail("sensord failed with native exit code \{rc}") }
}

///|
extern "c" fn sleep_seconds_ffi(seconds : Int) -> Int = "asa_sleep_seconds"

///|
extern "c" fn start_sensord_background_ffi() -> Int = "asa_start_sensord_background"

///|
extern "c" fn stop_sensord_background_ffi(pid : Int) -> Int = "asa_stop_sensord_background"

///|
extern "c" fn read_accel_shm_ffi() -> Bytes = "asa_read_accel_shm"

///|
extern "c" fn read_gyro_shm_ffi() -> Bytes = "asa_read_gyro_shm"

///|
extern "c" fn read_als_shm_ffi() -> Bytes = "asa_read_als_shm"

///|
extern "c" fn lid_count_ffi() -> Int = "asa_lid_count"

///|
extern "c" fn lid_angle_latest_ffi() -> Float = "asa_lid_angle_latest"

///|
pub fn sleep_seconds(seconds : Int) -> Unit {
  let _ = sleep_seconds_ffi(seconds)
}

///|
pub fn start_sensord_background() -> Int raise {
  guard get_euid_ffi() == 0 else {
    fail("sensord requires root privileges, run with: sudo moon run example")
  }
  let pid = start_sensord_background_ffi()
  guard pid > 0 else { fail("failed to start sensord background process") }
  pid
}

///|
pub fn stop_sensord_background(pid : Int) -> Unit {
  let _ = stop_sensord_background_ffi(pid)
}

///|
fn ring_latest(raw : Bytes) -> (Int, Int, Int, Int)? {
  match raw[:] {
    [
      u32le(write_idx),
      u32le(total_lo),
      u32le(total_hi),
      u32le(_),
      bytes(96000, ring),
      ..,
    ] => {
      let total = if total_hi > 0U || total_lo > 0x7FFFFFFFU {
        0x7FFFFFFF
      } else {
        total_lo.reinterpret_as_int()
      }
      if total <= 0 {
        None
      } else {
        let latest_idx = (write_idx.reinterpret_as_int() + ring_cap - 1) %
          ring_cap
        let entry_off = latest_idx * ring_entry
        match ring[entry_off:entry_off + ring_entry] {
          [i32le(x), i32le(y), i32le(z)] => Some((total, x, y, z))
          _ => None
        }
      }
    }
    _ => None
  }
}

///|
pub fn accel_latest() -> (Int, Int, Int, Int)? {
  ring_latest(read_accel_shm_ffi())
}

///|
pub fn gyro_latest() -> (Int, Int, Int, Int)? {
  ring_latest(read_gyro_shm_ffi())
}

///|
pub fn als_latest_words() -> (Int, Int, Int, Int, Int)? {
  let raw = read_als_shm_ffi()
  match raw[:] {
    [u32le(total), u32le(_), bytes(122, payload)] =>
      if total == 0U {
        None
      } else {
        match payload {
          [i32le(w0), i32le(w1), i32le(w2), i32le(w3), ..] =>
            Some((total.reinterpret_as_int(), w0, w1, w2, w3))
          _ => None
        }
      }
    _ => None
  }
}

///|
pub fn lid_latest() -> (Int, Float)? {
  let total = lid_count_ffi()
  let angle = lid_angle_latest_ffi()
  if total <= 0 || angle < 0.0 {
    None
  } else {
    Some((total, angle))
  }
}