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

use context;
use ffi;
use operation;
use std::ffi::CStr;
use std::mem::{self, forget, MaybeUninit};
use std::os::raw::{c_int, c_void};
use std::ptr;
use util::*;
use *;

#[derive(Debug)]
pub struct Stream(*mut ffi::pa_stream);

impl Stream {
    pub fn new<'a, CM>(
        c: &Context,
        name: &::std::ffi::CStr,
        ss: &SampleSpec,
        map: CM,
    ) -> Option<Self>
    where
        CM: Into<Option<&'a ChannelMap>>,
    {
        let ptr = unsafe {
            ffi::pa_stream_new(
                c.raw_mut(),
                name.as_ptr(),
                ss as *const _,
                to_ptr(map.into()),
            )
        };
        if ptr.is_null() {
            None
        } else {
            Some(Stream(ptr))
        }
    }

    #[doc(hidden)]
    pub fn raw_mut(&self) -> &mut ffi::pa_stream {
        unsafe { &mut *self.0 }
    }

    pub fn unref(self) {
        unsafe {
            ffi::pa_stream_unref(self.raw_mut());
        }
    }

    pub fn get_state(&self) -> StreamState {
        StreamState::try_from(unsafe { ffi::pa_stream_get_state(self.raw_mut()) })
            .expect("pa_stream_get_state returned invalid StreamState")
    }

    pub fn get_context(&self) -> Option<Context> {
        let ptr = unsafe { ffi::pa_stream_get_context(self.raw_mut()) };
        if ptr.is_null() {
            return None;
        }

        let ctx = unsafe { context::from_raw_ptr(ptr) };
        Some(ctx)
    }

    pub fn get_index(&self) -> u32 {
        unsafe { ffi::pa_stream_get_index(self.raw_mut()) }
    }

    pub fn get_device_name<'a>(&'a self) -> Result<&'a CStr> {
        let r = unsafe { ffi::pa_stream_get_device_name(self.raw_mut()) };
        if r.is_null() {
            let err = if let Some(c) = self.get_context() {
                c.errno()
            } else {
                ffi::PA_ERR_UNKNOWN
            };
            return Err(ErrorCode::from_error_code(err));
        }
        Ok(unsafe { CStr::from_ptr(r) })
    }

    pub fn is_suspended(&self) -> Result<bool> {
        let r = unsafe { ffi::pa_stream_is_suspended(self.raw_mut()) };
        error_result!(r != 0, r)
    }

    pub fn is_corked(&self) -> Result<bool> {
        let r = unsafe { ffi::pa_stream_is_corked(self.raw_mut()) };
        error_result!(r != 0, r)
    }

    pub fn connect_playback<'a, D, A, V, S>(
        &self,
        dev: D,
        attr: A,
        flags: StreamFlags,
        volume: V,
        sync_stream: S,
    ) -> Result<()>
    where
        D: Into<Option<&'a CStr>>,
        A: Into<Option<&'a BufferAttr>>,
        V: Into<Option<&'a CVolume>>,
        S: Into<Option<&'a mut Stream>>,
    {
        let r = unsafe {
            ffi::pa_stream_connect_playback(
                self.raw_mut(),
                str_to_ptr(dev.into()),
                to_ptr(attr.into()),
                flags.into(),
                to_ptr(volume.into()),
                map_to_mut_ptr(sync_stream.into(), |p| p.0),
            )
        };
        error_result!((), r)
    }

    pub fn connect_record<'a, D, A>(&self, dev: D, attr: A, flags: StreamFlags) -> Result<()>
    where
        D: Into<Option<&'a CStr>>,
        A: Into<Option<&'a BufferAttr>>,
    {
        let r = unsafe {
            ffi::pa_stream_connect_record(
                self.raw_mut(),
                str_to_ptr(dev.into()),
                to_ptr(attr.into()),
                flags.into(),
            )
        };
        error_result!((), r)
    }

    pub fn disconnect(&self) -> Result<()> {
        let r = unsafe { ffi::pa_stream_disconnect(self.raw_mut()) };
        error_result!((), r)
    }

    pub fn begin_write(&self, req_bytes: usize) -> Result<(*mut c_void, usize)> {
        let mut data: *mut c_void = ptr::null_mut();
        let mut nbytes = req_bytes;
        let r = unsafe { ffi::pa_stream_begin_write(self.raw_mut(), &mut data, &mut nbytes) };
        error_result!((data, nbytes), r)
    }

    pub fn cancel_write(&self) -> Result<()> {
        let r = unsafe { ffi::pa_stream_cancel_write(self.raw_mut()) };
        error_result!((), r)
    }

    pub fn write(
        &self,
        data: *const c_void,
        nbytes: usize,
        offset: i64,
        seek: SeekMode,
    ) -> Result<()> {
        let r = unsafe {
            ffi::pa_stream_write(self.raw_mut(), data, nbytes, None, offset, seek.into())
        };
        error_result!((), r)
    }

    pub unsafe fn peek(&self, data: *mut *const c_void, length: *mut usize) -> Result<()> {
        let r = ffi::pa_stream_peek(self.raw_mut(), data, length);
        error_result!((), r)
    }

    pub fn drop(&self) -> Result<()> {
        let r = unsafe { ffi::pa_stream_drop(self.raw_mut()) };
        error_result!((), r)
    }

    pub fn writable_size(&self) -> Result<usize> {
        let r = unsafe { ffi::pa_stream_writable_size(self.raw_mut()) };
        if r == ::std::usize::MAX {
            let err = if let Some(c) = self.get_context() {
                c.errno()
            } else {
                ffi::PA_ERR_UNKNOWN
            };
            return Err(ErrorCode::from_error_code(err));
        }
        Ok(r)
    }

    pub fn readable_size(&self) -> Result<usize> {
        let r = unsafe { ffi::pa_stream_readable_size(self.raw_mut()) };
        if r == ::std::usize::MAX {
            let err = if let Some(c) = self.get_context() {
                c.errno()
            } else {
                ffi::PA_ERR_UNKNOWN
            };
            return Err(ErrorCode::from_error_code(err));
        }
        Ok(r)
    }

    pub fn update_timing_info<CB>(&self, _: CB, userdata: *mut c_void) -> Result<Operation>
    where
        CB: Fn(&Stream, i32, *mut c_void),
    {
        assert_eq!(mem::size_of::<CB>(), 0);

        // See: A note about `wrapped` functions
        unsafe extern "C" fn wrapped<F>(
            s: *mut ffi::pa_stream,
            success: c_int,
            userdata: *mut c_void,
        ) where
            F: Fn(&Stream, i32, *mut c_void),
        {
            let mut stm = stream::from_raw_ptr(s);
            let cb = MaybeUninit::<F>::uninit();
            let result = (*cb.as_ptr())(&mut stm, success, userdata);
            forget(stm);

            result
        }

        let r = unsafe {
            ffi::pa_stream_update_timing_info(self.raw_mut(), Some(wrapped::<CB>), userdata)
        };
        if r.is_null() {
            let err = if let Some(c) = self.get_context() {
                c.errno()
            } else {
                ffi::PA_ERR_UNKNOWN
            };
            return Err(ErrorCode::from_error_code(err));
        }
        Ok(unsafe { operation::from_raw_ptr(r) })
    }

    pub fn clear_state_callback(&self) {
        unsafe {
            ffi::pa_stream_set_state_callback(self.raw_mut(), None, ptr::null_mut());
        }
    }

    pub fn set_state_callback<CB>(&self, _: CB, userdata: *mut c_void)
    where
        CB: Fn(&Stream, *mut c_void),
    {
        assert_eq!(mem::size_of::<CB>(), 0);

        // See: A note about `wrapped` functions
        unsafe extern "C" fn wrapped<F>(s: *mut ffi::pa_stream, userdata: *mut c_void)
        where
            F: Fn(&Stream, *mut c_void),
        {
            let mut stm = stream::from_raw_ptr(s);
            let cb = MaybeUninit::<F>::uninit();
            let result = (*cb.as_ptr())(&mut stm, userdata);
            forget(stm);

            result
        }

        unsafe {
            ffi::pa_stream_set_state_callback(self.raw_mut(), Some(wrapped::<CB>), userdata);
        }
    }

    pub fn clear_write_callback(&self) {
        unsafe {
            ffi::pa_stream_set_write_callback(self.raw_mut(), None, ptr::null_mut());
        }
    }

    pub fn set_write_callback<CB>(&self, _: CB, userdata: *mut c_void)
    where
        CB: Fn(&Stream, usize, *mut c_void),
    {
        assert_eq!(mem::size_of::<CB>(), 0);

        // See: A note about `wrapped` functions
        unsafe extern "C" fn wrapped<F>(
            s: *mut ffi::pa_stream,
            nbytes: usize,
            userdata: *mut c_void,
        ) where
            F: Fn(&Stream, usize, *mut c_void),
        {
            let mut stm = stream::from_raw_ptr(s);
            let cb = MaybeUninit::<F>::uninit();
            let result = (*cb.as_ptr())(&mut stm, nbytes, userdata);
            forget(stm);

            result
        }

        unsafe {
            ffi::pa_stream_set_write_callback(self.raw_mut(), Some(wrapped::<CB>), userdata);
        }
    }

    pub fn clear_read_callback(&self) {
        unsafe {
            ffi::pa_stream_set_read_callback(self.raw_mut(), None, ptr::null_mut());
        }
    }

    pub fn set_read_callback<CB>(&self, _: CB, userdata: *mut c_void)
    where
        CB: Fn(&Stream, usize, *mut c_void),
    {
        assert_eq!(mem::size_of::<CB>(), 0);

        // See: A note about `wrapped` functions
        unsafe extern "C" fn wrapped<F>(
            s: *mut ffi::pa_stream,
            nbytes: usize,
            userdata: *mut c_void,
        ) where
            F: Fn(&Stream, usize, *mut c_void),
        {
            let mut stm = stream::from_raw_ptr(s);
            let cb = MaybeUninit::<F>::uninit();
            let result = (*cb.as_ptr())(&mut stm, nbytes, userdata);
            forget(stm);

            result
        }

        unsafe {
            ffi::pa_stream_set_read_callback(self.raw_mut(), Some(wrapped::<CB>), userdata);
        }
    }

    pub fn cork<CB>(&self, b: i32, _: CB, userdata: *mut c_void) -> Result<Operation>
    where
        CB: Fn(&Stream, i32, *mut c_void),
    {
        assert_eq!(mem::size_of::<CB>(), 0);

        // See: A note about `wrapped` functions
        unsafe extern "C" fn wrapped<F>(
            s: *mut ffi::pa_stream,
            success: c_int,
            userdata: *mut c_void,
        ) where
            F: Fn(&Stream, i32, *mut c_void),
        {
            let mut stm = stream::from_raw_ptr(s);
            let cb = MaybeUninit::<F>::uninit();
            let result = (*cb.as_ptr())(&mut stm, success, userdata);
            forget(stm);

            result
        }

        let r = unsafe { ffi::pa_stream_cork(self.raw_mut(), b, Some(wrapped::<CB>), userdata) };
        if r.is_null() {
            let err = if let Some(c) = self.get_context() {
                c.errno()
            } else {
                ffi::PA_ERR_UNKNOWN
            };
            return Err(ErrorCode::from_error_code(err));
        }
        Ok(unsafe { operation::from_raw_ptr(r) })
    }

    pub fn get_time(&self) -> Result<USec> {
        let mut usec: USec = 0;
        let r = unsafe { ffi::pa_stream_get_time(self.raw_mut(), &mut usec) };
        error_result!(usec, r)
    }

    pub fn get_latency(&self) -> Result<StreamLatency> {
        let mut usec: u64 = 0;
        let mut negative: i32 = 0;
        let r = unsafe { ffi::pa_stream_get_latency(self.raw_mut(), &mut usec, &mut negative) };
        error_result!(
            if negative == 0 {
                StreamLatency::Positive(usec)
            } else {
                StreamLatency::Negative(usec)
            },
            r
        )
    }

    pub fn get_sample_spec(&self) -> &SampleSpec {
        unsafe {
            let ptr = ffi::pa_stream_get_sample_spec(self.raw_mut());
            debug_assert!(!ptr.is_null());
            &*ptr
        }
    }

    pub fn get_channel_map(&self) -> &ChannelMap {
        unsafe {
            let ptr = ffi::pa_stream_get_channel_map(self.raw_mut());
            debug_assert!(!ptr.is_null());
            &*ptr
        }
    }

    pub fn get_buffer_attr(&self) -> &BufferAttr {
        unsafe {
            let ptr = ffi::pa_stream_get_buffer_attr(self.raw_mut());
            debug_assert!(!ptr.is_null());
            &*ptr
        }
    }

    pub fn set_name<CB>(&self, name: &CStr, _: CB, userdata: *mut c_void) -> Result<Operation>
    where
        CB: Fn(&Stream, i32, *mut c_void),
    {
        assert_eq!(mem::size_of::<CB>(), 0);

        // See: A note about `wrapped` functions
        unsafe extern "C" fn wrapped<F>(
            s: *mut ffi::pa_stream,
            success: c_int,
            userdata: *mut c_void,
        ) where
            F: Fn(&Stream, i32, *mut c_void),
        {
            let mut stm = stream::from_raw_ptr(s);
            let cb = MaybeUninit::<F>::uninit();
            let result = (*cb.as_ptr())(&mut stm, success, userdata);
            forget(stm);

            result
        }

        let r = unsafe {
            ffi::pa_stream_set_name(self.raw_mut(), name.as_ptr(), Some(wrapped::<CB>), userdata)
        };
        if r.is_null() {
            let err = if let Some(c) = self.get_context() {
                c.errno()
            } else {
                ffi::PA_ERR_UNKNOWN
            };
            return Err(ErrorCode::from_error_code(err));
        }
        Ok(unsafe { operation::from_raw_ptr(r) })
    }
}

#[doc(hidden)]
pub unsafe fn from_raw_ptr(ptr: *mut ffi::pa_stream) -> Stream {
    Stream(ptr)
}
