//! Types used to mock a component's inputs.

use std::sync::{Arc, Mutex};

use crate::input::{ComponentInputs, TextInputId};

/// Used during tests to mock the component's inputs.
#[derive(Clone)]
pub struct MockComponentInputs {
    inputs: Arc<Mutex<MockComponentInputsInner>>,
}
struct MockComponentInputsInner {
    text: Box<dyn FnMut(TextInputId) -> String>,
}

impl MockComponentInputs {
    /// Create a new `MockComponentInputs`.
    pub fn new() -> Self {
        Self {
            inputs: Arc::new(Mutex::new(MockComponentInputsInner {
                text: Box::new(|_| panic!("Use `MockComponentInputs::set_text_input`")),
            })),
        }
    }

    /// Set the function that return a text input.
    pub fn set_text_input(&self, text_input: impl FnMut(TextInputId) -> String + 'static) {
        self.inputs.lock().unwrap().text = Box::new(text_input);
    }

    /// Create a [`ComponentInputs`] that uses this `MockComponentInputs`.
    pub fn component_inputs(&self) -> ComponentInputs {
        let inputs = self.clone();
        let inputs = Box::into_raw(Box::new(inputs));

        ComponentInputs {
            ctx: inputs.cast(),
            clone_ctx: |inputs| {
                let inputs: *mut MockComponentInputs = inputs.cast();
                let inputs = unsafe { &*inputs };

                let inputs_clone = inputs.clone();
                let inputs_clone = Box::into_raw(Box::new(inputs_clone));
                inputs_clone.cast()
            },
            free_ctx: |inputs| {
                let inputs: *mut MockComponentInputs = inputs.cast();
                let inputs: Box<MockComponentInputs> = unsafe { Box::from_raw(inputs) };
                drop(inputs)
            },

            text: |inputs, input_id| {
                let inputs: *mut MockComponentInputs = inputs.cast();
                let inputs = unsafe { &*inputs };

                let mut inputs = inputs.inputs.lock().unwrap();

                (inputs.text)(input_id)
            },
        }
    }
}
