//! Interface for reading from a data source
use std::marker::PhantomData;

use maudio_sys::ffi as sys;

use crate::{
    audio::{channels::Channel, formats::Format},
    Binding,
};

pub mod sources;

/// Describes an audio stream’s PCM format and layout.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct DataFormat {
    /// Sample format (e.g. `S16`, `F32`).
    pub format: Format,
    /// Number of interleaved channels.
    pub channels: u32,
    /// Sample rate in Hz.
    pub sample_rate: u32,
    /// Channel order/map for each channel, length == channels (when available).
    pub channel_map: Option<Vec<Channel>>,
}

#[derive(PartialEq)]
pub struct DataSource {
    inner: *mut sys::ma_data_source,
}

pub type GetNextCallback = sys::ma_data_source_get_next_proc;

impl Binding for DataSource {
    type Raw = *mut sys::ma_data_source;

    fn from_ptr(raw: Self::Raw) -> Self {
        Self { inner: raw }
    }

    fn to_raw(&self) -> Self::Raw {
        self.inner
    }
}

// Keeps the methods on AsSourcePtr private
pub(crate) mod private_data_source {
    use crate::{
        data_source::sources::{
            buffer::{AudioBuffer, AudioBufferOps, AudioBufferRef},
            decoder::{Decoder, DecoderOps},
            pulsewave::{PulseWave, PulseWaveOps},
            waveform::{WaveForm, WaveFormOps},
        },
        engine::node_graph::nodes::source::source_node::AttachedSourceNode,
        pcm_frames::PcmFormat,
    };

    use super::*;
    use maudio_sys::ffi as sys;

    pub trait DataSourcePtrProvider<T: ?Sized> {
        fn as_source_ptr(t: &T) -> *mut sys::ma_data_source;
    }

    pub struct DataSourceProvider;
    pub struct DataSourceRefProvider;
    pub struct AudioBufferProvider;
    pub struct AudioBufferRefProvider;
    pub struct DecoderProvider;
    pub struct DecoderRefProvider;
    pub struct PulseWaveProvider;
    pub struct WaveFormProvider;
    pub struct WaveFormF32Provider;
    pub struct AttachedSourceNodeProvider;

    impl DataSourcePtrProvider<DataSource> for DataSourceProvider {
        #[inline]
        fn as_source_ptr(t: &DataSource) -> *mut sys::ma_data_source {
            t.to_raw()
        }
    }

    impl DataSourcePtrProvider<DataSourceRef<'_>> for DataSourceRefProvider {
        #[inline]
        fn as_source_ptr(t: &DataSourceRef) -> *mut sys::ma_data_source {
            t.to_raw()
        }
    }

    impl<F: PcmFormat> DataSourcePtrProvider<AudioBuffer<'_, F>> for AudioBufferProvider {
        #[inline]
        fn as_source_ptr(t: &AudioBuffer<F>) -> *mut sys::ma_data_source {
            t.as_source().to_raw()
        }
    }

    impl<F: PcmFormat> DataSourcePtrProvider<AudioBufferRef<'_, F>> for AudioBufferRefProvider {
        #[inline]
        fn as_source_ptr(t: &AudioBufferRef<F>) -> *mut sys::ma_data_source {
            t.as_source().to_raw()
        }
    }

    impl<F: PcmFormat, S> DataSourcePtrProvider<Decoder<F, S>> for DecoderProvider {
        #[inline]
        fn as_source_ptr(t: &Decoder<F, S>) -> *mut sys::ma_data_source {
            t.as_source().to_raw()
        }
    }

    impl<F: PcmFormat> DataSourcePtrProvider<PulseWave<F>> for PulseWaveProvider {
        #[inline]
        fn as_source_ptr(t: &PulseWave<F>) -> *mut sys::ma_data_source {
            t.as_source().to_raw()
        }
    }

    impl<F: PcmFormat> DataSourcePtrProvider<WaveForm<F>> for WaveFormProvider {
        #[inline]
        fn as_source_ptr(t: &WaveForm<F>) -> *mut sys::ma_data_source {
            t.as_source().to_raw()
        }
    }

    impl<S: AsSourcePtr> DataSourcePtrProvider<AttachedSourceNode<'_, S>>
        for AttachedSourceNodeProvider
    {
        #[inline]
        fn as_source_ptr(t: &AttachedSourceNode<'_, S>) -> *mut sys::ma_data_source {
            t.as_source().to_raw()
        }
    }

    pub fn source_ptr<T: AsSourcePtr + ?Sized>(t: &T) -> *mut sys::ma_data_source {
        <T as AsSourcePtr>::__PtrProvider::as_source_ptr(t)
    }
}

#[doc(hidden)]
pub trait AsSourcePtr {
    type __PtrProvider: private_data_source::DataSourcePtrProvider<Self>;
}

#[doc(hidden)]
impl AsSourcePtr for DataSource {
    type __PtrProvider = private_data_source::DataSourceProvider;
}

#[doc(hidden)]
impl<'a> AsSourcePtr for DataSourceRef<'a> {
    type __PtrProvider = private_data_source::DataSourceRefProvider;
}

pub(crate) mod data_source_ffi {
    use core::f32;
    use std::marker::PhantomData;

    use maudio_sys::ffi as sys;

    use crate::{
        audio::channels::Channel,
        data_source::{
            private_data_source, AsSourcePtr, DataFormat, DataSource, DataSourceRef,
            GetNextCallback,
        },
        Binding, MaResult, MaudioError,
    };

    #[inline]
    pub fn ma_data_source_init(
        config: *const sys::ma_data_source_config,
        source: *mut sys::ma_data_source,
    ) -> MaResult<()> {
        let res = unsafe { sys::ma_data_source_init(config, source) };
        MaudioError::check(res)
    }

    #[inline]
    pub fn ma_data_source_uninit(source: &mut DataSource) {
        unsafe {
            sys::ma_data_source_uninit(source.to_raw());
        }
    }

    #[inline]
    pub fn ma_data_source_read_pcm_frames<S: AsSourcePtr + ?Sized>(
        source: &mut S,
        frame_count: u64,
        channels: u32,
    ) -> MaResult<(Vec<f32>, u64)> {
        let mut buffer = vec![0.0f32; (frame_count * channels as u64) as usize];
        let mut frames_read = 0;
        let res = unsafe {
            sys::ma_data_source_read_pcm_frames(
                private_data_source::source_ptr(source),
                buffer.as_mut_ptr() as *mut std::ffi::c_void,
                frame_count,
                &mut frames_read,
            )
        };
        MaudioError::check(res)?;
        Ok((buffer, frames_read))
    }

    #[inline]
    pub fn ma_data_source_seek_pcm_frames<S: AsSourcePtr + ?Sized>(
        source: &mut S,
        frame_count: u64,
    ) -> MaResult<u64> {
        let mut frames_seeked = 0;
        let res = unsafe {
            sys::ma_data_source_seek_pcm_frames(
                private_data_source::source_ptr(source),
                frame_count,
                &mut frames_seeked,
            )
        };
        MaudioError::check(res)?;
        Ok(frames_seeked)
    }

    #[inline]
    pub fn ma_data_source_seek_to_pcm_frame<S: AsSourcePtr + ?Sized>(
        source: &mut S,
        frame_index: u64,
    ) -> MaResult<()> {
        let res = unsafe {
            sys::ma_data_source_seek_to_pcm_frame(
                private_data_source::source_ptr(source),
                frame_index,
            )
        };
        MaudioError::check(res)
    }

    #[inline]
    pub fn ma_data_source_seek_seconds<S: AsSourcePtr + ?Sized>(
        source: &mut S,
        seconds: f32,
    ) -> MaResult<f32> {
        let mut seconds_seeked = 0.0f32;
        let res = unsafe {
            sys::ma_data_source_seek_seconds(
                private_data_source::source_ptr(source),
                seconds,
                &mut seconds_seeked,
            )
        };
        MaudioError::check(res)?;
        Ok(seconds_seeked)
    }

    #[inline]
    pub fn ma_data_source_seek_to_second<S: AsSourcePtr + ?Sized>(
        source: &mut S,
        seek_point: f32,
    ) -> MaResult<()> {
        let res = unsafe {
            sys::ma_data_source_seek_to_second(private_data_source::source_ptr(source), seek_point)
        };
        MaudioError::check(res)
    }

    pub fn ma_data_source_get_data_format<S: AsSourcePtr + ?Sized>(
        source: &S,
    ) -> MaResult<DataFormat> {
        let mut format_raw: sys::ma_format = sys::ma_format_ma_format_unknown;
        let mut channels: sys::ma_uint32 = 0;
        let mut sample_rate: sys::ma_uint32 = 0;
        let mut channel_map_raw = vec![0 as sys::ma_channel; sys::MA_MAX_CHANNELS as usize];
        let res = unsafe {
            sys::ma_data_source_get_data_format(
                private_data_source::source_ptr(source),
                &mut format_raw,
                &mut channels,
                &mut sample_rate,
                channel_map_raw.as_mut_ptr(),
                channel_map_raw.len(),
            )
        };
        MaudioError::check(res)?;
        // Could cast when passing the ptr to miniaudio, but copying should be fine here
        let mut channel_map: Vec<Channel> =
            channel_map_raw.into_iter().map(Channel::from_raw).collect();
        channel_map.truncate(channels as usize);

        Ok(DataFormat {
            format: format_raw.try_into()?,
            channels: channels as u32,
            sample_rate: sample_rate as u32,
            channel_map: Some(channel_map),
        })
    }

    #[inline]
    pub fn ma_data_source_get_cursor_in_pcm_frames<S: AsSourcePtr + ?Sized>(
        source: &mut S,
    ) -> MaResult<u64> {
        let mut cursor = 0;
        let res = unsafe {
            sys::ma_data_source_get_cursor_in_pcm_frames(
                private_data_source::source_ptr(source),
                &mut cursor,
            )
        };
        MaudioError::check(res)?;
        Ok(cursor)
    }

    #[inline]
    pub fn ma_data_source_get_length_in_pcm_frames<S: AsSourcePtr + ?Sized>(
        source: &mut S,
    ) -> MaResult<u64> {
        let mut length = 0;
        let res = unsafe {
            sys::ma_data_source_get_length_in_pcm_frames(
                private_data_source::source_ptr(source),
                &mut length,
            )
        };
        MaudioError::check(res)?;
        Ok(length)
    }

    #[inline]
    pub fn ma_data_source_get_cursor_in_seconds<S: AsSourcePtr + ?Sized>(
        source: &mut S,
    ) -> MaResult<f32> {
        let mut cursor = 0.0f32;
        let res = unsafe {
            sys::ma_data_source_get_cursor_in_seconds(
                private_data_source::source_ptr(source),
                &mut cursor,
            )
        };
        MaudioError::check(res)?;
        Ok(cursor)
    }

    #[inline]
    pub fn ma_data_source_get_length_in_seconds<S: AsSourcePtr + ?Sized>(
        source: &mut S,
    ) -> MaResult<f32> {
        let mut length = 0.0f32;
        let res = unsafe {
            sys::ma_data_source_get_length_in_seconds(
                private_data_source::source_ptr(source),
                &mut length,
            )
        };
        MaudioError::check(res)?;
        Ok(length)
    }

    #[inline]
    pub fn ma_data_source_set_looping<S: AsSourcePtr + ?Sized>(
        source: &mut S,
        is_looping: bool,
    ) -> MaResult<()> {
        let is_looping = is_looping as u32;
        let res = unsafe {
            sys::ma_data_source_set_looping(private_data_source::source_ptr(source), is_looping)
        };
        MaudioError::check(res)
    }

    #[inline]
    pub fn ma_data_source_is_looping<S: AsSourcePtr + ?Sized>(source: &S) -> bool {
        let res = unsafe {
            sys::ma_data_source_is_looping(private_data_source::source_ptr(source) as *const _)
        };
        res == 1
    }

    #[inline]
    pub fn ma_data_source_set_range_in_pcm_frames<S: AsSourcePtr + ?Sized>(
        source: &S,
        range: core::ops::Range<u64>,
    ) -> MaResult<()> {
        let res = unsafe {
            sys::ma_data_source_set_range_in_pcm_frames(
                private_data_source::source_ptr(source),
                range.start,
                range.end,
            )
        };
        MaudioError::check(res)
    }

    #[inline]
    pub fn ma_data_source_get_range_in_pcm_frames<S: AsSourcePtr + ?Sized>(
        source: &S,
    ) -> core::ops::Range<u64> {
        let mut begin = 0;
        let mut end = 0;
        unsafe {
            sys::ma_data_source_get_range_in_pcm_frames(
                private_data_source::source_ptr(source) as *const _,
                &mut begin,
                &mut end,
            );
        }
        begin..end
    }

    #[inline]
    pub fn ma_data_source_set_loop_point_in_pcm_frames<S: AsSourcePtr + ?Sized>(
        source: &mut S,
        begin: u64,
        end: u64,
    ) -> MaResult<()> {
        let res = unsafe {
            sys::ma_data_source_set_loop_point_in_pcm_frames(
                private_data_source::source_ptr(source),
                begin,
                end,
            )
        };
        MaudioError::check(res)
    }

    #[inline]
    pub fn ma_data_source_get_loop_point_in_pcm_frames<S: AsSourcePtr + ?Sized>(
        source: &S,
    ) -> core::ops::Range<u64> {
        let mut begin = 0;
        let mut end: u64 = 0;
        unsafe {
            sys::ma_data_source_get_loop_point_in_pcm_frames(
                private_data_source::source_ptr(source) as *const _,
                &mut begin,
                &mut end,
            );
        };
        begin..end
    }

    #[inline]
    pub fn ma_data_source_set_current<S: AsSourcePtr + ?Sized, C: AsSourcePtr + ?Sized>(
        source: &mut S,
        current: &mut C,
    ) -> MaResult<()> {
        let res = unsafe {
            sys::ma_data_source_set_current(
                private_data_source::source_ptr(source),
                private_data_source::source_ptr(current),
            )
        };
        MaudioError::check(res)
    }

    #[inline]
    pub fn ma_data_source_get_current<'a, S: AsSourcePtr + ?Sized>(
        source: &'a S,
    ) -> Option<DataSourceRef<'a>> {
        let ptr = unsafe {
            sys::ma_data_source_get_current(private_data_source::source_ptr(source) as *const _)
        };
        if ptr.is_null() {
            None
        } else {
            Some(DataSourceRef {
                inner: ptr,
                _marker: PhantomData,
            })
        }
    }

    #[inline]
    pub fn ma_data_source_set_next<S: AsSourcePtr + ?Sized, N: AsSourcePtr + ?Sized>(
        source: &mut S,
        next: &mut N,
    ) -> MaResult<()> {
        let res = unsafe {
            sys::ma_data_source_set_next(
                private_data_source::source_ptr(source),
                private_data_source::source_ptr(next),
            )
        };
        MaudioError::check(res)
    }

    #[inline]
    pub fn ma_data_source_get_next<'a, S: AsSourcePtr + ?Sized>(
        source: &'a S,
    ) -> Option<DataSourceRef<'a>> {
        let ptr = unsafe {
            sys::ma_data_source_get_next(private_data_source::source_ptr(source) as *const _)
        };
        if ptr.is_null() {
            None
        } else {
            Some(DataSourceRef {
                inner: ptr,
                _marker: PhantomData,
            })
        }
    }

    // TODO
    #[inline]
    pub fn ma_data_source_set_next_callback<S: AsSourcePtr + ?Sized>(
        source: &mut S,
        get_next_cb: GetNextCallback,
    ) -> MaResult<()> {
        let res = unsafe {
            sys::ma_data_source_set_next_callback(
                private_data_source::source_ptr(source),
                get_next_cb,
            )
        };
        MaudioError::check(res)
    }

    // TODO
    #[inline]
    pub fn ma_data_source_get_next_callback<S: AsSourcePtr + ?Sized>(
        source: &S,
    ) -> GetNextCallback {
        unsafe {
            sys::ma_data_source_get_next_callback(
                private_data_source::source_ptr(source) as *const _
            )
        }
    }
}

#[derive(PartialEq, Copy, Clone)]
pub struct DataSourceRef<'a> {
    inner: *mut sys::ma_data_source,
    _marker: PhantomData<&'a ()>,
}

impl<'a> Binding for DataSourceRef<'a> {
    type Raw = *mut sys::ma_data_source;

    fn from_ptr(raw: Self::Raw) -> Self {
        Self {
            inner: raw,
            _marker: PhantomData,
        }
    }

    fn to_raw(&self) -> Self::Raw {
        self.inner
    }
}
