use pyo3::prelude::*;
use pyxel::{
    Effect, Note, SharedSound as PyxelSharedSound, Sound as PyxelSound, Speed, Tone, Volume,
};

macro_rules! define_private_methods_for_list {
    ($type: ty, $elems: ident) => {
        fn new(pyxel_sound: PyxelSharedSound) -> Self {
            Self { pyxel_sound }
        }

        fn list(&self) -> &[$type] {
            unsafe { &*(&self.pyxel_sound.lock().$elems as *const Vec<$type>) }
        }

        fn list_mut(&mut self) -> &mut Vec<$type> {
            unsafe { &mut *(&mut self.pyxel_sound.lock().$elems as *mut Vec<$type>) }
        }
    };
}

#[pyclass]
#[derive(Clone)]
pub struct Notes {
    pyxel_sound: PyxelSharedSound,
}

impl Notes {
    define_private_methods_for_list!(Note, notes);
}

#[pymethods]
impl Notes {
    fn __len__(&self) -> PyResult<usize> {
        impl_len_method_for_list!(self)
    }

    fn __getitem__(&self, index: isize) -> PyResult<Note> {
        impl_getitem_method_for_list!(self, index)
    }

    fn __setitem__(&mut self, index: isize, value: Note) -> PyResult<()> {
        impl_setitem_method_for_list!(self, index, value)
    }

    pub fn from_list(&mut self, lst: Vec<Note>) -> PyResult<()> {
        impl_from_list_method_for_list!(self, lst)
    }

    pub fn to_list(&self) -> PyResult<Vec<Note>> {
        impl_to_list_method_for_list!(self)
    }
}

#[pyclass]
#[derive(Clone)]
pub struct Tones {
    pyxel_sound: PyxelSharedSound,
}

impl Tones {
    define_private_methods_for_list!(Tone, tones);
}

#[pymethods]
impl Tones {
    fn __len__(&self) -> PyResult<usize> {
        impl_len_method_for_list!(self)
    }

    fn __getitem__(&self, index: isize) -> PyResult<Tone> {
        impl_getitem_method_for_list!(self, index)
    }

    fn __setitem__(&mut self, index: isize, value: Tone) -> PyResult<()> {
        impl_setitem_method_for_list!(self, index, value)
    }

    pub fn from_list(&mut self, lst: Vec<Tone>) -> PyResult<()> {
        impl_from_list_method_for_list!(self, lst)
    }

    pub fn to_list(&self) -> PyResult<Vec<Tone>> {
        impl_to_list_method_for_list!(self)
    }
}

#[pyclass]
#[derive(Clone)]
pub struct Volumes {
    pyxel_sound: PyxelSharedSound,
}

impl Volumes {
    define_private_methods_for_list!(Volume, volumes);
}

#[pymethods]
impl Volumes {
    fn __len__(&self) -> PyResult<usize> {
        impl_len_method_for_list!(self)
    }

    fn __getitem__(&self, index: isize) -> PyResult<Volume> {
        impl_getitem_method_for_list!(self, index)
    }

    fn __setitem__(&mut self, index: isize, value: Volume) -> PyResult<()> {
        impl_setitem_method_for_list!(self, index, value)
    }

    pub fn from_list(&mut self, lst: Vec<Volume>) -> PyResult<()> {
        impl_from_list_method_for_list!(self, lst)
    }

    pub fn to_list(&self) -> PyResult<Vec<Volume>> {
        impl_to_list_method_for_list!(self)
    }
}

#[pyclass]
#[derive(Clone)]
pub struct Effects {
    pyxel_sound: PyxelSharedSound,
}

impl Effects {
    define_private_methods_for_list!(Effect, effects);
}

#[pymethods]
impl Effects {
    fn __len__(&self) -> PyResult<usize> {
        impl_len_method_for_list!(self)
    }

    fn __getitem__(&self, index: isize) -> PyResult<Effect> {
        impl_getitem_method_for_list!(self, index)
    }

    fn __setitem__(&mut self, index: isize, value: Effect) -> PyResult<()> {
        impl_setitem_method_for_list!(self, index, value)
    }

    pub fn from_list(&mut self, lst: Vec<Effect>) -> PyResult<()> {
        impl_from_list_method_for_list!(self, lst)
    }

    pub fn to_list(&self) -> PyResult<Vec<Effect>> {
        impl_to_list_method_for_list!(self)
    }
}

#[pyclass]
#[derive(Clone)]
pub struct Sound {
    pub pyxel_sound: PyxelSharedSound,
}

pub const fn wrap_pyxel_sound(pyxel_sound: PyxelSharedSound) -> Sound {
    Sound { pyxel_sound }
}

#[pymethods]
impl Sound {
    #[new]
    pub fn new() -> Self {
        wrap_pyxel_sound(PyxelSound::new())
    }

    #[getter]
    pub fn notes(&self) -> Notes {
        Notes::new(self.pyxel_sound.clone())
    }

    #[getter]
    pub fn tones(&self) -> Tones {
        Tones::new(self.pyxel_sound.clone())
    }

    #[getter]
    pub fn volumes(&self) -> Volumes {
        Volumes::new(self.pyxel_sound.clone())
    }

    #[getter]
    pub fn effects(&self) -> Effects {
        Effects::new(self.pyxel_sound.clone())
    }

    #[getter]
    pub fn get_speed(&self) -> Speed {
        self.pyxel_sound.lock().speed
    }

    #[setter]
    pub fn set_speed(&self, speed: Speed) {
        self.pyxel_sound.lock().speed = speed;
    }

    pub fn set(&self, notes: &str, tones: &str, volumes: &str, effects: &str, speed: Speed) {
        self.pyxel_sound
            .lock()
            .set(notes, tones, volumes, effects, speed);
    }

    pub fn set_notes(&self, notes: &str) {
        self.pyxel_sound.lock().set_notes(notes);
    }

    pub fn set_tones(&self, tones: &str) {
        self.pyxel_sound.lock().set_tones(tones);
    }

    pub fn set_volumes(&self, volumes: &str) {
        self.pyxel_sound.lock().set_volumes(volumes);
    }

    pub fn set_effects(&self, effects: &str) {
        self.pyxel_sound.lock().set_effects(effects);
    }
}

pub fn add_sound_class(m: &PyModule) -> PyResult<()> {
    m.add_class::<Notes>()?;
    m.add_class::<Tones>()?;
    m.add_class::<Volumes>()?;
    m.add_class::<Effects>()?;
    m.add_class::<Sound>()?;
    Ok(())
}
