//! Custom host backend.
//!
//! Allows user-defined host implementations with the `custom` feature.
//! See `examples/custom.rs` for usage.

use core::time::Duration;
use std::fmt;

use crate::{
    traits::{DeviceTrait, HostTrait, StreamTrait},
    Data, DeviceDescription, DeviceId, Error, ErrorKind, FrameCount, InputCallbackInfo,
    OutputCallbackInfo, SampleFormat, StreamConfig, StreamInstant, SupportedStreamConfig,
    SupportedStreamConfigRange,
};

/// A host that can be used to write custom [`HostTrait`] implementations.
///
/// # Usage
///
/// A [`CustomHost`](Host) can be used on its own, but most crates that depend on `cpal` use a [`cpal::Host`](crate::Host) instead.
/// You can turn a `CustomHost` into a `Host` fairly easily:
///
/// ```ignore
/// let custom = cpal::platform::CustomHost::from_host(/* ... */);
/// let host = cpal::Host::from(custom);
/// ```
///
/// Custom hosts are marked as unavailable and will not appear in [`cpal::available_hosts`](crate::available_hosts).
pub struct Host(Box<dyn HostErased>);

impl Host {
    // this only exists for impl_platform_host, which requires it
    pub(crate) fn new() -> Result<Self, Error> {
        Err(Error::new(ErrorKind::HostUnavailable))
    }

    /// Construct a custom host from an arbitrary [`HostTrait`] implementation.
    pub fn from_host<T>(host: T) -> Self
    where
        T: HostTrait + Send + Sync + 'static,
        T::Device: Send + Sync + Clone,
        <T::Device as DeviceTrait>::SupportedInputConfigs: Clone,
        <T::Device as DeviceTrait>::SupportedOutputConfigs: Clone,
        <T::Device as DeviceTrait>::Stream: Send + Sync,
    {
        Self(Box::new(host))
    }
}

/// A device that can be used to write custom [`DeviceTrait`] implementations.
///
/// # Usage
///
/// A [`CustomDevice`](Device) can be used on its own, but most crates that depend on `cpal` use a [`cpal::Device`](crate::Device) instead.
/// You can turn a `Device` into a `Device` fairly easily:
///
/// ```ignore
/// let custom = cpal::platform::CustomDevice::from_device(/* ... */);
/// let device = cpal::Device::from(custom);
/// ```
///
/// `rodio`, for example, lets you build an `OutputStream` with a [`cpal::Device`](crate::Device):
/// ```ignore
/// let custom = cpal::platform::CustomDevice::from_device(/* ... */);
/// let device = cpal::Device::from(custom);
///
/// let stream_builder = rodio::OutputStreamBuilder::from_device(device).expect("failed to build stream");
/// ```
pub struct Device(Box<dyn DeviceErased>);

impl Device {
    /// Construct a custom device from an arbitrary [`DeviceTrait`] implementation.
    pub fn from_device<T>(device: T) -> Self
    where
        T: DeviceTrait + Send + Sync + Clone + 'static,
        T::SupportedInputConfigs: Clone,
        T::SupportedOutputConfigs: Clone,
        T::Stream: Send + Sync,
    {
        Self(Box::new(device))
    }
}

impl Clone for Device {
    fn clone(&self) -> Self {
        self.0.clone()
    }
}

impl PartialEq for Device {
    fn eq(&self, other: &Self) -> bool {
        DeviceTrait::id(self).ok() == DeviceTrait::id(other).ok()
    }
}

impl Eq for Device {}

impl std::hash::Hash for Device {
    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
        DeviceTrait::id(self).ok().hash(state);
    }
}

impl fmt::Display for Device {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let desc = DeviceTrait::description(self).map_err(|_| fmt::Error)?;
        f.write_str(desc.name())
    }
}

impl fmt::Debug for Device {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.debug_tuple("Device")
            .field(&DeviceTrait::description(self).map(|d| d.name().to_owned()))
            .finish()
    }
}

/// A stream that can be used with custom [`StreamTrait`] implementations.
pub struct Stream(Box<dyn StreamErased>);

impl Stream {
    /// Construct a custom stream from an arbitrary [`StreamTrait`] implementation.
    pub fn from_stream<T>(stream: T) -> Self
    where
        T: StreamTrait + Send + Sync + 'static,
    {
        Self(Box::new(stream))
    }
}

// dyn-compatible versions of DeviceTrait, HostTrait, and StreamTrait
// these only accept/return things via trait objects

type Devices = Box<dyn Iterator<Item = Device>>;
trait HostErased: Send + Sync {
    fn devices(&self) -> Result<Devices, Error>;
    fn default_input_device(&self) -> Option<Device>;
    fn default_output_device(&self) -> Option<Device>;
}

pub struct SupportedConfigs(Box<dyn SupportedConfigsErased>);

// A trait for supported configs. This only adds a dyn compatible clone function
// This is required because `SupportedInputConfigsInner` & `SupportedOutputConfigsInner` are `Clone`
trait SupportedConfigsErased: Iterator<Item = SupportedStreamConfigRange> {
    fn clone(&self) -> SupportedConfigs;
}

impl<T> SupportedConfigsErased for T
where
    T: Iterator<Item = SupportedStreamConfigRange> + Clone + 'static,
{
    fn clone(&self) -> SupportedConfigs {
        SupportedConfigs(Box::new(Clone::clone(self)))
    }
}

impl Iterator for SupportedConfigs {
    type Item = SupportedStreamConfigRange;

    fn next(&mut self) -> Option<Self::Item> {
        self.0.next()
    }
}

impl Clone for SupportedConfigs {
    fn clone(&self) -> Self {
        self.0.clone()
    }
}

type ErrorCallback = Box<dyn FnMut(Error) + Send + 'static>;
type InputCallback = Box<dyn FnMut(&Data, &InputCallbackInfo) + Send + 'static>;
type OutputCallback = Box<dyn FnMut(&mut Data, &OutputCallbackInfo) + Send + 'static>;

trait DeviceErased: Send + Sync {
    fn description(&self) -> Result<DeviceDescription, Error>;
    fn id(&self) -> Result<DeviceId, Error>;
    fn supports_input(&self) -> bool;
    fn supports_output(&self) -> bool;
    fn supported_input_configs(&self) -> Result<SupportedConfigs, Error>;
    fn supported_output_configs(&self) -> Result<SupportedConfigs, Error>;
    fn default_input_config(&self) -> Result<SupportedStreamConfig, Error>;
    fn default_output_config(&self) -> Result<SupportedStreamConfig, Error>;
    fn build_input_stream_raw(
        &self,
        config: StreamConfig,
        sample_format: SampleFormat,
        data_callback: InputCallback,
        error_callback: ErrorCallback,
        timeout: Option<Duration>,
    ) -> Result<Stream, Error>;
    fn build_output_stream_raw(
        &self,
        config: StreamConfig,
        sample_format: SampleFormat,
        data_callback: OutputCallback,
        error_callback: ErrorCallback,
        timeout: Option<Duration>,
    ) -> Result<Stream, Error>;
    // Required because `DeviceInner` is clone
    fn clone(&self) -> Device;
}

trait StreamErased: Send + Sync {
    fn play(&self) -> Result<(), Error>;
    fn pause(&self) -> Result<(), Error>;
    fn now(&self) -> StreamInstant;
    fn buffer_size(&self) -> Result<FrameCount, Error>;
}

fn device_to_erased(d: impl DeviceErased + 'static) -> Device {
    Device(Box::new(d))
}

impl<T> HostErased for T
where
    T: HostTrait + Send + Sync,
    T::Devices: 'static,
    T::Device: DeviceErased + 'static,
{
    fn devices(&self) -> Result<Devices, Error> {
        let iter = <T as HostTrait>::devices(self)?;
        let erased = Box::new(iter.map(device_to_erased));
        Ok(erased)
    }

    fn default_input_device(&self) -> Option<Device> {
        <T as HostTrait>::default_input_device(self).map(device_to_erased)
    }

    fn default_output_device(&self) -> Option<Device> {
        <T as HostTrait>::default_output_device(self).map(device_to_erased)
    }
}

fn supported_configs_to_erased(
    i: impl Iterator<Item = SupportedStreamConfigRange> + Clone + 'static,
) -> SupportedConfigs {
    SupportedConfigs(Box::new(i))
}

fn stream_to_erased(s: impl StreamTrait + Send + Sync + 'static) -> Stream {
    Stream(Box::new(s))
}

impl<T> DeviceErased for T
where
    T: DeviceTrait + Send + Sync + Clone + 'static,
    T::SupportedInputConfigs: Clone + 'static,
    T::SupportedOutputConfigs: Clone + 'static,
    T::Stream: Send + Sync + 'static,
{
    fn description(&self) -> Result<DeviceDescription, Error> {
        <T as DeviceTrait>::description(self)
    }

    fn id(&self) -> Result<DeviceId, Error> {
        <T as DeviceTrait>::id(self)
    }

    fn supports_input(&self) -> bool {
        <T as DeviceTrait>::supports_input(self)
    }

    fn supports_output(&self) -> bool {
        <T as DeviceTrait>::supports_output(self)
    }

    fn supported_input_configs(&self) -> Result<SupportedConfigs, Error> {
        <T as DeviceTrait>::supported_input_configs(self).map(supported_configs_to_erased)
    }

    fn supported_output_configs(&self) -> Result<SupportedConfigs, Error> {
        <T as DeviceTrait>::supported_output_configs(self).map(supported_configs_to_erased)
    }

    fn default_input_config(&self) -> Result<SupportedStreamConfig, Error> {
        <T as DeviceTrait>::default_input_config(self)
    }

    fn default_output_config(&self) -> Result<SupportedStreamConfig, Error> {
        <T as DeviceTrait>::default_output_config(self)
    }

    fn build_input_stream_raw(
        &self,
        config: StreamConfig,
        sample_format: SampleFormat,
        data_callback: InputCallback,
        error_callback: ErrorCallback,
        timeout: Option<Duration>,
    ) -> Result<Stream, Error> {
        <T as DeviceTrait>::build_input_stream_raw(
            self,
            config,
            sample_format,
            data_callback,
            error_callback,
            timeout,
        )
        .map(stream_to_erased)
    }

    fn build_output_stream_raw(
        &self,
        config: StreamConfig,
        sample_format: SampleFormat,
        data_callback: OutputCallback,
        error_callback: ErrorCallback,
        timeout: Option<Duration>,
    ) -> Result<Stream, Error> {
        <T as DeviceTrait>::build_output_stream_raw(
            self,
            config,
            sample_format,
            data_callback,
            error_callback,
            timeout,
        )
        .map(stream_to_erased)
    }

    fn clone(&self) -> Device {
        device_to_erased(Clone::clone(self))
    }
}

impl<T> StreamErased for T
where
    T: StreamTrait + Send + Sync,
{
    fn play(&self) -> Result<(), Error> {
        <T as StreamTrait>::play(self)
    }

    fn pause(&self) -> Result<(), Error> {
        <T as StreamTrait>::pause(self)
    }

    fn now(&self) -> StreamInstant {
        <T as StreamTrait>::now(self)
    }

    fn buffer_size(&self) -> Result<FrameCount, Error> {
        <T as StreamTrait>::buffer_size(self)
    }
}

// implementations of HostTrait, DeviceTrait, and StreamTrait for custom versions

impl HostTrait for Host {
    type Devices = Devices;
    type Device = Device;

    fn is_available() -> bool {
        false
    }

    fn devices(&self) -> Result<Self::Devices, Error> {
        self.0.devices()
    }

    fn default_input_device(&self) -> Option<Self::Device> {
        self.0.default_input_device()
    }

    fn default_output_device(&self) -> Option<Self::Device> {
        self.0.default_output_device()
    }
}

impl DeviceTrait for Device {
    type SupportedInputConfigs = SupportedConfigs;

    type SupportedOutputConfigs = SupportedConfigs;

    type Stream = Stream;

    fn description(&self) -> Result<DeviceDescription, Error> {
        self.0.description()
    }

    fn id(&self) -> Result<DeviceId, Error> {
        self.0.id()
    }

    fn supports_input(&self) -> bool {
        self.0.supports_input()
    }

    fn supports_output(&self) -> bool {
        self.0.supports_output()
    }

    fn supported_input_configs(&self) -> Result<Self::SupportedInputConfigs, Error> {
        self.0.supported_input_configs()
    }

    fn supported_output_configs(&self) -> Result<Self::SupportedOutputConfigs, Error> {
        self.0.supported_output_configs()
    }

    fn default_input_config(&self) -> Result<SupportedStreamConfig, Error> {
        self.0.default_input_config()
    }

    fn default_output_config(&self) -> Result<SupportedStreamConfig, Error> {
        self.0.default_output_config()
    }

    fn build_input_stream_raw<D, E>(
        &self,
        config: StreamConfig,
        sample_format: SampleFormat,
        data_callback: D,
        error_callback: E,
        timeout: Option<Duration>,
    ) -> Result<Self::Stream, Error>
    where
        D: FnMut(&Data, &InputCallbackInfo) + Send + 'static,
        E: FnMut(Error) + Send + 'static,
    {
        self.0.build_input_stream_raw(
            config,
            sample_format,
            Box::new(data_callback),
            Box::new(error_callback),
            timeout,
        )
    }

    fn build_output_stream_raw<D, E>(
        &self,
        config: StreamConfig,
        sample_format: SampleFormat,
        data_callback: D,
        error_callback: E,
        timeout: Option<Duration>,
    ) -> Result<Self::Stream, Error>
    where
        D: FnMut(&mut Data, &OutputCallbackInfo) + Send + 'static,
        E: FnMut(Error) + Send + 'static,
    {
        self.0.build_output_stream_raw(
            config,
            sample_format,
            Box::new(data_callback),
            Box::new(error_callback),
            timeout,
        )
    }
}

impl StreamTrait for Stream {
    fn play(&self) -> Result<(), Error> {
        self.0.play()
    }

    fn pause(&self) -> Result<(), Error> {
        self.0.pause()
    }

    fn now(&self) -> StreamInstant {
        self.0.now()
    }

    fn buffer_size(&self) -> Result<FrameCount, Error> {
        self.0.buffer_size()
    }
}
