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

///|
pub fn filter_ev(
  ev : Event?,
  filter : (Event?, Gil) -> Event?,
  gil : Gil,
) -> Event? {
  let out = filter(ev, gil)
  match ev {
    None => out
    Some(e) =>
      match out {
        None => Some(e.drop())
        _ => out
      }
  }
}

///|
pub struct Jitter {
  threshold : Double
}

///|
pub fn Jitter::new() -> Jitter {
  { threshold: 0.01 }
}

///|
pub fn jitter(ev : Event?, gil : Gil, threshold : Double) -> Event? {
  match ev {
    Some(e) =>
      match e.event() {
        AxisChanged(_, val, axis_code) => {
          let old_data = match gil.state(e.id()) {
            None => 0.0
            Some(s) =>
              match s.axis_data(axis_code) {
                None => return ev
                Some(d) => d.value()
              }
          }
          if val != 0.0 && Double::abs(val - old_data) < threshold {
            Some(e.drop())
          } else {
            ev
          }
        }
        _ => ev
      }
    None => None
  }
}

///|
pub fn Jitter::filter(self : Jitter, ev : Event?, gil : Gil) -> Event? {
  jitter(ev, gil, self.threshold)
}

///|
fn apply_deadzone(
  x : Double,
  y : Double,
  threshold : Double,
) -> (Double, Double) {
  let magnitude = clamp(Double::sqrt(x * x + y * y), 0.0, 1.0)
  if magnitude <= threshold {
    (0.0, 0.0)
  } else {
    let norm = (magnitude - threshold) / (1.0 - threshold) / magnitude
    (x * norm, y * norm)
  }
}

///|
fn deadzone_nonzero_axis_idx(axis : Axis) -> Int? {
  Some(
    match axis {
      DPadX => 0
      DPadY => 1
      LeftStickX => 2
      LeftStickY => 3
      RightStickX => 4
      RightStickY => 5
      _ => return None
    },
  )
}

///|
pub fn deadzone(ev : Event?, gil : Gil) -> Event? {
  match ev {
    Some(e) =>
      match e.event() {
        AxisChanged(axis, val, code) => deadzone_axis(e, axis, val, code, gil)
        ButtonChanged(btn, val, code) => deadzone_button(e, btn, val, code, gil)
        _ => ev
      }
    None => None
  }
}

///|
fn deadzone_axis(
  e : Event,
  axis : Axis,
  val : Double,
  code : Code,
  gil : Gil,
) -> Event? {
  let threshold = match gil.deadzone(e.id(), code) {
    None => return Some(e)
    Some(t) => t
  }
  let id = e.id()
  let time = e.time()
  let other_axis_code : (Axis, Code)? = match axis.second_axis() {
    None => None
    Some(other_axis) =>
      match gil.axis_code(id, other_axis) {
        None => None
        Some(other_code) => Some((other_axis, other_code))
      }
  }
  match other_axis_code {
    None => {
      let new_val = apply_deadzone(val, 0.0, threshold).0
      let cur = match gil.state(id) {
        None => 0.0
        Some(s) => s.value(code)
      }
      if cur == new_val {
        Some(e.drop())
      } else {
        match deadzone_nonzero_axis_idx(axis) {
          None => ()
          Some(axis_idx) =>
            gil.gamepads_data[id.value()].have_sent_nonzero_for_axis[axis_idx] = new_val !=
              0.0
        }
        Some(Event::at(id, AxisChanged(axis, new_val, code), time))
      }
    }
    Some((other_axis, other_code)) => {
      let other_val = match gil.state(id) {
        None => 0.0
        Some(s) => s.value(other_code)
      }
      let (new_val, new_other_val) = apply_deadzone(val, other_val, threshold)
      match deadzone_nonzero_axis_idx(other_axis) {
        None => ()
        Some(other_axis_idx) => {
          let sent_nonzero = gil.gamepads_data[id.value()].have_sent_nonzero_for_axis[other_axis_idx]
          let other_state_val = match gil.state(id) {
            None => 0.0
            Some(s) => s.value(other_code)
          }
          if new_val == 0.0 &&
            new_other_val == 0.0 &&
            sent_nonzero &&
            other_state_val != 0.0 {
            gil.insert_event(
              Event::at(id, AxisChanged(other_axis, 0.0, other_code), time),
            )
            gil.gamepads_data[id.value()].have_sent_nonzero_for_axis[other_axis_idx] = false
          }
        }
      }
      let cur = match gil.state(id) {
        None => 0.0
        Some(s) => s.value(code)
      }
      if cur == new_val {
        Some(e.drop())
      } else {
        match deadzone_nonzero_axis_idx(axis) {
          None => ()
          Some(axis_idx) =>
            gil.gamepads_data[id.value()].have_sent_nonzero_for_axis[axis_idx] = new_val !=
              0.0
        }
        Some(Event::at(id, AxisChanged(axis, new_val, code), time))
      }
    }
  }
}

///|
fn deadzone_button(
  e : Event,
  btn : Button,
  val : Double,
  code : Code,
  gil : Gil,
) -> Event? {
  let threshold = match gil.deadzone(e.id(), code) {
    None => return Some(e)
    Some(t) => t
  }
  let new_val = apply_deadzone(val, 0.0, threshold).0
  let cur = match gil.state(e.id()) {
    None => 0.0
    Some(s) => s.value(code)
  }
  if cur == new_val {
    Some(e.drop())
  } else {
    Some(Event::at(e.id(), ButtonChanged(btn, new_val, code), e.time()))
  }
}

///|
pub fn axis_dpad_to_button(ev : Event?, gil : Gil) -> Event? {
  match ev {
    None => None
    Some(e) =>
      match e.event() {
        AxisChanged(axis, val, axis_code) =>
          match axis {
            DPadX => axis_dpad_x_to_buttons(e, val, axis_code, gil)
            DPadY => axis_dpad_y_to_buttons(e, val, axis_code, gil)
            _ => ev
          }
        _ => ev
      }
  }
}

///|
fn dpad_btn_codes_for_axis_code(axis_code : Code) -> (Code, Code, Code, Code) {
  if hid_page(axis_code) == 0 {
    (
      LEGACY_BTN_DPAD_UP,
      LEGACY_BTN_DPAD_DOWN,
      LEGACY_BTN_DPAD_LEFT,
      LEGACY_BTN_DPAD_RIGHT,
    )
  } else {
    (BTN_DPAD_UP, BTN_DPAD_DOWN, BTN_DPAD_LEFT, BTN_DPAD_RIGHT)
  }
}

///|
fn can_map_axis_dpad_to_button(
  id : GamepadId,
  axis_code : Code,
  gil : Gil,
) -> Bool {
  match gil.mapping(id) {
    None => false
    Some(m) => {
      let (btn_up, btn_down, btn_left, btn_right) = dpad_btn_codes_for_axis_code(
        axis_code,
      )
      let hats = m.hats_mapped()
      if hats == 0b1111 {
        true
      } else if hats == 0 {
        gil.axis_or_btn_name(id, btn_right) is None &&
        gil.axis_or_btn_name(id, btn_left) is None &&
        gil.axis_or_btn_name(id, btn_down) is None &&
        gil.axis_or_btn_name(id, btn_up) is None &&
        gil.button_code(id, DPadRight) is None
      } else {
        false
      }
    }
  }
}

///|
fn axis_dpad_x_to_buttons(
  e : Event,
  val : Double,
  axis_code : Code,
  gil : Gil,
) -> Event? {
  if !can_map_axis_dpad_to_button(e.id(), axis_code, gil) {
    return Some(e)
  }
  let (_, _, btn_left, btn_right) = dpad_btn_codes_for_axis_code(axis_code)
  let id = e.id()
  let time = e.time()
  let mut out = e.drop()
  let mut release_left = false
  let mut release_right = false
  let pressed_left = match gil.state(id) {
    None => false
    Some(s) => s.is_pressed(btn_left)
  }
  let pressed_right = match gil.state(id) {
    None => false
    Some(s) => s.is_pressed(btn_right)
  }
  if val == 1.0 {
    release_left = pressed_left
    gil.insert_event(
      Event::at(id, ButtonChanged(DPadRight, 1.0, btn_right), time),
    )
    out = Event::at(id, ButtonPressed(DPadRight, btn_right), time)
  } else if val == -1.0 {
    release_right = pressed_right
    gil.insert_event(
      Event::at(id, ButtonChanged(DPadLeft, 1.0, btn_left), time),
    )
    out = Event::at(id, ButtonPressed(DPadLeft, btn_left), time)
  } else {
    release_left = pressed_left
    release_right = pressed_right
  }
  if release_right {
    if !out.is_dropped() {
      gil.insert_event(out)
    }
    gil.insert_event(
      Event::at(id, ButtonChanged(DPadRight, 0.0, btn_right), time),
    )
    out = Event::at(id, ButtonReleased(DPadRight, btn_right), time)
  }
  if release_left {
    if !out.is_dropped() {
      gil.insert_event(out)
    }
    gil.insert_event(
      Event::at(id, ButtonChanged(DPadLeft, 0.0, btn_left), time),
    )
    out = Event::at(id, ButtonReleased(DPadLeft, btn_left), time)
  }
  Some(out)
}

///|
fn axis_dpad_y_to_buttons(
  e : Event,
  val : Double,
  axis_code : Code,
  gil : Gil,
) -> Event? {
  if !can_map_axis_dpad_to_button(e.id(), axis_code, gil) {
    return Some(e)
  }
  let (btn_up, btn_down, _, _) = dpad_btn_codes_for_axis_code(axis_code)
  let id = e.id()
  let time = e.time()
  let mut out = e.drop()
  let mut release_up = false
  let mut release_down = false
  let pressed_up = match gil.state(id) {
    None => false
    Some(s) => s.is_pressed(btn_up)
  }
  let pressed_down = match gil.state(id) {
    None => false
    Some(s) => s.is_pressed(btn_down)
  }
  if val == 1.0 {
    release_down = pressed_down
    gil.insert_event(Event::at(id, ButtonChanged(DPadUp, 1.0, btn_up), time))
    out = Event::at(id, ButtonPressed(DPadUp, btn_up), time)
  } else if val == -1.0 {
    release_up = pressed_up
    gil.insert_event(
      Event::at(id, ButtonChanged(DPadDown, 1.0, btn_down), time),
    )
    out = Event::at(id, ButtonPressed(DPadDown, btn_down), time)
  } else {
    release_up = pressed_up
    release_down = pressed_down
  }
  if release_up {
    if !out.is_dropped() {
      gil.insert_event(out)
    }
    gil.insert_event(Event::at(id, ButtonChanged(DPadUp, 0.0, btn_up), time))
    out = Event::at(id, ButtonReleased(DPadUp, btn_up), time)
  }
  if release_down {
    if !out.is_dropped() {
      gil.insert_event(out)
    }
    gil.insert_event(
      Event::at(id, ButtonChanged(DPadDown, 0.0, btn_down), time),
    )
    out = Event::at(id, ButtonReleased(DPadDown, btn_down), time)
  }
  Some(out)
}

///|
pub struct Repeat {
  after_ms : Int64
  every_ms : Int64
}

///|
pub fn Repeat::new() -> Repeat {
  { after_ms: 500L, every_ms: 30L }
}

///|
fn code_to_button_name(id : GamepadId, code : Code, gil : Gil) -> Button {
  match gil.axis_or_btn_name(id, code) {
    Some(Btn(b)) => b
    _ => Unknown
  }
}

///|
pub fn Repeat::filter(self : Repeat, ev : Event?, gil : Gil) -> Event? {
  match ev {
    Some(_) => ev
    None => {
      let now = runtime_now_ms()
      for i in 0.. ()
          Some(state) =>
            for pair in state.buttons_entries() {
              let (code, data) = pair
              let pressed = data.is_pressed()
              let repeating = data.is_repeating()
              let dur = now - data.timestamp()
              if pressed && !repeating && dur >= self.after_ms {
                let btn = code_to_button_name(id, code, gil)
                return Some(
                  Event::at(
                    id,
                    ButtonRepeated(btn, code),
                    data.timestamp() + self.after_ms,
                  ),
                )
              }
              if pressed && repeating && dur >= self.every_ms {
                let btn = code_to_button_name(id, code, gil)
                return Some(
                  Event::at(
                    id,
                    ButtonRepeated(btn, code),
                    data.timestamp() + self.every_ms,
                  ),
                )
              }
            }
        }
      }
      None
    }
  }
}