use std::{
    fmt::Display,
    sync::{Arc, Mutex},
    time::Duration,
};

use crate::types::robot_types::CommandFilter;

type ControlClosure<R, S> = Option<Box<dyn FnMut(&S, Duration) -> R + Send>>;

#[derive(Clone, Default)]
pub struct CommandHandle<R, S>
where
    R: Display + Clone + Send + Sync + 'static,
    S: Display + Clone + Default + Send + Sync + 'static,
{
    target: Arc<Mutex<Option<R>>>,
    closure: Arc<Mutex<ControlClosure<R, S>>>,
}

impl<R, S> CommandHandle<R, S>
where
    R: CommandFilter<S> + Display + Clone + Send + Sync + 'static,
    S: Display + Clone + Default + Send + Sync + 'static,
{
    pub fn new() -> Self {
        CommandHandle {
            target: Arc::new(Mutex::new(None)),
            closure: Arc::new(Mutex::new(None)),
        }
    }

    #[allow(unused)]
    pub fn set_target<T: Into<R>>(&self, target: T) {
        let mut target_lock = self.target.lock().unwrap();
        *target_lock = Some(target.into());
    }

    pub fn get_target(&self) -> Option<R> {
        let target_lock = self.target.lock().unwrap();
        target_lock.clone()
    }

    pub fn set_closure<F: FnMut(&S, Duration) -> R + Send + 'static>(&self, closure: F) {
        let mut closure_lock = self.closure.lock().unwrap();
        *closure_lock = Some(Box::new(closure));
    }

    #[allow(unused)]
    pub fn remove_closure(&self) {
        let mut closure_lock = self.closure.lock().unwrap();
        *closure_lock = None;
    }

    pub fn run_closure(&self, state: &S, duration: Duration) -> Option<R> {
        let mut closure_lock = self.closure.lock().unwrap();
        (*closure_lock)
            .as_mut()
            .map(|closure| closure(state, duration))
    }

    pub fn command(&self, state: &S, duration: Duration) -> Option<R> {
        match (self.run_closure(state, duration), self.get_target()) {
            (Some(res), None) => Some(res),
            (None, Some(target)) => Some(target),
            (Some(_), Some(_)) => {
                println!("There are both closures and target values, ignoring all of them");
                None
            }
            (None, None) => None,
        }
        .map(|res| res.filter(state))
    }
}
