// 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?, Gilrs) -> Event?,
  gilrs : Gilrs,
) -> Event? {
  let out = filter(ev, gilrs)
  // Mirror gilrs' contract: Some(event) must not become None.
  match ev {
    Some(e) =>
      match out {
        None => Some(e.drop())
        Some(_) => out
      }
    None => out
  }
}

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

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

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

///|
pub fn Jitter::filter(self : Jitter, ev : Event?, gilrs : Gilrs) -> Event? {
  jitter(ev, gilrs, 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 {
      Axis::DPadX => 0
      Axis::DPadY => 1
      Axis::LeftStickX => 2
      Axis::LeftStickY => 3
      Axis::RightStickX => 4
      Axis::RightStickY => 5
      _ => return None
    },
  )
}

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

///|
fn deadzone_axis(
  e : Event,
  axis : Axis,
  val : Double,
  code : Code,
  gilrs : Gilrs,
) -> Event? {
  let threshold = match gilrs.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 gilrs.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 gilrs.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) =>
            gilrs.gamepads_data[id.value()].have_sent_nonzero_for_axis[axis_idx] = new_val !=
              0.0
        }
        Some(Event::at(id, EventType::AxisChanged(axis, new_val, code), time))
      }
    }
    Some((other_axis, other_code)) => {
      let other_val = match gilrs.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 = gilrs.gamepads_data[id.value()].have_sent_nonzero_for_axis[other_axis_idx]
          let other_state_val = match gilrs.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 {
            gilrs.insert_event(
              Event::at(
                id,
                EventType::AxisChanged(other_axis, 0.0, other_code),
                time,
              ),
            )
            gilrs.gamepads_data[id.value()].have_sent_nonzero_for_axis[other_axis_idx] = false
          }
        }
      }
      let cur = match gilrs.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) =>
            gilrs.gamepads_data[id.value()].have_sent_nonzero_for_axis[axis_idx] = new_val !=
              0.0
        }
        Some(Event::at(id, EventType::AxisChanged(axis, new_val, code), time))
      }
    }
  }
}

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

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

///|
fn can_map_axis_dpad_to_button(id : GamepadId, gilrs : Gilrs) -> Bool {
  match gilrs.mapping(id) {
    None => false
    Some(m) => {
      let hats = m.hats_mapped()
      if hats == 0b1111 {
        true
      } else if hats == 0 {
        gilrs.axis_or_btn_name(id, BTN_DPAD_RIGHT) is None &&
        gilrs.axis_or_btn_name(id, BTN_DPAD_LEFT) is None &&
        gilrs.axis_or_btn_name(id, BTN_DPAD_DOWN) is None &&
        gilrs.axis_or_btn_name(id, BTN_DPAD_UP) is None &&
        gilrs.button_code(id, Button::DPadRight) is None
      } else {
        false
      }
    }
  }
}

///|
fn axis_dpad_x_to_buttons(e : Event, val : Double, gilrs : Gilrs) -> Event? {
  if !can_map_axis_dpad_to_button(e.id(), gilrs) {
    return Some(e)
  }
  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 gilrs.state(id) {
    None => false
    Some(s) => s.is_pressed(BTN_DPAD_LEFT)
  }
  let pressed_right = match gilrs.state(id) {
    None => false
    Some(s) => s.is_pressed(BTN_DPAD_RIGHT)
  }
  if val == 1.0 {
    release_left = pressed_left
    gilrs.insert_event(
      Event::at(
        id,
        EventType::ButtonChanged(Button::DPadRight, 1.0, BTN_DPAD_RIGHT),
        time,
      ),
    )
    out = Event::at(
      id,
      EventType::ButtonPressed(Button::DPadRight, BTN_DPAD_RIGHT),
      time,
    )
  } else if val == -1.0 {
    release_right = pressed_right
    gilrs.insert_event(
      Event::at(
        id,
        EventType::ButtonChanged(Button::DPadLeft, 1.0, BTN_DPAD_LEFT),
        time,
      ),
    )
    out = Event::at(
      id,
      EventType::ButtonPressed(Button::DPadLeft, BTN_DPAD_LEFT),
      time,
    )
  } else {
    release_left = pressed_left
    release_right = pressed_right
  }
  if release_right {
    if !out.is_dropped() {
      gilrs.insert_event(out)
    }
    gilrs.insert_event(
      Event::at(
        id,
        EventType::ButtonChanged(Button::DPadRight, 0.0, BTN_DPAD_RIGHT),
        time,
      ),
    )
    out = Event::at(
      id,
      EventType::ButtonReleased(Button::DPadRight, BTN_DPAD_RIGHT),
      time,
    )
  }
  if release_left {
    if !out.is_dropped() {
      gilrs.insert_event(out)
    }
    gilrs.insert_event(
      Event::at(
        id,
        EventType::ButtonChanged(Button::DPadLeft, 0.0, BTN_DPAD_LEFT),
        time,
      ),
    )
    out = Event::at(
      id,
      EventType::ButtonReleased(Button::DPadLeft, BTN_DPAD_LEFT),
      time,
    )
  }
  Some(out)
}

///|
fn axis_dpad_y_to_buttons(e : Event, val : Double, gilrs : Gilrs) -> Event? {
  if !can_map_axis_dpad_to_button(e.id(), gilrs) {
    return Some(e)
  }
  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 gilrs.state(id) {
    None => false
    Some(s) => s.is_pressed(BTN_DPAD_UP)
  }
  let pressed_down = match gilrs.state(id) {
    None => false
    Some(s) => s.is_pressed(BTN_DPAD_DOWN)
  }
  if val == 1.0 {
    release_down = pressed_down
    gilrs.insert_event(
      Event::at(
        id,
        EventType::ButtonChanged(Button::DPadUp, 1.0, BTN_DPAD_UP),
        time,
      ),
    )
    out = Event::at(
      id,
      EventType::ButtonPressed(Button::DPadUp, BTN_DPAD_UP),
      time,
    )
  } else if val == -1.0 {
    release_up = pressed_up
    gilrs.insert_event(
      Event::at(
        id,
        EventType::ButtonChanged(Button::DPadDown, 1.0, BTN_DPAD_DOWN),
        time,
      ),
    )
    out = Event::at(
      id,
      EventType::ButtonPressed(Button::DPadDown, BTN_DPAD_DOWN),
      time,
    )
  } else {
    release_up = pressed_up
    release_down = pressed_down
  }
  if release_up {
    if !out.is_dropped() {
      gilrs.insert_event(out)
    }
    gilrs.insert_event(
      Event::at(
        id,
        EventType::ButtonChanged(Button::DPadUp, 0.0, BTN_DPAD_UP),
        time,
      ),
    )
    out = Event::at(
      id,
      EventType::ButtonReleased(Button::DPadUp, BTN_DPAD_UP),
      time,
    )
  }
  if release_down {
    if !out.is_dropped() {
      gilrs.insert_event(out)
    }
    gilrs.insert_event(
      Event::at(
        id,
        EventType::ButtonChanged(Button::DPadDown, 0.0, BTN_DPAD_DOWN),
        time,
      ),
    )
    out = Event::at(
      id,
      EventType::ButtonReleased(Button::DPadDown, BTN_DPAD_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, gilrs : Gilrs) -> Button {
  match gilrs.axis_or_btn_name(id, code) {
    Some(AxisOrBtn::Btn(b)) => b
    _ => Button::Unknown
  }
}

///|
pub fn Repeat::filter(self : Repeat, ev : Event?, gilrs : Gilrs) -> Event? {
  match ev {
    Some(_) => ev
    None => {
      let now0 = gilrs.time()
      let now = if now0 != 0L { now0 } else { 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, gilrs)
                return Some(
                  Event::at(
                    id,
                    EventType::ButtonRepeated(btn, code),
                    data.timestamp() + self.after_ms,
                  ),
                )
              }
              if pressed && repeating && dur >= self.every_ms {
                let btn = code_to_button_name(id, code, gilrs)
                return Some(
                  Event::at(
                    id,
                    EventType::ButtonRepeated(btn, code),
                    data.timestamp() + self.every_ms,
                  ),
                )
              }
            }
        }
      }
      None
    }
  }
}