//! Microphone and speaker wrappers.
//!
//! This module exposes M5Unified audio capture, playback, queue state, and
//! device configuration through safe Rust buffer APIs.

use crate::Error;

fn channel_to_raw(channel: Option<u8>) -> i32 {
    channel.map(i32::from).unwrap_or(-1)
}

#[derive(Debug)]
pub struct Mic;

impl Mic {
    pub fn begin(&mut self) -> bool {
        unsafe { m5unified_sys::m5u_mic_begin() }
    }

    pub fn is_running(&self) -> bool {
        unsafe { m5unified_sys::m5u_mic_is_running() }
    }

    pub fn record_i16(&mut self, buffer: &mut [i16]) -> bool {
        unsafe { m5unified_sys::m5u_mic_record_i16(buffer.as_mut_ptr(), buffer.len()) }
    }

    pub fn record_u8(&mut self, buffer: &mut [u8]) -> bool {
        unsafe { m5unified_sys::m5u_mic_record_u8(buffer.as_mut_ptr(), buffer.len()) }
    }

    pub fn rms(&mut self, buffer: &mut [i16]) -> Option<f32> {
        if !self.record_i16(buffer) || buffer.is_empty() {
            return None;
        }
        let sum_sq: f32 = buffer.iter().map(|&s| (s as f32) * (s as f32)).sum();
        Some((sum_sq / buffer.len() as f32).sqrt())
    }

    pub fn is_enabled(&self) -> bool {
        unsafe { m5unified_sys::m5u_mic_is_enabled() }
    }

    pub fn is_recording(&self) -> bool {
        unsafe { m5unified_sys::m5u_mic_is_recording() }
    }

    pub fn recording_state(&self) -> AudioQueueState {
        AudioQueueState::from_raw(unsafe { m5unified_sys::m5u_mic_recording_state() })
    }

    pub fn end(&mut self) {
        unsafe { m5unified_sys::m5u_mic_end() }
    }

    pub fn set_sample_rate(&mut self, sample_rate_hz: u32) {
        unsafe { m5unified_sys::m5u_mic_set_sample_rate(sample_rate_hz) }
    }

    pub fn noise_filter_level(&self) -> i32 {
        unsafe { m5unified_sys::m5u_mic_get_noise_filter_level() as i32 }
    }

    pub fn set_noise_filter_level(&mut self, level: i32) -> bool {
        unsafe { m5unified_sys::m5u_mic_set_noise_filter_level(level) }
    }

    pub fn record_i16_at(&mut self, buffer: &mut [i16], sample_rate_hz: u32) -> bool {
        unsafe {
            m5unified_sys::m5u_mic_record_i16_at(buffer.as_mut_ptr(), buffer.len(), sample_rate_hz)
        }
    }

    pub fn record_u8_at(&mut self, buffer: &mut [u8], sample_rate_hz: u32) -> bool {
        self.record_u8_with_options(
            buffer,
            RecordingOptions {
                sample_rate_hz,
                stereo: false,
            },
        )
    }

    pub fn record_i16_with_options(
        &mut self,
        buffer: &mut [i16],
        options: RecordingOptions,
    ) -> bool {
        unsafe {
            m5unified_sys::m5u_mic_record_i16_ex(
                buffer.as_mut_ptr(),
                buffer.len(),
                options.sample_rate_hz,
                options.stereo,
            )
        }
    }

    pub fn record_u8_with_options(&mut self, buffer: &mut [u8], options: RecordingOptions) -> bool {
        unsafe {
            m5unified_sys::m5u_mic_record_u8_ex(
                buffer.as_mut_ptr(),
                buffer.len(),
                options.sample_rate_hz,
                options.stereo,
            )
        }
    }

    pub fn config(&self) -> MicConfig {
        let mut raw = m5unified_sys::m5u_mic_config_t::default();
        let ok = unsafe { m5unified_sys::m5u_mic_get_config(&mut raw) };
        if ok {
            MicConfig::from_raw(raw)
        } else {
            MicConfig::default()
        }
    }

    pub fn set_config(&mut self, config: MicConfig) -> Result<(), Error> {
        let raw = config.to_raw();
        unsafe { m5unified_sys::m5u_mic_set_config(&raw) }
            .then_some(())
            .ok_or(Error::Unavailable("microphone config"))
    }

    pub fn begin_dma_capture(&mut self, config: AudioCaptureConfig) -> Result<AudioCapture, Error> {
        AudioCapture::begin(config)
    }
}

#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub struct MicConfig {
    pub pin_data_in: i32,
    pub pin_bck: i32,
    pub pin_mck: i32,
    pub pin_ws: i32,
    pub sample_rate: u32,
    pub left_channel: bool,
    pub stereo: bool,
    pub over_sampling: u8,
    pub magnification: u8,
    pub noise_filter_level: u8,
    pub use_adc: bool,
    pub dma_buf_len: usize,
    pub dma_buf_count: usize,
    pub task_priority: u8,
    pub task_pinned_core: u8,
    pub i2s_port: i32,
}

impl MicConfig {
    fn from_raw(raw: m5unified_sys::m5u_mic_config_t) -> Self {
        Self {
            pin_data_in: raw.pin_data_in,
            pin_bck: raw.pin_bck,
            pin_mck: raw.pin_mck,
            pin_ws: raw.pin_ws,
            sample_rate: raw.sample_rate,
            left_channel: raw.left_channel != 0,
            stereo: raw.stereo != 0,
            over_sampling: raw.over_sampling,
            magnification: raw.magnification,
            noise_filter_level: raw.noise_filter_level,
            use_adc: raw.use_adc != 0,
            dma_buf_len: raw.dma_buf_len,
            dma_buf_count: raw.dma_buf_count,
            task_priority: raw.task_priority,
            task_pinned_core: raw.task_pinned_core,
            i2s_port: raw.i2s_port,
        }
    }

    fn to_raw(self) -> m5unified_sys::m5u_mic_config_t {
        m5unified_sys::m5u_mic_config_t {
            pin_data_in: self.pin_data_in,
            pin_bck: self.pin_bck,
            pin_mck: self.pin_mck,
            pin_ws: self.pin_ws,
            sample_rate: self.sample_rate,
            left_channel: u8::from(self.left_channel),
            stereo: u8::from(self.stereo),
            over_sampling: self.over_sampling,
            magnification: self.magnification,
            noise_filter_level: self.noise_filter_level,
            use_adc: u8::from(self.use_adc),
            dma_buf_len: self.dma_buf_len,
            dma_buf_count: self.dma_buf_count,
            task_priority: self.task_priority,
            task_pinned_core: self.task_pinned_core,
            i2s_port: self.i2s_port,
        }
    }
}

impl Default for MicConfig {
    fn default() -> Self {
        Self::from_raw(m5unified_sys::m5u_mic_config_t::default())
    }
}

#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub struct AudioCaptureConfig {
    pub sample_rate_hz: u32,
    pub dma_frame_num: usize,
    pub dma_desc_num: usize,
}

impl Default for AudioCaptureConfig {
    fn default() -> Self {
        Self {
            sample_rate_hz: 16_000,
            dma_frame_num: 240,
            dma_desc_num: 4,
        }
    }
}

#[derive(Debug)]
pub struct AudioCapture {
    channels: u8,
}

impl AudioCapture {
    pub fn begin(config: AudioCaptureConfig) -> Result<Self, Error> {
        let mut channels = 0u8;
        let ok = unsafe {
            m5unified_sys::m5u_audio_capture_begin(
                config.sample_rate_hz,
                config.dma_frame_num,
                config.dma_desc_num,
                &mut channels,
            )
        };
        if ok {
            Ok(Self { channels })
        } else {
            Err(Error::Unavailable("audio DMA capture"))
        }
    }

    pub fn channels(&self) -> u8 {
        self.channels
    }

    pub fn read_i16(&mut self, buffer: &mut [i16], timeout_ms: u32) -> usize {
        unsafe {
            m5unified_sys::m5u_audio_capture_read_i16(buffer.as_mut_ptr(), buffer.len(), timeout_ms)
        }
    }

    pub fn end(self) {
        drop(self);
    }
}

impl Drop for AudioCapture {
    fn drop(&mut self) {
        unsafe { m5unified_sys::m5u_audio_capture_end() }
    }
}

#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub struct RecordingOptions {
    pub sample_rate_hz: u32,
    pub stereo: bool,
}

impl Default for RecordingOptions {
    fn default() -> Self {
        Self {
            sample_rate_hz: 16_000,
            stereo: false,
        }
    }
}

#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum AudioQueueState {
    Idle,
    Active,
    Full,
    Unknown(usize),
}

impl AudioQueueState {
    fn from_raw(raw: usize) -> Self {
        match raw {
            0 => Self::Idle,
            1 => Self::Active,
            2 => Self::Full,
            other => Self::Unknown(other),
        }
    }

    pub fn is_active(self) -> bool {
        !matches!(self, Self::Idle)
    }
}

#[derive(Debug)]
pub struct Speaker;

impl Speaker {
    pub fn begin(&mut self) -> bool {
        unsafe { m5unified_sys::m5u_speaker_begin() }
    }

    pub fn is_running(&self) -> bool {
        unsafe { m5unified_sys::m5u_speaker_is_running() }
    }

    pub fn set_volume(&mut self, volume: u8) {
        unsafe { m5unified_sys::m5u_speaker_set_volume(volume) }
    }

    pub fn tone(&mut self, frequency_hz: u32, duration_ms: u32) -> bool {
        unsafe { m5unified_sys::m5u_speaker_tone(frequency_hz, duration_ms) }
    }

    pub fn play_i16(&mut self, samples: &[i16], sample_rate_hz: u32) -> bool {
        unsafe {
            m5unified_sys::m5u_speaker_play_i16(samples.as_ptr(), samples.len(), sample_rate_hz)
        }
    }

    pub fn is_enabled(&self) -> bool {
        unsafe { m5unified_sys::m5u_speaker_is_enabled() }
    }

    pub fn end(&mut self) {
        unsafe { m5unified_sys::m5u_speaker_end() }
    }

    pub fn volume(&self) -> u8 {
        unsafe { m5unified_sys::m5u_speaker_get_volume() }
    }

    pub fn config(&self) -> SpeakerConfig {
        let mut raw = m5unified_sys::m5u_speaker_config_t::default();
        let ok = unsafe { m5unified_sys::m5u_speaker_get_config(&mut raw) };
        if ok {
            SpeakerConfig::from_raw(raw)
        } else {
            SpeakerConfig::default()
        }
    }

    pub fn set_config(&mut self, config: SpeakerConfig) -> Result<(), Error> {
        let raw = config.to_raw();
        unsafe { m5unified_sys::m5u_speaker_set_config(&raw) }
            .then_some(())
            .ok_or(Error::Unavailable("speaker config"))
    }

    pub fn tone_ex(&mut self, frequency_hz: f32, duration_ms: u32, channel: Option<u8>) -> bool {
        unsafe {
            m5unified_sys::m5u_speaker_tone_ex(frequency_hz, duration_ms, channel_to_raw(channel))
        }
    }

    pub fn tone_with_options(&mut self, frequency_hz: f32, options: ToneOptions) -> bool {
        unsafe {
            m5unified_sys::m5u_speaker_tone_options(
                frequency_hz,
                options.duration_ms,
                channel_to_raw(options.channel),
                options.stop_current_sound,
            )
        }
    }

    pub fn tone_with_raw(
        &mut self,
        frequency_hz: f32,
        data: &[u8],
        options: ToneOptions,
        stereo: bool,
    ) -> bool {
        unsafe {
            m5unified_sys::m5u_speaker_tone_full(
                frequency_hz,
                options.duration_ms,
                channel_to_raw(options.channel),
                options.stop_current_sound,
                data.as_ptr(),
                data.len(),
                stereo,
            )
        }
    }

    pub fn play_u8(&mut self, samples: &[u8], sample_rate_hz: u32) -> bool {
        unsafe {
            m5unified_sys::m5u_speaker_play_u8(samples.as_ptr(), samples.len(), sample_rate_hz)
        }
    }

    pub fn play_u8_with_options(&mut self, samples: &[u8], options: RawPlaybackOptions) -> bool {
        unsafe {
            m5unified_sys::m5u_speaker_play_u8_ex(
                samples.as_ptr(),
                samples.len(),
                options.sample_rate_hz,
                options.stereo,
                options.repeat,
                channel_to_raw(options.channel),
                options.stop_current_sound,
            )
        }
    }

    pub fn play_i8_with_options(&mut self, samples: &[i8], options: RawPlaybackOptions) -> bool {
        unsafe {
            m5unified_sys::m5u_speaker_play_i8_ex(
                samples.as_ptr(),
                samples.len(),
                options.sample_rate_hz,
                options.stereo,
                options.repeat,
                channel_to_raw(options.channel),
                options.stop_current_sound,
            )
        }
    }

    pub fn play_i16_with_options(&mut self, samples: &[i16], options: RawPlaybackOptions) -> bool {
        unsafe {
            m5unified_sys::m5u_speaker_play_i16_ex(
                samples.as_ptr(),
                samples.len(),
                options.sample_rate_hz,
                options.stereo,
                options.repeat,
                channel_to_raw(options.channel),
                options.stop_current_sound,
            )
        }
    }

    pub fn play_wav(&mut self, data: &[u8]) -> bool {
        unsafe { m5unified_sys::m5u_speaker_play_wav(data.as_ptr(), data.len()) }
    }

    pub fn play_wav_with_options(&mut self, data: &[u8], options: WavPlaybackOptions) -> bool {
        unsafe {
            m5unified_sys::m5u_speaker_play_wav_ex(
                data.as_ptr(),
                data.len(),
                options.repeat,
                channel_to_raw(options.channel),
                options.stop_current_sound,
            )
        }
    }

    pub fn is_playing(&self, channel: Option<u8>) -> bool {
        unsafe { m5unified_sys::m5u_speaker_is_playing(channel_to_raw(channel)) }
    }

    pub fn playing_channels(&self) -> usize {
        unsafe { m5unified_sys::m5u_speaker_playing_channels() }
    }

    pub fn channel_playing_state(&self, channel: u8) -> AudioQueueState {
        AudioQueueState::from_raw(unsafe {
            m5unified_sys::m5u_speaker_channel_playing_state(i32::from(channel))
        })
    }

    pub fn stop(&mut self, channel: Option<u8>) {
        unsafe { m5unified_sys::m5u_speaker_stop(channel_to_raw(channel)) }
    }

    pub fn channel_volume(&self, channel: u8) -> u8 {
        unsafe { m5unified_sys::m5u_speaker_get_channel_volume(i32::from(channel)) }
    }

    pub fn set_channel_volume(&mut self, channel: u8, volume: u8) {
        unsafe { m5unified_sys::m5u_speaker_set_channel_volume(i32::from(channel), volume) }
    }

    pub fn set_all_channel_volume(&mut self, volume: u8) {
        unsafe { m5unified_sys::m5u_speaker_set_all_channel_volume(volume) }
    }
}

#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub struct ToneOptions {
    pub duration_ms: u32,
    pub channel: Option<u8>,
    pub stop_current_sound: bool,
}

impl Default for ToneOptions {
    fn default() -> Self {
        Self {
            duration_ms: u32::MAX,
            channel: None,
            stop_current_sound: true,
        }
    }
}

#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub struct RawPlaybackOptions {
    pub sample_rate_hz: u32,
    pub stereo: bool,
    pub repeat: u32,
    pub channel: Option<u8>,
    pub stop_current_sound: bool,
}

impl Default for RawPlaybackOptions {
    fn default() -> Self {
        Self {
            sample_rate_hz: 44_100,
            stereo: false,
            repeat: 1,
            channel: None,
            stop_current_sound: false,
        }
    }
}

#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub struct WavPlaybackOptions {
    pub repeat: u32,
    pub channel: Option<u8>,
    pub stop_current_sound: bool,
}

impl Default for WavPlaybackOptions {
    fn default() -> Self {
        Self {
            repeat: 1,
            channel: None,
            stop_current_sound: false,
        }
    }
}

#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub struct SpeakerConfig {
    pub pin_data_out: i32,
    pub pin_bck: i32,
    pub pin_mck: i32,
    pub pin_ws: i32,
    pub sample_rate: u32,
    pub stereo: bool,
    pub buzzer: bool,
    pub use_dac: bool,
    pub dac_zero_level: u8,
    pub magnification: u8,
    pub dma_buf_len: usize,
    pub dma_buf_count: usize,
    pub task_priority: u8,
    pub task_pinned_core: u8,
    pub i2s_port: i32,
}

impl SpeakerConfig {
    fn from_raw(raw: m5unified_sys::m5u_speaker_config_t) -> Self {
        Self {
            pin_data_out: raw.pin_data_out,
            pin_bck: raw.pin_bck,
            pin_mck: raw.pin_mck,
            pin_ws: raw.pin_ws,
            sample_rate: raw.sample_rate,
            stereo: raw.stereo != 0,
            buzzer: raw.buzzer != 0,
            use_dac: raw.use_dac != 0,
            dac_zero_level: raw.dac_zero_level,
            magnification: raw.magnification,
            dma_buf_len: raw.dma_buf_len,
            dma_buf_count: raw.dma_buf_count,
            task_priority: raw.task_priority,
            task_pinned_core: raw.task_pinned_core,
            i2s_port: raw.i2s_port,
        }
    }

    fn to_raw(self) -> m5unified_sys::m5u_speaker_config_t {
        m5unified_sys::m5u_speaker_config_t {
            pin_data_out: self.pin_data_out,
            pin_bck: self.pin_bck,
            pin_mck: self.pin_mck,
            pin_ws: self.pin_ws,
            sample_rate: self.sample_rate,
            stereo: u8::from(self.stereo),
            buzzer: u8::from(self.buzzer),
            use_dac: u8::from(self.use_dac),
            dac_zero_level: self.dac_zero_level,
            magnification: self.magnification,
            dma_buf_len: self.dma_buf_len,
            dma_buf_count: self.dma_buf_count,
            task_priority: self.task_priority,
            task_pinned_core: self.task_pinned_core,
            i2s_port: self.i2s_port,
        }
    }
}

impl Default for SpeakerConfig {
    fn default() -> Self {
        Self::from_raw(m5unified_sys::m5u_speaker_config_t::default())
    }
}
