#![allow(deprecated)]

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

use gilrs;
use gilrs::{EventType, Gilrs};
use serde_derive::{Deserialize, Serialize};
use toml;
use winit::event::{DeviceEvent, ElementState, Event, ModifiersState, MouseButton, MouseScrollDelta, ScanCode, WindowEvent};

use crate::config::LogConfig;

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

#[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,
}

#[derive(Debug, Serialize, Deserialize)]
struct InputConfigButton {
    name: String,
    scan_code: Option<u32>,
    mouse: Option<String>,
    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>,
    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: [f64; 2],
    input_events: HashSet<DigitalInputEvent>,
    pressed_scan_codes: HashSet<ScanCode>,
    pressed_mouse_buttons: HashSet<MouseButton>,
    analog_wheel_state: f32,
    config: InputConfigConfig,
    log_config: LogConfig,
    controller_input: Gilrs,
}

#[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(), analog_wheel_state: 0.0,
            config: config.config, mouse_delta_x: 0.0, mouse_delta_y: 0.0, mouse_position: [0., 0.], log_config,
            controller_input: Gilrs::new().unwrap() };

        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 input = if let Some(scan_code) = bn.scan_code {
                DigitalInput::Keyboard(KeyboardInput { scan_code, modifiers })
            } else if let Some(button_name) = &bn.mouse {
                let button_name_lower = button_name.to_lowercase();
                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 }),
                }
            } else if let Some(controller_button) = &bn.controller_button {
                DigitalInput::Controller(ControllerInput {
                    button: string_to_button(controller_button.as_str()).expect(&format!("Unknown controller button: {}", controller_button.as_str()))
                })
            } else {
                panic!("No mouse or keyboard input for button {:?}", bn.name);
            };

            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] });
            }
        });

        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)) })
                },
                other => panic!("Axis {:?} needs either positive_button and negative_button 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;
    }

    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, 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 = [position.x, position.y];
            },
            _ => {}
        }
    }

    pub fn button_down(self: &Self, button_code: &str) -> bool {
        match self.virtual_buttons.get(button_code) {
            Some(virtual_button) => {
                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 button_just_pressed(self: &Self, button_code: &str) -> bool {
        match self.virtual_buttons.get(button_code) {
            Some(virtual_button) => {
                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) => {
                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
            }
        }
    }

    pub fn get_axis(self: &Self, axis_code: &str) -> 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 } else { 0.0 }
                    },
                    AxisInput::MouseMove(direction, modifiers) => {
                        if self.modifiers_are_pressed(modifiers) {
                            match direction {
                                MouseMoveDirection::X => self.mouse_delta_x as f32,
                                MouseMoveDirection::Y => self.mouse_delta_y as f32,
                            }
                        } else {
                            0.0
                        }
                    },
                    AxisInput::Digital(positive_button, negative_button) => {
                        self.virtual_button_to_float(positive_button) - self.virtual_button_to_float(negative_button)
                    },
                    AxisInput::Controller(controller_input) => {
                         self.controller_input.gamepads()
                             .map(|(_id, gamepad)| gamepad.value(controller_input.axis))
                             .fold(0.0, fold_axis_value)
                    }
                }
            }).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, modifiers: KeyboardModifierState) {
        let input = DigitalInput::Keyboard(KeyboardInput { scan_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 frame_start(self: &mut Self) {
        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();
    }
}

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),
}

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

#[derive(Debug, Eq, PartialEq, Hash, Clone)]
pub struct KeyboardInput {
    scan_code: ScanCode,
    modifiers: KeyboardModifierState,
}

#[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, 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,
    }
}