// Copyright © 2017-2018 Mozilla Foundation
//
// This program is made available under an ISC-style license.  See the
// accompanying file LICENSE for details.

use cubeb_core;
use ffi;
use std::ffi::CString;
use std::marker::PhantomData;
use std::mem::ManuallyDrop;
use std::os::raw::{c_long, c_void};
use std::slice::{from_raw_parts, from_raw_parts_mut};
use std::{ops, panic, ptr};
use {ContextRef, DeviceId, Error, Result, State, StreamParamsRef};

/// User supplied data callback.
///
/// - Calling other cubeb functions from this callback is unsafe.
/// - The code in the callback should be non-blocking.
/// - Returning less than the number of frames this callback asks for or
///   provides puts the stream in drain mode. This callback will not be called
///   again, and the state callback will be called with CUBEB_STATE_DRAINED when
///   all the frames have been output.
///
/// # Arguments
///
/// - `input_buffer`: A slice containing the input data, zero-len if this is an output-only stream.
/// - `output_buffer`: A mutable slice to be filled with audio samples, zero-len if this is an input-only stream.
///
/// # Return value
///
/// If the stream has output, this is the number of frames written to the output buffer. In this
/// case, if this number is less than the length of the output buffer, then the stream will start to
/// drain.
///
/// If the stream is input only, then returning the length of the input buffer indicates data has
/// been read.  In this case, a value less than that will result in the stream being stopped.
pub type DataCallback<F> = dyn FnMut(&[F], &mut [F]) -> isize + Send + Sync + 'static;

/// User supplied state callback.
///
/// # Arguments
///
/// `state`: The new state of the stream
pub type StateCallback = dyn FnMut(State) + Send + Sync + 'static;

/// User supplied callback called when the underlying device changed.
pub type DeviceChangedCallback = dyn FnMut() + Send + Sync + 'static;

pub struct StreamCallbacks<F> {
    pub(crate) data: Box<DataCallback<F>>,
    pub(crate) state: Box<StateCallback>,
    pub(crate) device_changed: Option<Box<DeviceChangedCallback>>,
}

/// Audio input/output stream
///
/// # Example
/// ```no_run
/// extern crate cubeb;
/// use std::thread;
/// use std::time::Duration;
///
/// type Frame = cubeb::MonoFrame<f32>;
///
/// fn main() {
///     let ctx = cubeb::init("Cubeb tone example").unwrap();
///
///     let params = cubeb::StreamParamsBuilder::new()
///         .format(cubeb::SampleFormat::Float32LE)
///         .rate(44_100)
///         .channels(1)
///         .layout(cubeb::ChannelLayout::MONO)
///         .prefs(cubeb::StreamPrefs::NONE)
///         .take();
///
///     let phase_inc = 440.0 / 44_100.0;
///     let mut phase = 0.0;
///     let volume = 0.25;
///
///     let mut builder = cubeb::StreamBuilder::<Frame>::new();
///     builder
///         .name("Cubeb Square Wave")
///         .default_output(&params)
///         .latency(0x1000)
///         .data_callback(move |_, output| {
///             // Generate a square wave
///             for x in output.iter_mut() {
///                 x.m = if phase < 0.5 { volume } else { -volume };
///                 phase = (phase + phase_inc) % 1.0;
///             }
///
///             output.len() as isize
///         })
///         .state_callback(|state| {
///             println!("stream {:?}", state);
///         });
///     let stream = builder.init(&ctx).expect("Failed to create stream.");
///
///     // Start playback
///     stream.start().unwrap();
///
///     // Play for 1/2 second
///     thread::sleep(Duration::from_millis(500));
///
///     // Shutdown
///     stream.stop().unwrap();
/// }
/// ```
pub struct Stream<F>(ManuallyDrop<cubeb_core::Stream>, PhantomData<*const F>);

impl<F> Stream<F> {
    fn new(s: cubeb_core::Stream) -> Stream<F> {
        Stream(ManuallyDrop::new(s), PhantomData)
    }
}

impl<F> Drop for Stream<F> {
    fn drop(&mut self) {
        let user_ptr = self.user_ptr();
        unsafe { ManuallyDrop::drop(&mut self.0) };
        let _ = unsafe { Box::from_raw(user_ptr as *mut StreamCallbacks<F>) };
    }
}

impl<F> ops::Deref for Stream<F> {
    type Target = cubeb_core::Stream;

    fn deref(&self) -> &Self::Target {
        &self.0
    }
}

/// Stream builder
///
/// ```no_run
/// use cubeb::{Context, MonoFrame, Sample};
/// use std::f32::consts::PI;
/// use std::thread;
/// use std::time::Duration;
///
/// const SAMPLE_FREQUENCY: u32 = 48_000;
/// const STREAM_FORMAT: cubeb::SampleFormat = cubeb::SampleFormat::S16LE;
/// type Frame = MonoFrame<i16>;
///
/// let ctx = Context::init(None, None).unwrap();
///
/// let params = cubeb::StreamParamsBuilder::new()
///     .format(STREAM_FORMAT)
///     .rate(SAMPLE_FREQUENCY)
///     .channels(1)
///     .layout(cubeb::ChannelLayout::MONO)
///     .take();
///
/// let mut position = 0u32;
///
/// let mut builder = cubeb::StreamBuilder::<Frame>::new();
/// builder
///     .name("Cubeb tone (mono)")
///     .default_output(&params)
///     .latency(0x1000)
///     .data_callback(move |_, output| {
///         // generate our test tone on the fly
///         for f in output.iter_mut() {
///             // North American dial tone
///             let t1 = (2.0 * PI * 350.0 * position as f32 / SAMPLE_FREQUENCY as f32).sin();
///             let t2 = (2.0 * PI * 440.0 * position as f32 / SAMPLE_FREQUENCY as f32).sin();
///
///             f.m = i16::from_float(0.5 * (t1 + t2));
///
///             position += 1;
///         }
///         output.len() as isize
///     })
///     .state_callback(|state| {
///         println!("stream {:?}", state);
///     });
///
/// let stream = builder.init(&ctx).expect("Failed to create cubeb stream");
/// ```
pub struct StreamBuilder<'a, F> {
    name: Option<CString>,
    input: Option<(DeviceId, &'a StreamParamsRef)>,
    output: Option<(DeviceId, &'a StreamParamsRef)>,
    latency: Option<u32>,
    data_cb: Option<Box<DataCallback<F>>>,
    state_cb: Option<Box<StateCallback>>,
    device_changed_cb: Option<Box<DeviceChangedCallback>>,
}

impl<'a, F> StreamBuilder<'a, F> {
    pub fn new() -> StreamBuilder<'a, F> {
        Default::default()
    }

    /// User supplied data callback, see [`DataCallback`]
    pub fn data_callback<D>(&mut self, cb: D) -> &mut Self
    where
        D: FnMut(&[F], &mut [F]) -> isize + Send + Sync + 'static,
    {
        self.data_cb = Some(Box::new(cb) as Box<DataCallback<F>>);
        self
    }

    /// User supplied state callback, see [`StateCallback`]
    pub fn state_callback<S>(&mut self, cb: S) -> &mut Self
    where
        S: FnMut(State) + Send + Sync + 'static,
    {
        self.state_cb = Some(Box::new(cb) as Box<StateCallback>);
        self
    }

    /// A name for this stream.
    pub fn name<T: Into<Vec<u8>>>(&mut self, name: T) -> &mut Self {
        self.name = Some(CString::new(name).unwrap());
        self
    }

    /// Use the default input device with `params`
    ///
    /// Optional if the stream is output only
    pub fn default_input(&mut self, params: &'a StreamParamsRef) -> &mut Self {
        self.input = Some((ptr::null(), params));
        self
    }

    /// Use a specific input device with `params`
    ///
    /// Optional if the stream is output only
    pub fn input(&mut self, device: DeviceId, params: &'a StreamParamsRef) -> &mut Self {
        self.input = Some((device, params));
        self
    }

    /// Use the default output device with `params`
    ///
    /// Optional if the stream is input only
    pub fn default_output(&mut self, params: &'a StreamParamsRef) -> &mut Self {
        self.output = Some((ptr::null(), params));
        self
    }

    /// Use a specific output device with `params`
    ///
    /// Optional if the stream is input only
    pub fn output(&mut self, device: DeviceId, params: &'a StreamParamsRef) -> &mut Self {
        self.output = Some((device, params));
        self
    }

    /// Stream latency in frames.
    ///
    /// Valid range is [1, 96000].
    pub fn latency(&mut self, latency: u32) -> &mut Self {
        self.latency = Some(latency);
        self
    }

    /// User supplied callback called when the underlying device changed.
    ///
    /// See [`StateCallback`]
    ///
    /// Optional
    pub fn device_changed_cb<CB>(&mut self, cb: CB) -> &mut Self
    where
        CB: FnMut() + Send + Sync + 'static,
    {
        self.device_changed_cb = Some(Box::new(cb) as Box<DeviceChangedCallback>);
        self
    }

    /// Build the stream
    pub fn init(self, ctx: &ContextRef) -> Result<Stream<F>> {
        if self.data_cb.is_none() || self.state_cb.is_none() {
            return Err(Error::Error);
        }

        let has_device_changed = self.device_changed_cb.is_some();
        let cbs = Box::into_raw(Box::new(StreamCallbacks {
            data: self.data_cb.unwrap(),
            state: self.state_cb.unwrap(),
            device_changed: self.device_changed_cb,
        }));

        let stream_name = self.name.as_deref();
        let (input_device, input_stream_params) =
            self.input.map_or((ptr::null(), None), |x| (x.0, Some(x.1)));
        let (output_device, output_stream_params) = self
            .output
            .map_or((ptr::null(), None), |x| (x.0, Some(x.1)));
        let latency = self.latency.unwrap_or(1);
        let data_callback: ffi::cubeb_data_callback = Some(data_cb_c::<F>);
        let state_callback: ffi::cubeb_state_callback = Some(state_cb_c::<F>);

        let stream = unsafe {
            ctx.stream_init(
                stream_name,
                input_device,
                input_stream_params,
                output_device,
                output_stream_params,
                latency,
                data_callback,
                state_callback,
                cbs as *mut _,
            )?
        };
        if has_device_changed {
            let device_changed_callback: ffi::cubeb_device_changed_callback =
                Some(device_changed_cb_c::<F>);
            stream.register_device_changed_callback(device_changed_callback)?;
        }
        Ok(Stream::new(stream))
    }
}

impl<F> Default for StreamBuilder<'_, F> {
    fn default() -> Self {
        StreamBuilder {
            name: None,
            input: None,
            output: None,
            latency: None,
            data_cb: None,
            state_cb: None,
            device_changed_cb: None,
        }
    }
}

// C callable callbacks
unsafe extern "C" fn data_cb_c<F>(
    _: *mut ffi::cubeb_stream,
    user_ptr: *mut c_void,
    input_buffer: *const c_void,
    output_buffer: *mut c_void,
    nframes: c_long,
) -> c_long {
    let ok = panic::catch_unwind(|| {
        let cbs = &mut *(user_ptr as *mut StreamCallbacks<F>);
        let input: &[F] = if input_buffer.is_null() {
            &[]
        } else {
            from_raw_parts(input_buffer as *const _, nframes as usize)
        };
        let output: &mut [F] = if output_buffer.is_null() {
            &mut []
        } else {
            from_raw_parts_mut(output_buffer as *mut _, nframes as usize)
        };
        (cbs.data)(input, output) as c_long
    });
    ok.unwrap_or(0)
}

unsafe extern "C" fn state_cb_c<F>(
    _: *mut ffi::cubeb_stream,
    user_ptr: *mut c_void,
    state: ffi::cubeb_state,
) {
    let ok = panic::catch_unwind(|| {
        let state = State::from(state);
        let cbs = &mut *(user_ptr as *mut StreamCallbacks<F>);
        (cbs.state)(state);
    });
    ok.expect("State callback panicked");
}

unsafe extern "C" fn device_changed_cb_c<F>(user_ptr: *mut c_void) {
    let ok = panic::catch_unwind(|| {
        let cbs = &mut *(user_ptr as *mut StreamCallbacks<F>);
        if let Some(ref mut device_changed) = cbs.device_changed {
            device_changed();
        }
    });
    ok.expect("Device changed callback panicked");
}
