use enums::{ChannelData, FileTypes, MessageType, Status};
use rtaudio::{CsAudioDevice, RtAudioParams};
#[derive(Debug, Clone)]
pub struct FileInfo {
    
    pub name: Option<String>,
    
    pub file_type: FileTypes,
    
    pub is_writing: bool,
    
    pub is_temp: bool,
}
#[doc(hidden)]
#[derive(Default)]
pub struct Callbacks<'a> {
    pub message_cb: Option<Box<dyn FnMut(MessageType, &str) + 'a>>,
    pub audio_dev_list_cb: Option<Box<dyn FnMut(CsAudioDevice) + 'a>>,
    pub play_open_cb: Option<Box<dyn FnMut(&RtAudioParams) -> Status + 'a>>,
    pub rec_open_cb: Option<Box<dyn FnMut(&RtAudioParams) -> Status + 'a>>,
    pub rt_play_cb: Option<Box<dyn FnMut(&[f64]) + 'a>>,
    pub rt_rec_cb: Option<Box<dyn FnMut(&mut [f64]) -> usize + 'a>>,
    pub sense_event_cb: Option<Box<dyn FnMut() + 'a>>,
    pub keyboard_cb: Option<Box<dyn FnMut() -> char + 'a>>, 
    
    pub rt_close_cb: Option<Box<dyn FnMut() + 'a>>,
    pub cscore_cb: Option<Box<dyn FnMut() + 'a>>,
    pub input_channel_cb: Option<Box<dyn FnMut(&str) -> ChannelData + 'a>>,
    pub output_channel_cb: Option<Box<dyn FnMut(&str, ChannelData) + 'a>>,
    pub file_open_cb: Option<Box<dyn FnMut(&FileInfo) + 'a>>,
    pub midi_in_open_cb: Option<Box<dyn FnMut(&str) + 'a>>,
    pub midi_out_open_cb: Option<Box<dyn FnMut(&str) + 'a>>,
    pub midi_read_cb: Option<Box<dyn FnMut(&mut [u8]) -> usize + 'a>>,
    pub midi_write_cb: Option<Box<dyn FnMut(&[u8]) -> usize + 'a>>,
    pub midi_in_close_cb: Option<Box<dyn FnMut() + 'a>>,
    pub midi_out_close_cb: Option<Box<dyn FnMut() + 'a>>,
    pub yield_cb: Option<Box<dyn FnMut() -> bool + 'a>>,
}
pub const MESSAGE_CB: u32 = 1;
pub const SENSE_EVENT: u32 = 2;
pub const PLAY_OPEN: u32 = 3;
pub const REC_OPEN: u32 = 4;
pub const REAL_TIME_PLAY: u32 = 6;
pub const REAL_TIME_REC: u32 = 7;
pub const AUDIO_DEV_LIST: u32 = 9;
pub const RT_CLOSE_CB: u32 = 11;
pub const CSCORE_CB: u32 = 12;
pub const CHANNEL_INPUT_CB: u32 = 13;
pub const CHANNEL_OUTPUT_CB: u32 = 14;
pub const FILE_OPEN_CB: u32 = 15;
pub const MIDI_IN_OPEN_CB: u32 = 16;
pub const MIDI_OUT_OPEN_CB: u32 = 17;
pub const MIDI_READ_CB: u32 = 18;
pub const MIDI_WRITE_CB: u32 = 19;
pub const MIDI_IN_CLOSE: u32 = 20;
pub const MIDI_OUT_CLOSE: u32 = 21;
pub const YIELD_CB: u32 = 22;
pub mod Trampoline {
    use std::panic::{self, AssertUnwindSafe};
    pub extern crate csound_sys as raw;
    use super::*;
    use csound::CallbackHandler;
    use libc::{c_char, c_int, c_uchar,  c_void, memcpy};
    use rtaudio::{CsAudioDevice, RtAudioParams};
    use std::ffi::{CStr, CString};
    use std::slice;
    pub fn ptr_to_string(ptr: *const c_char) -> Option<String> {
        let mut result = None;
        if !ptr.is_null() {
            result = match unsafe { CStr::from_ptr(ptr) }.to_str().ok() {
                Some(str_slice) => Some(str_slice.to_owned()),
                None => None,
            };
        }
        result
    }
    pub fn convert_str_to_c<'a, T>(string: T) -> Result<CString, &'static str>
        where
            T: AsRef<str>,
    {
        let string = string.as_ref();
        if string.is_empty() {
            return Err("Empty string");
        }
        CString::new(string).map_err(|_| "Bad string")
    }
    fn catch<T, F: FnOnce() -> T>(f: F) -> Option<T> {
        match panic::catch_unwind(AssertUnwindSafe(f)) {
            Ok(ret) => Some(ret),
            Err(_) => {
                std::process::exit(-1);
            }
        }
    }
    pub extern "C" fn message_string_cb(
        csound: *mut raw::CSOUND,
        attr: c_int,
        message: *const c_char,
    ) {
        catch(|| unsafe {
            let info = CStr::from_ptr(message);
            if let Ok(s) = info.to_str() {
                if let Some(fun) = (*(raw::csoundGetHostData(csound) as *mut CallbackHandler))
                    .callbacks
                    .message_cb
                    .as_mut()
                {
                    fun(MessageType::from(attr as u32), s);
                }
            }
        });
    }
    
    pub extern "C" fn senseEventCallback(csound: *mut raw::CSOUND, _userData: *mut c_void) {
        catch(|| unsafe {
            if let Some(fun) = (*(raw::csoundGetHostData(csound) as *mut CallbackHandler))
                .callbacks
                .sense_event_cb
                .as_mut()
            {
                fun();
            }
        });
    }
    
    pub extern "C" fn playOpenCallback(
        csound: *mut raw::CSOUND,
        dev: *const raw::csRtAudioParams,
    ) -> c_int {
        catch(|| unsafe {
            let rtParams = RtAudioParams {
                devName: ptr_to_string((*dev).devName),
                devNum: (*dev).devNum as u32,
                bufSamp_SW: (*dev).bufSamp_SW as u32,
                bufSamp_HW: (*dev).bufSamp_HW as u32,
                nChannels: (*dev).nChannels as u32,
                sampleFormat: (*dev).sampleFormat as u32,
                sampleRate: (*dev).sampleRate as f32,
            };
            if let Some(fun) = (*(raw::csoundGetHostData(csound) as *mut CallbackHandler))
                .callbacks
                .play_open_cb
                .as_mut()
            {
                return fun(&rtParams).to_i32() as c_int;
            }
            0
        })
        .unwrap()
    }
    pub extern "C" fn recOpenCallback(
        csound: *mut raw::CSOUND,
        dev: *const raw::csRtAudioParams,
    ) -> c_int {
        catch(|| unsafe {
            let rtParams = RtAudioParams {
                devName: ptr_to_string((*dev).devName),
                devNum: (*dev).devNum as u32,
                bufSamp_SW: (*dev).bufSamp_SW as u32,
                bufSamp_HW: (*dev).bufSamp_HW as u32,
                nChannels: (*dev).nChannels as u32,
                sampleFormat: (*dev).sampleFormat as u32,
                sampleRate: (*dev).sampleRate as f32,
            };
            if let Some(fun) = (*(raw::csoundGetHostData(csound) as *mut CallbackHandler))
                .callbacks
                .rec_open_cb
                .as_mut()
            {
                return fun(&rtParams).to_i32() as c_int;
            }
            -1
        })
        .unwrap()
    }
    pub extern "C" fn rtcloseCallback(csound: *mut raw::CSOUND) {
        catch(|| unsafe {
            if let Some(fun) = (*(raw::csoundGetHostData(csound) as *mut CallbackHandler))
                .callbacks
                .rt_close_cb
                .as_mut()
            {
                fun();
            }
        });
    }
    pub extern "C" fn rtplayCallback(csound: *mut raw::CSOUND, outBuf: *const f64, nbytes: c_int) {
        catch(|| unsafe {
            let out = slice::from_raw_parts(outBuf, nbytes as usize);
            if let Some(fun) = (*(raw::csoundGetHostData(csound) as *mut CallbackHandler))
                .callbacks
                .rt_play_cb
                .as_mut()
            {
                fun(&out);
            }
        });
    }
    pub extern "C" fn rtrecordCallback(
        csound: *mut raw::CSOUND,
        outBuf: *mut f64,
        nbytes: c_int,
    ) -> c_int {
        catch(|| unsafe {
            let mut buff = slice::from_raw_parts_mut(outBuf, nbytes as usize);
            if let Some(fun) = (*(raw::csoundGetHostData(csound) as *mut CallbackHandler))
                .callbacks
                .rt_rec_cb
                .as_mut()
            {
                return fun(&mut buff) as c_int;
            }
            -1
        })
        .unwrap()
    }
    pub extern "C" fn audioDeviceListCallback(
        csound: *mut raw::CSOUND,
        dev: *mut raw::CS_AUDIODEVICE,
        isOutput: c_int,
    ) -> c_int {
        catch(|| unsafe {
            let audioDevice = CsAudioDevice {
                device_name: ptr_to_string((*dev).device_name.as_ptr()),
                device_id: ptr_to_string((*dev).device_id.as_ptr()),
                rt_module: ptr_to_string((*dev).rt_module.as_ptr()),
                max_nchnls: (*dev).max_nchnls as u32,
                isOutput: isOutput as u32,
            };
            if let Some(fun) = (*(raw::csoundGetHostData(csound) as *mut CallbackHandler))
                .callbacks
                .audio_dev_list_cb
                .as_mut()
            {
                fun(audioDevice);
            }
            0
        })
        .unwrap()
    }
    
    
    pub extern "C" fn fileOpenCallback(
        csound: *mut raw::CSOUND,
        filePath: *const c_char,
        fileType: c_int,
        operation: c_int,
        isTemp: c_int,
    ) {
        catch(|| unsafe {
            let name = ptr_to_string(filePath);
            let file_info = FileInfo {
                name,
                file_type: FileTypes::from(fileType as u8),
                is_writing: operation != 0,
                is_temp: isTemp != 0,
            };
            if let Some(fun) = (*(raw::csoundGetHostData(csound) as *mut CallbackHandler))
                .callbacks
                .file_open_cb
                .as_mut()
            {
                fun(&file_info);
            }
        });
    }
    
    
    
    pub extern "C" fn scoreCallback(csound: *mut raw::CSOUND) {
        catch(|| unsafe {
            if let Some(fun) = (*(raw::csoundGetHostData(csound) as *mut CallbackHandler))
                .callbacks
                .cscore_cb
                .as_mut()
            {
                fun();
            }
        });
    }
    
    pub extern "C" fn inputChannelCallback(
        csound: *mut raw::CSOUND,
        channelName: *const c_char,
        channelValuePtr: *mut c_void,
        _channelType: *const c_void,
    ) {
        catch(|| unsafe {
            let name = (CStr::from_ptr(channelName)).to_str();
            if name.is_err() {
                return;
            }
            let name = name.unwrap();
            let result = if let Some(fun) = (*(raw::csoundGetHostData(csound)
                as *mut CallbackHandler))
                .callbacks
                .input_channel_cb
                .as_mut()
            {
                fun(name)
            } else {
                return;
            };
            match result {
                ChannelData::CS_CONTROL_CHANNEL(data) => {
                    *(channelValuePtr as *mut f64) = data;
                }
                ChannelData::CS_STRING_CHANNEL(s) => {
                    let len = s.len();
                    let c_str = CString::new(s);
                    if raw::csoundGetChannelDatasize(csound, channelName) as usize <= len
                        && c_str.is_ok()
                    {
                        memcpy(channelValuePtr, c_str.unwrap().as_ptr() as *mut c_void, len);
                    }
                }
                _ => {}
            }
        });
    }
    pub extern "C" fn outputChannelCallback(
        csound: *mut raw::CSOUND,
        channelName: *const c_char,
        channelValuePtr: *mut c_void,
        _channelType: *const c_void,
    ) {
        catch(|| unsafe {
            let name = (CStr::from_ptr(channelName)).to_str();
            if name.is_err() {
                return;
            }
            let name = name.unwrap();
            let mut ptr = ::std::ptr::null_mut();
            let ptr: *mut *mut f64 = &mut ptr as *mut *mut _;
            let channel_type = raw::csoundGetChannelPtr(csound, ptr, channelName, 0) as u32;
            let channel_type = channel_type & raw::CSOUND_CHANNEL_TYPE_MASK as u32;
            let fun = if let Some(fun) = (*(raw::csoundGetHostData(csound) as *mut CallbackHandler))
                .callbacks
                .output_channel_cb
                .as_mut()
            {
                fun
            } else {
                return;
            };
            match channel_type {
                raw::CSOUND_CONTROL_CHANNEL => {
                    let value = *(channelValuePtr as *mut f64);
                    let data = ChannelData::CS_CONTROL_CHANNEL(value);
                    fun(name, data);
                }
                raw::CSOUND_STRING_CHANNEL => {
                    let data = ChannelData::CS_STRING_CHANNEL(
                        ptr_to_string(channelValuePtr as *const c_char)
                            .unwrap_or_else(|| "".to_owned()),
                    );
                    fun(name, data);
                }
                _ => {}
            }
        });
    }
    
    
    pub extern "C" fn midiInOpenCallback(
        csound: *mut raw::CSOUND,
        _userData: *mut *mut c_void,
        devName: *const c_char,
    ) -> c_int {
        catch(|| unsafe {
            let name = match CStr::from_ptr(devName).to_str() {
                Ok(s) => s,
                _ => return raw::CSOUND_ERROR,
            };
            if let Some(fun) = (*(raw::csoundGetHostData(csound) as *mut CallbackHandler))
                .callbacks
                .midi_in_open_cb
                .as_mut()
            {
                fun(&name);
            }
            raw::CSOUND_SUCCESS
        })
        .unwrap()
    }
    
    pub extern "C" fn midiOutOpenCallback(
        csound: *mut raw::CSOUND,
        _userData: *mut *mut c_void,
        devName: *const c_char,
    ) -> c_int {
        catch(|| unsafe {
            let name = match CStr::from_ptr(devName).to_str() {
                Ok(s) => s,
                _ => return raw::CSOUND_ERROR,
            };
            if let Some(fun) = (*(raw::csoundGetHostData(csound) as *mut CallbackHandler))
                .callbacks
                .midi_out_open_cb
                .as_mut()
            {
                fun(&name);
            }
            raw::CSOUND_SUCCESS
        })
        .unwrap()
    }
    
    pub extern "C" fn midiReadCallback(
        csound: *mut raw::CSOUND,
        _userData: *mut c_void,
        buf: *mut c_uchar,
        nbytes: c_int,
    ) -> c_int {
        catch(|| unsafe {
            let mut out = slice::from_raw_parts_mut(buf, nbytes as usize);
            if let Some(fun) = (*(raw::csoundGetHostData(csound) as *mut CallbackHandler))
                .callbacks
                .midi_read_cb
                .as_mut()
            {
                return fun(&mut out) as c_int;
            }
            -1
        })
        .unwrap()
    }
    
    #[allow(dead_code)]
    pub extern "C" fn midiWriteCallback(
        csound: *mut raw::CSOUND,
        _userData: *mut c_void,
        buf: *const u8,
        nbytes: c_int,
    ) -> c_int {
        catch(|| unsafe {
            let buffer = slice::from_raw_parts(buf, nbytes as usize);
            if let Some(fun) = (*(raw::csoundGetHostData(csound) as *mut CallbackHandler))
                .callbacks
                .midi_write_cb
                .as_mut()
            {
                return fun(&buffer) as c_int;
            }
            -1
        })
        .unwrap()
    }
    
    pub extern "C" fn midiInCloseCallback(
        csound: *mut raw::CSOUND,
        _userData: *mut c_void,
    ) -> c_int {
        catch(|| unsafe {
            if let Some(fun) = (*(raw::csoundGetHostData(csound) as *mut CallbackHandler))
                .callbacks
                .midi_in_close_cb
                .as_mut()
            {
                fun();
            }
            raw::CSOUND_SUCCESS
        })
        .unwrap()
    }
    
    pub extern "C" fn midiOutCloseCallback(
        csound: *mut raw::CSOUND,
        _userData: *mut c_void,
    ) -> c_int {
        catch(|| unsafe {
            if let Some(fun) = (*(raw::csoundGetHostData(csound) as *mut CallbackHandler))
                .callbacks
                .midi_out_close_cb
                .as_mut()
            {
                fun();
            }
            raw::CSOUND_SUCCESS
        })
        .unwrap()
    }
    pub extern "C" fn yieldCallback(csound: *mut raw::CSOUND) -> c_int {
        catch(|| unsafe {
            if let Some(fun) = (*(raw::csoundGetHostData(csound) as *mut CallbackHandler))
                .callbacks
                .yield_cb
                .as_mut()
            {
                return fun() as c_int;
            }
            0
        })
        .unwrap()
    }
}