use std::{
    borrow::Borrow,
    error::Error,
    fmt::{Debug, Display, Formatter, Result as FmtResult},
    hash::Hash,
};
use derivative::Derivative;
use fnv::FnvHashMap as HashMap;
use serde::{Deserialize, Serialize};
use smallvec::SmallVec;
use super::{Axis, Button};
pub trait BindingTypes: Debug + Send + Sync + 'static {
    
    type Axis: Clone + Debug + Hash + Eq + Send + Sync + 'static;
    
    type Action: Clone + Debug + Hash + Eq + Send + Sync + 'static;
}
#[derive(Debug, Default, Clone, PartialEq, Eq, Hash)]
pub struct StringBindings;
impl BindingTypes for StringBindings {
    type Axis = String;
    type Action = String;
}
#[derive(Derivative, Serialize, Deserialize)]
#[derivative(Debug(bound = ""), Default(bound = ""), Clone(bound = ""))]
#[serde(bound(
    serialize = "T::Axis: Serialize, T::Action: Serialize",
    deserialize = "T::Axis: Deserialize<'de>, T::Action: Deserialize<'de>",
))]
pub struct Bindings<T: BindingTypes> {
    pub(super) axes: HashMap<T::Axis, Axis>,
    
    
    
    
    pub(super) actions: HashMap<T::Action, SmallVec<[SmallVec<[Button; 2]>; 4]>>,
}
#[derive(Clone, Derivative)]
#[derivative(Debug(bound = ""))]
pub enum BindingError<T: BindingTypes> {
    
    MouseWheelAxisAlreadyBound(T::Axis),
    
    ComboContainsDuplicates(T::Action),
    
    ComboAlreadyBound(T::Action),
    
    ButtonBoundToAxis(T::Axis, Axis),
    
    AxisButtonAlreadyBoundToAxis(T::Axis, Axis),
    
    AxisButtonAlreadyBoundToAction(T::Action, Button),
    
    
    ControllerAxisAlreadyBound(T::Axis),
}
impl<T: BindingTypes> PartialEq for BindingError<T> {
    fn eq(&self, other: &Self) -> bool {
        match (self, other) {
            (
                BindingError::ComboContainsDuplicates(a),
                BindingError::ComboContainsDuplicates(x),
            ) => a == x,
            (BindingError::ComboAlreadyBound(a), BindingError::ComboAlreadyBound(x)) => a == x,
            (BindingError::ButtonBoundToAxis(a, b), BindingError::ButtonBoundToAxis(x, y)) => {
                a == x && b == y
            }
            (
                BindingError::AxisButtonAlreadyBoundToAxis(a, b),
                BindingError::AxisButtonAlreadyBoundToAxis(x, y),
            ) => a == x && b == y,
            (
                BindingError::AxisButtonAlreadyBoundToAction(a, b),
                BindingError::AxisButtonAlreadyBoundToAction(x, y),
            ) => a == x && b == y,
            (
                BindingError::ControllerAxisAlreadyBound(a),
                BindingError::ControllerAxisAlreadyBound(x),
            ) => a == x,
            (
                BindingError::MouseWheelAxisAlreadyBound(a),
                BindingError::MouseWheelAxisAlreadyBound(x),
            ) => a == x,
            (_, _) => false,
        }
    }
}
impl<T: BindingTypes> Display for BindingError<T>
where
    T::Action: Display,
    T::Axis: Display,
{
    fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
        match *self {
            BindingError::ComboContainsDuplicates(ref id) => write!(
                f,
                "Combo provided contained two (or more) of the same button: {}",
                id
            ),
            BindingError::ComboAlreadyBound(ref action) => {
                write!(f, "Combo provided was already bound to action {}", action)
            }
            BindingError::ButtonBoundToAxis(ref id, ref _axis) => {
                write!(f, "Button provided was a button in use by axis {}", id)
            }
            BindingError::AxisButtonAlreadyBoundToAxis(ref id, ref _axis) => write!(
                f,
                "Axis provided contained a button that's already in use by axis {}",
                id
            ),
            BindingError::AxisButtonAlreadyBoundToAction(ref id, ref _action) => write!(
                f,
                "Axis provided contained a button that's already in use by single button action {}",
                id
            ),
            BindingError::ControllerAxisAlreadyBound(ref id) => {
                write!(f, "Controller axis provided is already in use by {}", id)
            }
            BindingError::MouseWheelAxisAlreadyBound(ref id) => {
                write!(f, "Mouse wheel axis provided is already in use by {}", id)
            }
        }
    }
}
impl<T: BindingTypes> Error for BindingError<T>
where
    T::Action: Display,
    T::Axis: Display,
{
}
#[derive(Debug, Clone, PartialEq)]
pub enum ActionRemovedError {
    
    ActionExistsButBindingDoesnt,
    
    ActionNotFound,
    
    BindingContainsDuplicates,
}
impl Display for ActionRemovedError {
    fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
        match *self {
            ActionRemovedError::ActionExistsButBindingDoesnt => {
                write!(f, "Action found, but binding isn't present")
            }
            ActionRemovedError::ActionNotFound => write!(f, "Action not found"),
            ActionRemovedError::BindingContainsDuplicates => {
                write!(f, "Binding removal requested contains duplicate buttons")
            }
        }
    }
}
impl Error for ActionRemovedError {}
impl<T: BindingTypes> Bindings<T> {
    
    pub fn new() -> Self {
        Default::default()
    }
}
impl<T: BindingTypes> Bindings<T> {
    
    
    
    
    pub fn insert_axis<A: Into<T::Axis>>(
        &mut self,
        id: A,
        axis: Axis,
    ) -> Result<Option<Axis>, BindingError<T>> {
        let id = id.into();
        self.check_axis_invariants(&id, &axis)?;
        Ok(self.axes.insert(id, axis))
    }
    
    pub fn remove_axis<A>(&mut self, id: &A) -> Option<Axis>
    where
        T::Axis: Borrow<A>,
        A: Hash + Eq + ?Sized,
    {
        self.axes.remove(id)
    }
    
    pub fn axis<A>(&self, id: &A) -> Option<&Axis>
    where
        T::Axis: Borrow<A>,
        A: Hash + Eq + ?Sized,
    {
        self.axes.get(id)
    }
    
    pub fn axes(&self) -> impl Iterator<Item = &T::Axis> {
        self.axes.keys()
    }
    
    
    
    pub fn insert_action_binding<B: IntoIterator<Item = Button>>(
        &mut self,
        id: T::Action,
        binding: B,
    ) -> Result<(), BindingError<T>> {
        let bind: SmallVec<[Button; 2]> = binding.into_iter().collect();
        self.check_action_invariants(&id, bind.as_slice())?;
        let mut make_new = false;
        match self.actions.get_mut(&id) {
            Some(action_bindings) => {
                action_bindings.push(bind.clone());
            }
            None => {
                make_new = true;
            }
        }
        if make_new {
            let mut bindings = SmallVec::new();
            bindings.push(bind.clone());
            self.actions.insert(id, bindings);
        }
        Ok(())
    }
    
    pub fn remove_action_binding<A>(
        &mut self,
        id: &A,
        binding: &[Button],
    ) -> Result<(), ActionRemovedError>
    where
        T::Action: Borrow<A>,
        A: Hash + Eq + ?Sized,
    {
        for i in 0..binding.len() {
            for j in (i + 1)..binding.len() {
                if binding[i] == binding[j] {
                    return Err(ActionRemovedError::BindingContainsDuplicates);
                }
            }
        }
        let kill_it;
        if let Some(action_bindings) = self.actions.get_mut(id) {
            let index = action_bindings.iter().position(|b| {
                b.len() == binding.len()
                    
                    
                    && b.iter().all(|b| binding.iter().any(|binding| b == binding))
            });
            if let Some(index) = index {
                action_bindings.swap_remove(index);
            } else {
                return Err(ActionRemovedError::ActionExistsButBindingDoesnt);
            }
            kill_it = action_bindings.is_empty();
        } else {
            return Err(ActionRemovedError::ActionNotFound);
        }
        if kill_it {
            self.actions.remove(id);
        }
        Ok(())
    }
    
    pub fn action_bindings<A>(&self, id: &A) -> impl Iterator<Item = &[Button]>
    where
        T::Action: Borrow<A>,
        A: Hash + Eq + ?Sized,
    {
        self.actions
            .get(id)
            .map(SmallVec::as_slice)
            .unwrap_or(&[])
            .iter()
            .map(SmallVec::as_slice)
    }
    
    pub fn actions(&self) -> impl Iterator<Item = &T::Action> {
        self.actions.keys()
    }
    
    pub fn check_invariants(&mut self) -> Result<(), BindingError<T>> {
        
        
        let action_bindings = self
            .actions
            .iter()
            .map(|(k, v)| (k.clone(), v.clone()))
            .collect::<Vec<_>>();
        for (k, v) in action_bindings {
            for c in v {
                self.remove_action_binding(&k, &c)
                    .expect("Unreachable: We just cloned the bindings, they can't be incorrect.");
                self.insert_action_binding(k.clone(), c)?;
            }
        }
        let axis_bindings = self
            .axes
            .iter()
            .map(|(k, v)| (k.clone(), v.clone()))
            .collect::<Vec<_>>();
        for (k, a) in axis_bindings {
            self.remove_axis(&k);
            self.insert_axis(k, a)?;
        }
        Ok(())
    }
    fn check_action_invariants(
        &self,
        id: &T::Action,
        bind: &[Button],
    ) -> Result<(), BindingError<T>> {
        
        for i in 0..bind.len() {
            for j in (i + 1)..bind.len() {
                if bind[i] == bind[j] {
                    return Err(BindingError::ComboContainsDuplicates(id.clone()));
                }
            }
        }
        if bind.len() == 1 {
            for (k, a) in self.axes.iter() {
                if let Axis::Emulated { pos, neg } = a {
                    if bind[0] == *pos || bind[0] == *neg {
                        return Err(BindingError::ButtonBoundToAxis(k.clone(), a.clone()));
                    }
                }
            }
        }
        for (k, a) in self.actions.iter() {
            for c in a {
                if c.len() == bind.len() && bind.iter().all(|bind| c.iter().any(|c| c == bind)) {
                    return Err(BindingError::ComboAlreadyBound(k.clone()));
                }
            }
        }
        Ok(())
    }
    fn check_axis_invariants(&self, id: &T::Axis, axis: &Axis) -> Result<(), BindingError<T>> {
        match axis {
            Axis::Emulated {
                pos: ref axis_pos,
                neg: ref axis_neg,
            } => {
                for (k, a) in self.axes.iter().filter(|(k, _a)| *k != id) {
                    if let Axis::Emulated { pos, neg } = a {
                        if axis_pos == pos || axis_pos == neg || axis_neg == pos || axis_neg == neg
                        {
                            return Err(BindingError::AxisButtonAlreadyBoundToAxis(
                                k.clone(),
                                a.clone(),
                            ));
                        }
                    }
                }
                for (k, a) in self.actions.iter() {
                    for c in a {
                        
                        if c.len() == 1 && (c[0] == *axis_pos || c[0] == *axis_neg) {
                            return Err(BindingError::AxisButtonAlreadyBoundToAction(
                                k.clone(),
                                c[0],
                            ));
                        }
                    }
                }
            }
            Axis::Controller {
                controller_id: ref input_controller_id,
                axis: ref input_axis,
                ..
            } => {
                for (k, a) in self.axes.iter().filter(|(k, _a)| *k != id) {
                    if let Axis::Controller {
                        controller_id,
                        axis,
                        ..
                    } = a
                    {
                        if controller_id == input_controller_id && axis == input_axis {
                            return Err(BindingError::ControllerAxisAlreadyBound(k.clone()));
                        }
                    }
                }
            }
            Axis::MouseWheel {
                horizontal: ref input_horizontal,
            } => {
                for (k, a) in self.axes.iter().filter(|(k, _a)| *k != id) {
                    if let Axis::MouseWheel { horizontal } = a {
                        if input_horizontal == horizontal {
                            return Err(BindingError::MouseWheelAxisAlreadyBound(k.clone()));
                        }
                    }
                }
            }
        }
        Ok(())
    }
}
#[cfg(test)]
mod tests {
    use super::*;
    use crate::{button::*, controller::ControllerAxis};
    use winit::{MouseButton, VirtualKeyCode};
    #[test]
    fn add_and_remove_actions() {
        let mut bindings = Bindings::<StringBindings>::new();
        assert_eq!(bindings.actions().next(), None);
        assert_eq!(bindings.action_bindings("test_action").next(), None);
        bindings
            .insert_action_binding(
                String::from("test_action"),
                [Button::Mouse(MouseButton::Left)].iter().cloned(),
            )
            .unwrap();
        assert_eq!(
            bindings.actions().collect::<Vec<_>>(),
            vec![&String::from("test_action")]
        );
        let action_bindings = bindings.action_bindings("test_action").collect::<Vec<_>>();
        assert_eq!(action_bindings, vec![[Button::Mouse(MouseButton::Left)]]);
        bindings
            .remove_action_binding("test_action", &[Button::Mouse(MouseButton::Left)])
            .unwrap();
        assert_eq!(bindings.actions().next(), None);
        assert_eq!(bindings.action_bindings("test_action").next(), None);
        bindings
            .insert_action_binding(
                String::from("test_action"),
                [
                    Button::Mouse(MouseButton::Left),
                    Button::Mouse(MouseButton::Right),
                ]
                .iter()
                .cloned(),
            )
            .unwrap();
        assert_eq!(
            bindings.actions().collect::<Vec<_>>(),
            vec![&String::from("test_action")],
        );
        let action_bindings = bindings.action_bindings("test_action").collect::<Vec<_>>();
        assert_eq!(
            action_bindings,
            vec![[
                Button::Mouse(MouseButton::Left),
                Button::Mouse(MouseButton::Right)
            ]]
        );
        bindings
            .remove_action_binding(
                "test_action",
                &[
                    Button::Mouse(MouseButton::Left),
                    Button::Mouse(MouseButton::Right),
                ],
            )
            .unwrap();
        assert_eq!(bindings.actions().next(), None);
        assert_eq!(bindings.action_bindings("test_action").next(), None);
        bindings
            .insert_action_binding(
                String::from("test_action"),
                [
                    Button::Mouse(MouseButton::Left),
                    Button::Mouse(MouseButton::Right),
                ]
                .iter()
                .cloned(),
            )
            .unwrap();
        assert_eq!(
            bindings
                .remove_action_binding(
                    "test_action",
                    &[
                        Button::Mouse(MouseButton::Right),
                        Button::Mouse(MouseButton::Right),
                    ],
                )
                .unwrap_err(),
            ActionRemovedError::BindingContainsDuplicates
        );
        assert_eq!(
            bindings
                .remove_action_binding("test_action", &[Button::Mouse(MouseButton::Left),],)
                .unwrap_err(),
            ActionRemovedError::ActionExistsButBindingDoesnt
        );
        assert_eq!(
            bindings
                .remove_action_binding("nonsense_action", &[Button::Mouse(MouseButton::Left),],)
                .unwrap_err(),
            ActionRemovedError::ActionNotFound
        );
        let actions = bindings.actions().collect::<Vec<_>>();
        assert_eq!(actions, vec![&String::from("test_action")]);
        let action_bindings = bindings.action_bindings("test_action").collect::<Vec<_>>();
        assert_eq!(
            action_bindings,
            vec![[
                Button::Mouse(MouseButton::Left),
                Button::Mouse(MouseButton::Right)
            ]]
        );
        bindings
            .remove_action_binding(
                "test_action",
                &[
                    Button::Mouse(MouseButton::Right),
                    Button::Mouse(MouseButton::Left),
                ],
            )
            .unwrap();
        assert_eq!(bindings.actions().next(), None);
        assert_eq!(bindings.action_bindings("test_action").next(), None);
    }
    #[test]
    fn insert_errors() {
        let mut bindings = Bindings::<StringBindings>::new();
        assert_eq!(
            bindings
                .insert_action_binding(
                    String::from("test_action"),
                    [
                        Button::Mouse(MouseButton::Left),
                        Button::Mouse(MouseButton::Right),
                        Button::Mouse(MouseButton::Left),
                    ]
                    .iter()
                    .cloned(),
                )
                .unwrap_err(),
            BindingError::ComboContainsDuplicates(String::from("test_action"))
        );
        bindings
            .insert_action_binding(
                String::from("test_action"),
                [Button::Mouse(MouseButton::Left)].iter().cloned(),
            )
            .unwrap();
        assert_eq!(
            bindings
                .insert_action_binding(
                    String::from("test_action"),
                    [Button::Mouse(MouseButton::Left),].iter().cloned(),
                )
                .unwrap_err(),
            BindingError::ComboAlreadyBound(String::from("test_action"))
        );
        assert_eq!(
            bindings
                .insert_action_binding(
                    String::from("test_action_2"),
                    [Button::Mouse(MouseButton::Left),].iter().cloned(),
                )
                .unwrap_err(),
            BindingError::ComboAlreadyBound(String::from("test_action"))
        );
        assert_eq!(
            bindings
                .insert_axis(
                    String::from("test_axis"),
                    Axis::Emulated {
                        pos: Button::Mouse(MouseButton::Left),
                        neg: Button::Mouse(MouseButton::Right),
                    },
                )
                .unwrap_err(),
            BindingError::AxisButtonAlreadyBoundToAction(
                String::from("test_action"),
                Button::Mouse(MouseButton::Left)
            )
        );
        assert_eq!(
            bindings
                .insert_axis(
                    String::from("test_axis"),
                    Axis::Emulated {
                        pos: Button::Key(VirtualKeyCode::Left),
                        neg: Button::Key(VirtualKeyCode::Right),
                    },
                )
                .unwrap(),
            None
        );
        assert_eq!(
            bindings
                .insert_action_binding(
                    String::from("test_action_2"),
                    [Button::Key(VirtualKeyCode::Left),].iter().cloned(),
                )
                .unwrap_err(),
            BindingError::ButtonBoundToAxis(
                String::from("test_axis"),
                Axis::Emulated {
                    pos: Button::Key(VirtualKeyCode::Left),
                    neg: Button::Key(VirtualKeyCode::Right),
                }
            )
        );
        assert_eq!(
            bindings
                .insert_action_binding(
                    String::from("test_axis_2"),
                    [Button::Key(VirtualKeyCode::Left),].iter().cloned(),
                )
                .unwrap_err(),
            BindingError::ButtonBoundToAxis(
                String::from("test_axis"),
                Axis::Emulated {
                    pos: Button::Key(VirtualKeyCode::Left),
                    neg: Button::Key(VirtualKeyCode::Right),
                }
            )
        );
        assert_eq!(
            bindings
                .insert_axis(
                    String::from("test_axis_2"),
                    Axis::Emulated {
                        pos: Button::Key(VirtualKeyCode::Left),
                        neg: Button::Key(VirtualKeyCode::Up),
                    },
                )
                .unwrap_err(),
            BindingError::AxisButtonAlreadyBoundToAxis(
                String::from("test_axis"),
                Axis::Emulated {
                    pos: Button::Key(VirtualKeyCode::Left),
                    neg: Button::Key(VirtualKeyCode::Right),
                }
            )
        );
        assert_eq!(
            bindings
                .insert_axis(
                    String::from("test_controller_axis"),
                    Axis::Controller {
                        controller_id: 0,
                        axis: ControllerAxis::RightX,
                        invert: false,
                        dead_zone: 0.25,
                    },
                )
                .unwrap(),
            None
        );
        assert_eq!(
            bindings
                .insert_axis(
                    String::from("test_controller_axis"),
                    Axis::Controller {
                        controller_id: 0,
                        axis: ControllerAxis::LeftX,
                        invert: false,
                        dead_zone: 0.25,
                    },
                )
                .unwrap(),
            Some(Axis::Controller {
                controller_id: 0,
                axis: ControllerAxis::RightX,
                invert: false,
                dead_zone: 0.25,
            })
        );
        assert_eq!(
            bindings
                .insert_axis(
                    String::from("test_controller_axis_2"),
                    Axis::Controller {
                        controller_id: 0,
                        axis: ControllerAxis::LeftX,
                        invert: true,
                        dead_zone: 0.1,
                    },
                )
                .unwrap_err(),
            BindingError::ControllerAxisAlreadyBound(String::from("test_controller_axis"))
        );
        assert_eq!(
            bindings
                .insert_axis(
                    String::from("test_mouse_wheel_axis"),
                    Axis::MouseWheel { horizontal: true },
                )
                .unwrap(),
            None
        );
        assert_eq!(
            bindings
                .insert_axis(
                    String::from("test_mouse_wheel_axis"),
                    Axis::MouseWheel { horizontal: false },
                )
                .unwrap(),
            Some(Axis::MouseWheel { horizontal: true })
        );
        assert_eq!(
            bindings
                .insert_axis(
                    String::from("test_mouse_wheel_axis_2"),
                    Axis::MouseWheel { horizontal: false },
                )
                .unwrap_err(),
            BindingError::MouseWheelAxisAlreadyBound(String::from("test_mouse_wheel_axis"))
        );
    }
    #[test]
    fn add_and_remove_axes() {
        let mut bindings = Bindings::<StringBindings>::new();
        assert_eq!(
            bindings
                .insert_axis(
                    String::from("test_axis"),
                    Axis::Emulated {
                        pos: Button::Key(VirtualKeyCode::Left),
                        neg: Button::Key(VirtualKeyCode::Right),
                    },
                )
                .unwrap(),
            None
        );
        assert_eq!(
            bindings.remove_axis("test_axis"),
            Some(Axis::Emulated {
                pos: Button::Key(VirtualKeyCode::Left),
                neg: Button::Key(VirtualKeyCode::Right),
            })
        );
        assert_eq!(
            bindings
                .insert_axis(
                    String::from("test_controller_axis"),
                    Axis::Controller {
                        controller_id: 0,
                        axis: ControllerAxis::RightX,
                        invert: false,
                        dead_zone: 0.25,
                    },
                )
                .unwrap(),
            None
        );
        assert_eq!(
            bindings.remove_axis("test_controller_axis"),
            Some(Axis::Controller {
                controller_id: 0,
                axis: ControllerAxis::RightX,
                invert: false,
                dead_zone: 0.25,
            })
        );
        assert_eq!(
            bindings
                .insert_axis(
                    String::from("test_mouse_wheel_axis"),
                    Axis::MouseWheel { horizontal: false },
                )
                .unwrap(),
            None
        );
        assert_eq!(
            bindings.remove_axis("test_mouse_wheel_axis"),
            Some(Axis::MouseWheel { horizontal: false })
        );
    }
}