#![allow(deprecated)]

use std::collections::{HashMap, HashSet};
use std::fs;
use std::hash::Hash;
use std::iter::FromIterator;

use cgmath::{InnerSpace, Vector2, Zero, vec2};
use gilrs;
use gilrs::{EventType, Gilrs};
use rust_engine_proc::perf;
use serde_derive::{Deserialize, Serialize};
use toml;
use winapi::shared::minwindef::UINT;
use winapi::um::winuser;
use winit::event::{DeviceEvent, ElementState, Event, ModifiersState, MouseButton, MouseScrollDelta, ScanCode, Touch, TouchPhase, VirtualKeyCode, WindowEvent};

use crate::config::LogConfig;

#[derive(Debug, Clone)]
pub struct VirtualButton {
    pub digital_inputs: Vec<DigitalInput>,
    pub touch_count: Option<u8>,
}

#[derive(Debug)]
pub struct VirtualAxis {
    pub axis_inputs: Vec<AxisInput>,
}

#[derive(Debug, PartialEq, Eq, Hash)]
pub enum DigitalInputEvent {
    Pressed(DigitalInput),
    Released(DigitalInput),
}

#[derive(Debug, Serialize, Deserialize)]
struct InputConfig {
    config: InputConfigConfig,
    button: Vec<InputConfigButton>,
    axis: Vec<InputConfigAxis>,
}

#[derive(Debug, Serialize, Deserialize)]
struct InputConfigConfig {
    line_height_px: f32,
    mouse_speed: f32,
}

#[derive(Debug, Serialize, Deserialize)]
struct InputConfigButton {
    name: String,
    scan_code: Option<u32>,
    vk_code: Option<String>,
    mouse: Option<String>,
    touch: Option<u8>,
    controller_button: Option<String>,
    ctrl: Option<bool>,
    shift: Option<bool>,
    alt: Option<bool>,
    logo: Option<bool>,
}

#[derive(Debug, Serialize, Deserialize)]
struct InputConfigAxis {
    name: String,
    positive_button: Option<String>,
    negative_button: Option<String>,
    mouse_axis: Option<String>,
    controller_axis: Option<String>,
    touch_axis: Option<String>,
    ctrl: Option<bool>,
    shift: Option<bool>,
    alt: Option<bool>,
    logo: Option<bool>,
}

#[derive(Debug)]
pub struct InputState {
    pub virtual_buttons: HashMap<String, VirtualButton>,
    pub virtual_axes: HashMap<String, VirtualAxis>,
    pub mouse_delta_x: f64,
    pub mouse_delta_y: f64,
    pub mouse_position: Vector2<f64>,
    pub typed_characters: Vec<char>,
    input_events: HashSet<DigitalInputEvent>,
    pub pressed_scan_codes: HashSet<ScanCode>,
    pub pressed_mouse_buttons: HashSet<MouseButton>,
    pressed_touch_positions: HashMap<u64, Vector2<f64>>,
    analog_wheel_state: f32,
    config: InputConfigConfig,
    log_config: LogConfig,
    controller_input: Gilrs,
    touch_inputs: Vec<TouchInput>,
}

#[derive(Debug, Eq, PartialEq, Hash, Clone)]
pub struct KeyboardModifierState {
    shift: bool,
    ctrl: bool,
    alt: bool,
    logo: bool
}

impl KeyboardModifierState {
    pub fn from_deprecated_state(old_state: &ModifiersState) -> KeyboardModifierState {
        KeyboardModifierState {
            shift: old_state.shift(),
            ctrl: old_state.ctrl(),
            alt: old_state.alt(),
            logo: old_state.logo()
        }
    }
}

impl InputState {
    pub fn new(toml_path: &str, log_config: LogConfig) -> InputState {
        let config: InputConfig = toml::from_slice(&fs::read(toml_path).expect("Failed to read input config!")).expect("Failed to parse input config!");

        let mut state = InputState { virtual_buttons: HashMap::new(), virtual_axes: HashMap::new(),
            input_events: HashSet::new(), pressed_scan_codes: HashSet::new(),
            pressed_mouse_buttons: HashSet::new(), pressed_touch_positions: HashMap::new(), touch_inputs: vec![], analog_wheel_state: 0.0,
            config: config.config, mouse_delta_x: 0.0, mouse_delta_y: 0.0, mouse_position: Vector2::zero(), log_config,
            controller_input: Gilrs::new().unwrap(), typed_characters: vec![] };

        // Create virtual buttons from config
        config.button.iter().for_each(|bn| {
            let modifiers = KeyboardModifierState {
                shift: bn.shift.is_some(),
                ctrl: bn.ctrl.is_some(),
                alt: bn.alt.is_some(),
                logo: bn.logo.is_some()
            };

            let mut inputs = vec![];

            // Keyboard buttons
            if let Some(scan_code) = bn.scan_code {
                inputs.push(DigitalInput::Keyboard(KeyboardInput { scan_code, key_code: None, modifiers: modifiers.clone() }));
            }
            if let Some(vk_code) = &bn.vk_code {
                if let Some(scan_code) = vk_to_scan_code(&vk_code) {
                    inputs.push(DigitalInput::Keyboard(KeyboardInput { scan_code, key_code: None, modifiers: modifiers.clone() }))
                }
            }

            // Mouse buttons
            if let Some(button_name) = &bn.mouse {
                let button_name_lower = button_name.to_lowercase();
                let button_input = match button_name_lower.as_str() {
                    "left" => DigitalInput::Mouse(MouseInput { button: MouseButton::Left, modifiers }),
                    "middle" => DigitalInput::Mouse(MouseInput { button: MouseButton::Middle, modifiers }),
                    "right" => DigitalInput::Mouse(MouseInput { button: MouseButton::Right, modifiers }),
                    "wheelup" => DigitalInput::Wheel(WheelInput { direction: WheelInputDirection::Up, modifiers }),
                    "wheeldown" => DigitalInput::Wheel(WheelInput { direction: WheelInputDirection::Down, modifiers }),
                    other => DigitalInput::Mouse(MouseInput { button: MouseButton::Other(other.parse().expect(&format!("Unknown button: {:?}", other))), modifiers }),
                };
                inputs.push(button_input);
            }
            
            // Controller buttons
            if let Some(controller_button) = &bn.controller_button {
                inputs.push(DigitalInput::Controller(ControllerInput {
                    button: string_to_button(controller_button.as_str()).expect(&format!("Unknown controller button: {}", controller_button.as_str()))
                }));
            }

            // Touch clicks
            if let Some(_) = bn.touch {
                if let Some(virtual_button) = state.virtual_buttons.get_mut(&bn.name) {
                    virtual_button.touch_count = bn.touch;
                } else {
                    state.virtual_buttons.insert(bn.name.clone(), VirtualButton { digital_inputs: vec![], touch_count: bn.touch });
                }
            }

            // Insert digital inputs
            for input in inputs {
                if let Some(virtual_button) = state.virtual_buttons.get_mut(&bn.name) {
                    virtual_button.digital_inputs.push(input);
                } else {
                    state.virtual_buttons.insert(bn.name.clone(), VirtualButton { digital_inputs: vec![input], touch_count: bn.touch });
                }
            }
        });

        // Create virtual axes from config
        config.axis.iter().for_each(|axis| {
            let axis_input = match axis {
                InputConfigAxis { positive_button: Some(pos_button_name), negative_button: Some(neg_button_name), .. } => {
                    let positive_button = state.virtual_buttons.get(pos_button_name)
                        .expect(&format!("Button {:?} of axis {:?} not found!", axis.positive_button, axis.name))
                        .clone();
                    let negative_button = state.virtual_buttons.get(neg_button_name)
                        .expect(&format!("Button {:?} of axis {:?} not found!", axis.positive_button, axis.name))
                        .clone();

                    AxisInput::Digital(positive_button, negative_button)
                },
                InputConfigAxis { mouse_axis: Some(axis_name), .. } => {
                    let modifiers = KeyboardModifierState {
                        shift: axis.shift.is_some(),
                        ctrl: axis.ctrl.is_some(),
                        alt: axis.alt.is_some(),
                        logo: axis.logo.is_some(),
                    };
                    match axis_name.to_lowercase().as_str() {
                        "wheel" => AxisInput::Wheel(modifiers),
                        "x" => AxisInput::MouseMove(MouseMoveDirection::X, modifiers),
                        "y" => AxisInput::MouseMove(MouseMoveDirection::Y, modifiers),
                        other => panic!("Axis {:?} has unknown mouse axis name {:?}!", axis_name, other),
                    }
                },
                InputConfigAxis { controller_axis: Some(controller_axis_name), .. } => {
                    AxisInput::Controller(AnalogControllerInput { axis: string_to_axis(controller_axis_name).expect(&format!("Unknown controller axis: {}", controller_axis_name)) })
                },
                InputConfigAxis { touch_axis: Some(touch_axis_name), .. } => {
                    if let Some(axis) = string_to_touch_axis(touch_axis_name) {
                        AxisInput::Touch(axis)
                    } else {
                        panic!("Unknown touch axis {:?}", touch_axis_name);
                    }
                },
                other => panic!("Axis {:?} needs either (positive_button and negative_button) or (touch_axis) or (mouse_wheel) or (controller_axis must be set to true)", other.name)
            };

            if let Some(virtual_axis) = state.virtual_axes.get_mut(&axis.name) {
                virtual_axis.axis_inputs.push(axis_input);
            } else {
                state.virtual_axes.insert(axis.name.clone(), VirtualAxis { axis_inputs: vec![axis_input] });
            }
        });

        return state;
    }

    #[perf("input_events", crate::perf::PerformanceCounter)]
    pub fn on_window_event(self: &mut Self, event: &Event<()>) {
        match event {
            Event::WindowEvent { event: WindowEvent::KeyboardInput { device_id, input, .. }, .. } => {
                if self.log_config.input.buttons {
                    let mods = mods_to_string(&KeyboardModifierState::from_deprecated_state(&input.modifiers));
                    if mods.len() > 0 {
                        println!("Keyboard {:?} {:?} {:?} + {:?} {:?}", device_id, input.state, &mods, input.virtual_keycode, input.scancode)
                    } else {
                        println!("Keyboard {:?} {:?} {:?} {:?}", device_id, input.state, input.virtual_keycode, input.scancode)
                    }
                }

                self.on_keyboard_event(input.state, input.scancode, input.virtual_keycode, KeyboardModifierState::from_deprecated_state(&input.modifiers));
            },
            Event::WindowEvent { event: WindowEvent::MouseInput { device_id, state, button, modifiers }, .. } => {
                if self.log_config.input.buttons {
                    let mods = mods_to_string(&KeyboardModifierState::from_deprecated_state(&modifiers));
                    if mods.len() > 0 {
                        println!("Mouse {:?} {:?} {:?} + {:?}", device_id, state, &mods, button)
                    } else {
                        println!("Mouse {:?} {:?} {:?}", device_id, state, button)
                    }
                }

                self.on_mouse_event(state, button, &KeyboardModifierState::from_deprecated_state(&modifiers));
            },
            Event::WindowEvent { event: WindowEvent::MouseWheel { device_id, delta, phase, modifiers }, .. } => {
                if self.log_config.input.buttons {
                    let mods = mods_to_string(&KeyboardModifierState::from_deprecated_state(&modifiers));
                    if mods.len() > 0 {
                        println!("Scroll {:?} {:?} {:?} + {:?}", device_id, phase, &mods, delta)
                    } else {
                        println!("Scroll {:?} {:?} {:?}", device_id, phase, delta)
                    }
                }

                self.on_mouse_wheel_event(delta, &KeyboardModifierState::from_deprecated_state(&modifiers));
            },
            Event::DeviceEvent { device_id, event: DeviceEvent::MouseMotion { delta: (delta_x, delta_y) } } => {
                if self.log_config.input.mouse_motion {
                    println!("MouseMotion {:?}, ({:?},{:?})", device_id, delta_x, delta_y);
                }
                self.mouse_delta_x += *delta_x;
                self.mouse_delta_y += *delta_y;
            },
            Event::WindowEvent { event: WindowEvent::CursorMoved { position, .. }, .. } => {
                self.mouse_position = vec2(position.x, position.y);
            },
            Event::WindowEvent { event: WindowEvent::Touch(touch), .. } => {
                if self.log_config.input.touch {
                    println!("Touch {:?}, at {:?}, id: {:?}, force: {:?}", touch.phase, touch.location, touch.id, touch.force);
                }
                self.on_touch_event(touch);
            },
            Event::WindowEvent { event: WindowEvent::ReceivedCharacter(c), .. } => {
                self.typed_characters.push(*c);
            },
            _ => {}
        }
    }

    #[allow(dead_code)]
    pub fn button_down(self: &Self, button_code: &str) -> bool {
        match self.virtual_buttons.get(button_code) {
            Some(virtual_button) => {
                if let Some(count) = virtual_button.touch_count {
                    if self.pressed_touch_positions.len() == count as usize {
                        return true;
                    }
                }
                virtual_button.digital_inputs.iter().any(|vi| self.digital_input_pressed(vi))
            }
            None => {
                if self.log_config.input.buttons {
                    println!("Button {:?} not found!", button_code);
                }
                false
            }
        }
    }

    pub fn _scan_code_just_pressed(&self, scan_code: &ScanCode) -> bool {
        self.input_events.iter().any(|ie| match ie {
            DigitalInputEvent::Pressed(di) => match di {
                DigitalInput::Keyboard(ki) => ki.scan_code == *scan_code,
                _ => false,
            },
            _ => false,
        })
    }

    pub fn _scan_code_just_released(&self, scan_code: &ScanCode) -> bool {
        self.input_events.iter().any(|ie| match ie {
            DigitalInputEvent::Released(di) => match di {
                DigitalInput::Keyboard(ki) => ki.scan_code == *scan_code,
                _ => false,
            },
            _ => false,
        })
    }

    pub fn button_just_pressed(self: &Self, button_code: &str) -> bool {
        match self.virtual_buttons.get(button_code) {
            Some(virtual_button) => {
                if let Some(count) = virtual_button.touch_count {
                    if self.pressed_touch_positions.len() == count as usize
                        && self.touch_inputs.iter().any(|ti| ti.phase == TouchPhase::Started) {
                        return true;
                    }
                }

                self.input_events.iter().any(|input_event| {
                    if let DigitalInputEvent::Pressed(digital_input) = input_event {
                        virtual_button.digital_inputs.iter().any(|virtual_button_input| virtual_button_input == digital_input)
                    } else {
                        false
                    }
                })
            }
            None => {
                if self.log_config.input.missing_bindings {
                    println!("Button {:?} not found!", button_code);
                }
                false
            }
        }
    }

    pub fn button_just_released(self: &Self, button_code: &str) -> bool {
        match self.virtual_buttons.get(button_code) {
            Some(virtual_button) => {
                if let Some(count) = virtual_button.touch_count {
                    if self.pressed_touch_positions.len() < count as usize
                        && self.touch_inputs.iter().any(|ti| ti.phase == TouchPhase::Ended) {
                        return true;
                    }
                }

                self.input_events.iter().any(|input_event| {
                    if let DigitalInputEvent::Released(digital_input) = input_event {
                        virtual_button.digital_inputs.iter().any(|virtual_button_input| virtual_button_input == digital_input)
                    } else {
                        false
                    }
                })
            }
            None => {
                if self.log_config.input.missing_bindings {
                    println!("Button {:?} not found!", button_code);
                }
                false
            }
        }
    }

    fn get_touch_drag_distance(&self) -> Vector2<f64> {
        if let Some(old_pos) = self.pressed_touch_positions.values().next() {
            if let Some(newest_input) = self.touch_inputs.iter().filter(|ti| ti.phase == TouchPhase::Started || ti.phase == TouchPhase::Moved).last() {
                newest_input.touch_location - old_pos
            } else {
                vec2(0.0, 0.0)
            }
        } else {
            vec2(0.0, 0.0)
        }
    }

    pub fn get_axis_timescaled(self: &Self, axis_code: &str, delta_time: f32) -> f32 {
        if let Some(axis) = self.virtual_axes.get(axis_code) {
            axis.axis_inputs.iter().map(|item| {
                match &item {
                    AxisInput::Wheel(modifiers) => {
                        if self.modifiers_are_pressed(modifiers) { self.analog_wheel_state * delta_time } else { 0.0 }
                    },
                    AxisInput::MouseMove(direction, modifiers) => {
                        if self.modifiers_are_pressed(modifiers) {
                            match direction {
                                MouseMoveDirection::X => self.mouse_delta_x as f32 * self.config.mouse_speed,
                                MouseMoveDirection::Y => self.mouse_delta_y as f32 * self.config.mouse_speed,
                            }
                        } else {
                            0.0
                        }
                    },
                    AxisInput::Digital(positive_button, negative_button) => {
                        (self.virtual_button_to_float(positive_button) - self.virtual_button_to_float(negative_button)) * delta_time
                    },
                    AxisInput::Controller(controller_input) => {
                         self.controller_input.gamepads()
                             .map(|(_id, gamepad)| gamepad.value(controller_input.axis))
                             .fold(0.0, fold_axis_value) * delta_time
                    },
                    &AxisInput::Touch(touch_axis) => {
                        match touch_axis {
                            TouchAxis::Horizontal => {
                                if self.pressed_touch_positions.len() == 1 {
                                    self.get_touch_drag_distance().x as f32
                                } else {
                                    0.0
                                }
                            },
                            TouchAxis::Vertical => {
                                if self.pressed_touch_positions.len() == 1 {
                                    self.get_touch_drag_distance().y as f32
                                } else {
                                    0.0
                                }
                            },
                            TouchAxis::Rotate => {
                                if self.pressed_touch_positions.len() == 2 {
                                    let mut positions = self.pressed_touch_positions.iter();
                                    let (id_1, pos_1) = positions.next().unwrap();
                                    let (id_2, pos_2) = positions.next().unwrap();
                                    let mut touch_loc_1 = vec2(0., 0.);
                                    let mut touch_loc_2 = vec2(0., 0.);
                                    let mut touch_1 = None;
                                    let mut touch_2 = None;

                                    fn filtered_vector_sub(a: Vector2<f64>, b: &Vector2<f64>) -> Option<Vector2<f64>> {
                                        Some(a - b).filter(|v| v.magnitude() > 0.01)
                                    }

                                    for input in &self.touch_inputs {
                                        if input.id == *id_1 {
                                            touch_1 = filtered_vector_sub(input.touch_location, &pos_1);
                                            touch_loc_1 = input.touch_location;
                                        }
                                        if input.id == *id_2 {
                                            touch_2 = filtered_vector_sub(input.touch_location, &pos_2);
                                            touch_loc_2 = input.touch_location;
                                        }
                                    }
                                    
                                    let diff = touch_loc_2 - touch_loc_1;
                                    let norm_n = vec2(-diff.y, diff.x).normalize();
                                    
                                    match (touch_1, touch_2) {
                                        (Some(v1), Some(v2)) => {
                                            let v1_n = v1.normalize();
                                            let v2_n = v2.normalize();

                                            let direction = v1_n.dot(norm_n) - v2_n.dot(norm_n);
                                            let distance = (v1.magnitude() + v2.magnitude()) / 2.;
                                            (direction * distance) as f32
                                        },
                                        (Some(v1), None) => {
                                            let direction = v1.normalize().dot(norm_n);
                                            (direction * v1.magnitude()) as f32
                                        },
                                        (None, Some(v2)) => {
                                            let direction = v2.normalize().dot(norm_n);
                                            (-direction * v2.magnitude()) as f32
                                        },
                                        (None, None) => { 0.0 }
                                    }
                                } else {
                                    0.0
                                }
                            },
                        }
                    },
                }
            }).fold(0.0, fold_axis_value)
        } else {
            if self.log_config.input.missing_bindings {
                println!("Axis {:?} not found!", axis_code);
            }
            0.0
        }
    }

    pub fn digital_input_pressed(self: &Self, digital_input: &DigitalInput) -> bool {
        match digital_input {
            DigitalInput::Keyboard(keyboard_input) => {
                self.pressed_scan_codes.contains(&keyboard_input.scan_code)
                    && self.modifiers_are_pressed(&keyboard_input.modifiers)
            },
            DigitalInput::Mouse(mouse_input) => {
                self.pressed_mouse_buttons.contains(&mouse_input.button)
                    && self.modifiers_are_pressed(&mouse_input.modifiers)
            },
            DigitalInput::Wheel(wheel_input) => {
                self.input_events.contains(&DigitalInputEvent::Pressed(DigitalInput::Wheel(wheel_input.clone())))
            },
            DigitalInput::Controller(controller_input) => {
                self.controller_input.gamepads().any(|(_id, gamepad)| gamepad.is_pressed(controller_input.button))
            }
        }
    }

    fn modifiers_are_pressed(self: &Self, modifiers: &KeyboardModifierState) -> bool {
           (!modifiers.ctrl  || self.pressed_scan_codes.contains(&29))
        && (!modifiers.shift || self.pressed_scan_codes.contains(&42) || self.pressed_scan_codes.contains(&54))
        && (!modifiers.alt   || self.pressed_scan_codes.contains(&56))
        && (!modifiers.logo  || self.pressed_scan_codes.contains(&91) || self.pressed_scan_codes.contains(&92))
    }

    fn virtual_button_to_float(self: &Self, input: &VirtualButton) -> f32 {
        if input.digital_inputs.iter().any(|di| self.digital_input_pressed(di)) { 1.0 } else { 0.0 }
    }

    pub fn on_keyboard_event(self: &mut Self, state: ElementState, scan_code: ScanCode, key_code: Option<VirtualKeyCode>, modifiers: KeyboardModifierState) {
        let input = DigitalInput::Keyboard(KeyboardInput { scan_code, key_code, modifiers });
        match state {
            ElementState::Pressed => {
                if self.pressed_scan_codes.contains(&scan_code) { return; }
                self.input_events.insert(DigitalInputEvent::Pressed(input));
                self.pressed_scan_codes.insert(scan_code);
            },
            ElementState::Released => {
                self.input_events.insert(DigitalInputEvent::Released(input));
                self.pressed_scan_codes.remove(&scan_code);
            },
        }
    }

    pub fn on_mouse_event(self: &mut Self, state: &ElementState, button: &MouseButton, modifiers: &KeyboardModifierState) {
        let input = DigitalInput::Mouse(MouseInput { button: button.clone(), modifiers: modifiers.clone() });
        match state {
            ElementState::Pressed => {
                self.input_events.insert(DigitalInputEvent::Pressed(input));
                self.pressed_mouse_buttons.insert(button.clone());
            },
            ElementState::Released => {
                self.input_events.insert(DigitalInputEvent::Released(input));
                self.pressed_mouse_buttons.remove(button);
            },
        }
    }

    pub fn on_mouse_wheel_event(self: &mut Self, delta: &MouseScrollDelta, modifiers: &KeyboardModifierState) {
        let vertical_direction = match delta {
            MouseScrollDelta::LineDelta(_x, y)   => {
                if *y > 0.0 { Some(WheelInputDirection::Up) }
                else if *y < 0.0 { Some(WheelInputDirection::Down) }
                else { None }
            },
            MouseScrollDelta::PixelDelta(pixels) => {
                if pixels.y > 0.0 { Some(WheelInputDirection::Up) }
                else if pixels.y < 0.0 { Some(WheelInputDirection::Down) }
                else { None }
            }
        };

        if let Some(direction) = vertical_direction {
            let input = DigitalInput::Wheel(WheelInput { direction, modifiers: modifiers.clone() });
            self.input_events.insert(DigitalInputEvent::Pressed(input.clone()));
            self.input_events.insert(DigitalInputEvent::Released(input));
        }

        self.analog_wheel_state = match delta {
            MouseScrollDelta::LineDelta(_x, y) => *y * self.config.line_height_px,
            MouseScrollDelta::PixelDelta(pixels) => pixels.y as f32,
        };
    }

    pub fn on_touch_event(&mut self, event: &Touch) {
        self.mouse_position = vec2(event.location.x, event.location.y);
        match event.phase {
            winit::event::TouchPhase::Started => {
                self.pressed_touch_positions.insert(event.id, self.mouse_position);
                self.touch_inputs.push(TouchInput { id: event.id, touch_location: self.mouse_position, phase: event.phase });
            },
            winit::event::TouchPhase::Moved => {
                self.touch_inputs.push(TouchInput { id: event.id, touch_location: self.mouse_position, phase: event.phase });
            },
            winit::event::TouchPhase::Ended => {
                self.pressed_touch_positions.remove(&event.id);
                self.touch_inputs.push(TouchInput { id: event.id, touch_location: self.mouse_position, phase: event.phase });
            },
            winit::event::TouchPhase::Cancelled => {
                self.pressed_touch_positions.remove(&event.id);
            },
        }
    }

    pub fn frame_start(self: &mut Self) {
        // Read out all controller events
        while let Some(event) = self.controller_input.next_event() {
            match event.event {
                EventType::ButtonPressed(button, _) => {
                    if self.log_config.input.buttons { println!("ControllerButton Pressed {:?} {:?}", event.id, button); }
                    self.input_events.insert(DigitalInputEvent::Pressed(DigitalInput::Controller(ControllerInput { button })));
                },
                EventType::ButtonRepeated(_, _) => {},
                EventType::ButtonReleased(button, _) => {
                    if self.log_config.input.buttons { println!("ControllerButton Released {:?} {:?}", event.id, button); }
                    self.input_events.insert(DigitalInputEvent::Released(DigitalInput::Controller(ControllerInput { button })));
                },
                EventType::ButtonChanged(_, _, _) => {},
                EventType::AxisChanged(axis, value, _) => {
                    if self.log_config.input.buttons { println!("ControllerAxis {:?} {:?} {:?}", event.id, axis, value); }
                },
                EventType::Connected => {},
                EventType::Disconnected => {},
                EventType::Dropped => {},
            }
        }
    }

    pub fn frame_end(self: &mut Self) {
        self.analog_wheel_state = 0.0;
        self.mouse_delta_x = 0.0;
        self.mouse_delta_y = 0.0;
        self.input_events.clear();
        self.typed_characters.clear();

        // Store final touch positions as base for next frame
        for touch_input in &mut self.touch_inputs {
            self.pressed_touch_positions.get_mut(&touch_input.id).map(|pos| *pos = touch_input.touch_location);
        }
        self.touch_inputs.clear();
    }
}

fn mods_to_string(modifiers: &KeyboardModifierState) -> String {
    String::from_iter(
        vec!["shift", "ctrl", "alt", "logo"].iter()
            .zip(vec![modifiers.shift, modifiers.ctrl, modifiers.alt, modifiers.logo])
            .filter(|(&_name, state)| *state)
            .map(|(&name, _state)| name))
}

#[derive(Debug)]
pub enum AxisInput {
    Wheel(KeyboardModifierState),
    MouseMove(MouseMoveDirection, KeyboardModifierState),
    Digital(VirtualButton, VirtualButton),
    Controller(AnalogControllerInput),
    Touch(TouchAxis)
}

#[derive(Debug, Eq, PartialEq, Clone, Hash)]
pub enum DigitalInput {
    Keyboard(KeyboardInput),
    Wheel(WheelInput),
    Mouse(MouseInput),
    Controller(ControllerInput),
}

#[derive(Debug, Eq, Clone)]
pub struct KeyboardInput {
    scan_code: ScanCode,
    key_code: Option<VirtualKeyCode>,
    modifiers: KeyboardModifierState,
}

impl PartialEq for KeyboardInput {
    fn eq(&self, other: &Self) -> bool {
        (self.scan_code == other.scan_code || (self.key_code != None && self.key_code == other.key_code)) && self.modifiers == other.modifiers
    }
}

impl Hash for KeyboardInput {
    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
        self.scan_code.hash(state);
        self.modifiers.hash(state);
    }
}

#[derive(Debug, Eq, PartialEq, Hash, Clone)]
pub struct MouseInput {
    button: MouseButton,
    modifiers: KeyboardModifierState,
}

#[derive(Debug, Eq, PartialEq, Hash, Clone)]
pub struct WheelInput {
    direction: WheelInputDirection,
    modifiers: KeyboardModifierState,
}

#[derive(Debug, Eq, PartialEq, Hash, Clone)]
pub struct ControllerInput {
    button: gilrs::Button,
}

#[derive(Debug, Clone)]
pub struct TouchInput {
    id: u64,
    touch_location: Vector2<f64>,
    phase: TouchPhase
}

impl Eq for TouchInput {}

impl PartialEq for TouchInput {
    fn eq(&self, other: &Self) -> bool {
        self.id.eq(&other.id) && self.phase.eq(&other.phase)
    }
}

impl Hash for TouchInput {
    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
        state.write_u64(self.id);
        self.phase.hash(state)
    }
}

#[derive(Debug, Eq, PartialEq, Hash, Clone)]
pub enum TouchAxis {
    Horizontal,
    Vertical,
    Rotate
}

#[derive(Debug, Eq, PartialEq, Hash, Clone)]
pub enum WheelInputDirection {
    Up,
    Down,
}

#[derive(Debug, Eq, PartialEq, Hash, Clone)]
pub enum MouseMoveDirection {
    X,
    Y,
}

#[derive(Debug, PartialEq, Clone)]
pub struct AnalogWheelInput {
    value: f32,
    modifiers: KeyboardModifierState,
}

#[derive(Debug, PartialEq, Clone)]
pub struct AnalogControllerInput {
    axis: gilrs::Axis,
}

fn fold_axis_value(fold: f32, it: f32) -> f32 {
    if f32::abs(it) > f32::abs(fold) { it } else { fold }
}

fn string_to_button(button_name: &str) -> Option<gilrs::Button> {
    match button_name {
        "South" => Some(gilrs::Button::South),
        "East" => Some(gilrs::Button::East),
        "North" => Some(gilrs::Button::North),
        "West" => Some(gilrs::Button::West),
        "C" => Some(gilrs::Button::C),
        "Z" => Some(gilrs::Button::Z),
        // Triggers
        "LeftTrigger" => Some(gilrs::Button::LeftTrigger),
        "LeftTrigger2" => Some(gilrs::Button::LeftTrigger2),
        "RightTrigger" => Some(gilrs::Button::RightTrigger),
        "RightTrigger2" => Some(gilrs::Button::RightTrigger2),
        // Menu Pad
        "Select" => Some(gilrs::Button::Select),
        "Start" => Some(gilrs::Button::Start),
        "Mode" => Some(gilrs::Button::Mode),
        // Sticks
        "LeftThumb" => Some(gilrs::Button::LeftThumb),
        "RightThumb" => Some(gilrs::Button::RightThumb),
        // D-Pad
        "DPadUp" => Some(gilrs::Button::DPadUp),
        "DPadDown" => Some(gilrs::Button::DPadDown),
        "DPadLeft" => Some(gilrs::Button::DPadLeft),
        "DPadRight" => Some(gilrs::Button::DPadRight),

        _ => None,
    }
}

fn string_to_axis(axis_name: &str) -> Option<gilrs::Axis> {
    match axis_name {
        "LeftStickX" => Some(gilrs::Axis::LeftStickX),
        "LeftStickY" => Some(gilrs::Axis::LeftStickY),
        "LeftZ" => Some(gilrs::Axis::LeftZ),
        "RightStickX" => Some(gilrs::Axis::RightStickX),
        "RightStickY" => Some(gilrs::Axis::RightStickY),
        "RightZ" => Some(gilrs::Axis::RightZ),
        "DPadX" => Some(gilrs::Axis::DPadX),
        "DPadY" => Some(gilrs::Axis::DPadY),
        _ => None,
    }
}

fn string_to_touch_axis(touch_axis_name: &str) -> Option<TouchAxis> {
    match touch_axis_name {
        "horizontal" => Some(TouchAxis::Horizontal),
        "vertical" => Some(TouchAxis::Vertical),
        "rotate" => Some(TouchAxis::Rotate),
        _ => None,
    }
}


// https://searchfox.org/mozilla-central/source/widget/windows/KeyboardLayout.cpp
const VIRTUAL_KEY_CODES: [&str; 256] = [
    "NULL",
    "VK_LBUTTON",
    "VK_RBUTTON",
    "VK_CANCEL",
    "VK_MBUTTON",
    "VK_XBUTTON1",
    "VK_XBUTTON2",
    "0x07",
    "VK_BACK",
    "VK_TAB",
    "0x0A",
    "0x0B",
    "VK_CLEAR",
    "VK_RETURN",
    "0x0E",
    "0x0F",

    "VK_SHIFT",
    "VK_CONTROL",
    "VK_MENU",
    "VK_PAUSE",
    "VK_CAPITAL",
    "VK_KANA, VK_HANGUL",
    "0x16",
    "VK_JUNJA",
    "VK_FINAL",
    "VK_HANJA, VK_KANJI",
    "0x1A",
    "VK_ESCAPE",
    "VK_CONVERT",
    "VK_NONCONVERT",
    "VK_ACCEPT",
    "VK_MODECHANGE",

    "VK_SPACE",
    "VK_PRIOR",
    "VK_NEXT",
    "VK_END",
    "VK_HOME",
    "VK_LEFT",
    "VK_UP",
    "VK_RIGHT",
    "VK_DOWN",
    "VK_SELECT",
    "VK_PRINT",
    "VK_EXECUTE",
    "VK_SNAPSHOT",
    "VK_INSERT",
    "VK_DELETE",
    "VK_HELP",

    "VK_0",
    "VK_1",
    "VK_2",
    "VK_3",
    "VK_4",
    "VK_5",
    "VK_6",
    "VK_7",
    "VK_8",
    "VK_9",
    "0x3A",
    "0x3B",
    "0x3C",
    "0x3D",
    "0x3E",
    "0x3F",

    "0x40",
    "VK_A",
    "VK_B",
    "VK_C",
    "VK_D",
    "VK_E",
    "VK_F",
    "VK_G",
    "VK_H",
    "VK_I",
    "VK_J",
    "VK_K",
    "VK_L",
    "VK_M",
    "VK_N",
    "VK_O",

    "VK_P",
    "VK_Q",
    "VK_R",
    "VK_S",
    "VK_T",
    "VK_U",
    "VK_V",
    "VK_W",
    "VK_X",
    "VK_Y",
    "VK_Z",
    "VK_LWIN",
    "VK_RWIN",
    "VK_APPS",
    "0x5E",
    "VK_SLEEP",

    "VK_NUMPAD0",
    "VK_NUMPAD1",
    "VK_NUMPAD2",
    "VK_NUMPAD3",
    "VK_NUMPAD4",
    "VK_NUMPAD5",
    "VK_NUMPAD6",
    "VK_NUMPAD7",
    "VK_NUMPAD8",
    "VK_NUMPAD9",
    "VK_MULTIPLY",
    "VK_ADD",
    "VK_SEPARATOR",
    "VK_SUBTRACT",
    "VK_DECIMAL",
    "VK_DIVIDE",

    "VK_F1",
    "VK_F2",
    "VK_F3",
    "VK_F4",
    "VK_F5",
    "VK_F6",
    "VK_F7",
    "VK_F8",
    "VK_F9",
    "VK_F10",
    "VK_F11",
    "VK_F12",
    "VK_F13",
    "VK_F14",
    "VK_F15",
    "VK_F16",

    "VK_F17",
    "VK_F18",
    "VK_F19",
    "VK_F20",
    "VK_F21",
    "VK_F22",
    "VK_F23",
    "VK_F24",
    "0x88",
    "0x89",
    "0x8A",
    "0x8B",
    "0x8C",
    "0x8D",
    "0x8E",
    "0x8F",

    "VK_NUMLOCK",
    "VK_SCROLL",
    "VK_OEM_NEC_EQUAL, VK_OEM_FJ_JISHO",
    "VK_OEM_FJ_MASSHOU",
    "VK_OEM_FJ_TOUROKU",
    "VK_OEM_FJ_LOYA",
    "VK_OEM_FJ_ROYA",
    "0x97",
    "0x98",
    "0x99",
    "0x9A",
    "0x9B",
    "0x9C",
    "0x9D",
    "0x9E",
    "0x9F",

    "VK_LSHIFT",
    "VK_RSHIFT",
    "VK_LCONTROL",
    "VK_RCONTROL",
    "VK_LMENU",
    "VK_RMENU",
    "VK_BROWSER_BACK",
    "VK_BROWSER_FORWARD",
    "VK_BROWSER_REFRESH",
    "VK_BROWSER_STOP",
    "VK_BROWSER_SEARCH",
    "VK_BROWSER_FAVORITES",
    "VK_BROWSER_HOME",
    "VK_VOLUME_MUTE",
    "VK_VOLUME_DOWN",
    "VK_VOLUME_UP",

    "VK_MEDIA_NEXT_TRACK",
    "VK_MEDIA_PREV_TRACK",
    "VK_MEDIA_STOP",
    "VK_MEDIA_PLAY_PAUSE",
    "VK_LAUNCH_MAIL",
    "VK_LAUNCH_MEDIA_SELECT",
    "VK_LAUNCH_APP1",
    "VK_LAUNCH_APP2",
    "0xB8",
    "0xB9",
    "VK_OEM_1",
    "VK_OEM_PLUS",
    "VK_OEM_COMMA",
    "VK_OEM_MINUS",
    "VK_OEM_PERIOD",
    "VK_OEM_2",

    "VK_OEM_3",
    "VK_ABNT_C1",
    "VK_ABNT_C2",
    "0xC3",
    "0xC4",
    "0xC5",
    "0xC6",
    "0xC7",
    "0xC8",
    "0xC9",
    "0xCA",
    "0xCB",
    "0xCC",
    "0xCD",
    "0xCE",
    "0xCF",

    "0xD0",
    "0xD1",
    "0xD2",
    "0xD3",
    "0xD4",
    "0xD5",
    "0xD6",
    "0xD7",
    "0xD8",
    "0xD9",
    "0xDA",
    "VK_OEM_4",
    "VK_OEM_5",
    "VK_OEM_6",
    "VK_OEM_7",
    "VK_OEM_8",

    "0xE0",
    "VK_OEM_AX",
    "VK_OEM_102",
    "VK_ICO_HELP",
    "VK_ICO_00",
    "VK_PROCESSKEY",
    "VK_ICO_CLEAR",
    "VK_PACKET",
    "0xE8",
    "VK_OEM_RESET",
    "VK_OEM_JUMP",
    "VK_OEM_PA1",
    "VK_OEM_PA2",
    "VK_OEM_PA3",
    "VK_OEM_WSCTRL",
    "VK_OEM_CUSEL",

    "VK_OEM_ATTN",
    "VK_OEM_FINISH",
    "VK_OEM_COPY",
    "VK_OEM_AUTO",
    "VK_OEM_ENLW",
    "VK_OEM_BACKTAB",
    "VK_ATTN",
    "VK_CRSEL",
    "VK_EXSEL",
    "VK_EREOF",
    "VK_PLAY",
    "VK_ZOOM",
    "VK_NONAME",
    "VK_PA1",
    "VK_OEM_CLEAR",
    "0xFF"
];

pub fn vk_to_scan_code(name: &str) -> Option<ScanCode> {
    let index = VIRTUAL_KEY_CODES.iter().position(|c| c == &name);
    if let Some(idx) = index {
        unsafe {
            let vc = winuser::MapVirtualKeyA(idx as UINT, winuser::MAPVK_VK_TO_VSC);
            if vc == 0 { return None }
            return Some(vc as ScanCode);
        }
    }
    None
}