use x11rb::protocol::{
    xinput,
    xproto::{self, ModMask},
};

use gpui::{Modifiers, MouseButton, NavigationDirection};

pub(crate) enum ButtonOrScroll {
    Button(MouseButton),
    Scroll(ScrollDirection),
}

pub(crate) enum ScrollDirection {
    Up,
    Down,
    Left,
    Right,
}

pub(crate) fn button_or_scroll_from_event_detail(detail: u32) -> Option<ButtonOrScroll> {
    Some(match detail {
        1 => ButtonOrScroll::Button(MouseButton::Left),
        2 => ButtonOrScroll::Button(MouseButton::Middle),
        3 => ButtonOrScroll::Button(MouseButton::Right),
        4 => ButtonOrScroll::Scroll(ScrollDirection::Up),
        5 => ButtonOrScroll::Scroll(ScrollDirection::Down),
        6 => ButtonOrScroll::Scroll(ScrollDirection::Left),
        7 => ButtonOrScroll::Scroll(ScrollDirection::Right),
        8 => ButtonOrScroll::Button(MouseButton::Navigate(NavigationDirection::Back)),
        9 => ButtonOrScroll::Button(MouseButton::Navigate(NavigationDirection::Forward)),
        _ => return None,
    })
}

pub(crate) fn modifiers_from_state(state: xproto::KeyButMask) -> Modifiers {
    Modifiers {
        control: state.contains(xproto::KeyButMask::CONTROL),
        alt: state.contains(xproto::KeyButMask::MOD1),
        shift: state.contains(xproto::KeyButMask::SHIFT),
        platform: state.contains(xproto::KeyButMask::MOD4),
        function: false,
    }
}

pub(crate) fn modifiers_from_xinput_info(modifier_info: xinput::ModifierInfo) -> Modifiers {
    Modifiers {
        control: modifier_info.effective as u16 & ModMask::CONTROL.bits()
            == ModMask::CONTROL.bits(),
        alt: modifier_info.effective as u16 & ModMask::M1.bits() == ModMask::M1.bits(),
        shift: modifier_info.effective as u16 & ModMask::SHIFT.bits() == ModMask::SHIFT.bits(),
        platform: modifier_info.effective as u16 & ModMask::M4.bits() == ModMask::M4.bits(),
        function: false,
    }
}

pub(crate) fn pressed_button_from_mask(button_mask: u32) -> Option<MouseButton> {
    Some(if button_mask & 2 == 2 {
        MouseButton::Left
    } else if button_mask & 4 == 4 {
        MouseButton::Middle
    } else if button_mask & 8 == 8 {
        MouseButton::Right
    } else {
        return None;
    })
}

pub(crate) fn get_valuator_axis_index(
    valuator_mask: &Vec<u32>,
    valuator_number: u16,
) -> Option<usize> {
    // XInput valuator masks have a 1 at the bit indexes corresponding to each
    // valuator present in this event's axisvalues. Axisvalues is ordered from
    // lowest valuator number to highest, so counting bits before the 1 bit for
    // this valuator yields the index in axisvalues.
    if bit_is_set_in_vec(valuator_mask, valuator_number) {
        Some(popcount_upto_bit_index(valuator_mask, valuator_number) as usize)
    } else {
        None
    }
}

/// Returns the number of 1 bits in `bit_vec` for all bits where `i < bit_index`.
fn popcount_upto_bit_index(bit_vec: &Vec<u32>, bit_index: u16) -> u32 {
    let array_index = bit_index as usize / 32;
    let popcount: u32 = bit_vec
        .get(array_index)
        .map_or(0, |bits| keep_bits_upto(*bits, bit_index % 32).count_ones());
    if array_index == 0 {
        popcount
    } else {
        // Valuator numbers over 32 probably never occur for scroll position, but may as well
        // support it.
        let leading_popcount: u32 = bit_vec
            .iter()
            .take(array_index)
            .map(|bits| bits.count_ones())
            .sum();
        popcount + leading_popcount
    }
}

fn bit_is_set_in_vec(bit_vec: &Vec<u32>, bit_index: u16) -> bool {
    let array_index = bit_index as usize / 32;
    bit_vec
        .get(array_index)
        .is_some_and(|bits| bit_is_set(*bits, bit_index % 32))
}

fn bit_is_set(bits: u32, bit_index: u16) -> bool {
    bits & (1 << bit_index) != 0
}

/// Sets every bit with `i >= bit_index` to 0.
fn keep_bits_upto(bits: u32, bit_index: u16) -> u32 {
    if bit_index == 0 {
        0
    } else if bit_index >= 32 {
        u32::MAX
    } else {
        bits & ((1 << bit_index) - 1)
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_get_valuator_axis_index() {
        assert!(get_valuator_axis_index(&vec![0b11], 0) == Some(0));
        assert!(get_valuator_axis_index(&vec![0b11], 1) == Some(1));
        assert!(get_valuator_axis_index(&vec![0b11], 2) == None);

        assert!(get_valuator_axis_index(&vec![0b100], 0) == None);
        assert!(get_valuator_axis_index(&vec![0b100], 1) == None);
        assert!(get_valuator_axis_index(&vec![0b100], 2) == Some(0));
        assert!(get_valuator_axis_index(&vec![0b100], 3) == None);

        assert!(get_valuator_axis_index(&vec![0b1010, 0], 0) == None);
        assert!(get_valuator_axis_index(&vec![0b1010, 0], 1) == Some(0));
        assert!(get_valuator_axis_index(&vec![0b1010, 0], 2) == None);
        assert!(get_valuator_axis_index(&vec![0b1010, 0], 3) == Some(1));

        assert!(get_valuator_axis_index(&vec![0b1010, 0b1], 0) == None);
        assert!(get_valuator_axis_index(&vec![0b1010, 0b1], 1) == Some(0));
        assert!(get_valuator_axis_index(&vec![0b1010, 0b1], 2) == None);
        assert!(get_valuator_axis_index(&vec![0b1010, 0b1], 3) == Some(1));
        assert!(get_valuator_axis_index(&vec![0b1010, 0b1], 32) == Some(2));
        assert!(get_valuator_axis_index(&vec![0b1010, 0b1], 33) == None);

        assert!(get_valuator_axis_index(&vec![0b1010, 0b101], 34) == Some(3));
    }
}
