use super::common;
use super::error_fmt;
use super::latency;
use super::ports;
use crate::audio::io::AudioIO;
use std::ffi::CString;
use std::sync::Arc;

pub use super::midi_hub::MidiHub;
pub use super::options::HwOptions;

const SIO_PLAY: u32 = 0x1;
const SIO_REC: u32 = 0x2;
const SIO_IGNORE: u32 = 0;
const SIO_SYNC: u32 = 1;

#[cfg(target_endian = "little")]
const SIO_LE_NATIVE: u32 = 1;
#[cfg(target_endian = "big")]
const SIO_LE_NATIVE: u32 = 0;

#[repr(C)]
struct SioPar {
    bits: u32,
    bps: u32,
    sig: u32,
    le: u32,
    msb: u32,
    rchan: u32,
    pchan: u32,
    rate: u32,
    bufsz: u32,
    xrun: u32,
    round: u32,
    appbufsz: u32,
    __pad: [i32; 3],
    __magic: u32,
}

enum SioHdl {}

#[link(name = "sndio")]
unsafe extern "C" {
    fn sio_initpar(par: *mut SioPar);
    fn sio_open(name: *const std::os::raw::c_char, mode: u32, nbio: i32) -> *mut SioHdl;
    fn sio_close(hdl: *mut SioHdl);
    fn sio_setpar(hdl: *mut SioHdl, par: *mut SioPar) -> i32;
    fn sio_getpar(hdl: *mut SioHdl, par: *mut SioPar) -> i32;
    fn sio_start(hdl: *mut SioHdl) -> i32;
    fn sio_stop(hdl: *mut SioHdl) -> i32;
    fn sio_read(hdl: *mut SioHdl, addr: *mut std::os::raw::c_void, nbytes: usize) -> usize;
    fn sio_write(hdl: *mut SioHdl, addr: *const std::os::raw::c_void, nbytes: usize) -> usize;
    fn sio_eof(hdl: *mut SioHdl) -> i32;
    fn sio_sun_getfd(name: *const std::os::raw::c_char, mode: u32, nbio: i32) -> i32;
    fn sio_sun_fdopen(fd: i32, mode: u32, nbio: i32) -> *mut SioHdl;
}

pub struct HwDriver {
    hdl: *mut SioHdl,
    audio_ins: Vec<Arc<AudioIO>>,
    audio_outs: Vec<Arc<AudioIO>>,
    output_gain_linear: f32,
    output_balance: f32,
    sample_rate: i32,
    period_frames: usize,
    channels_in: usize,
    channels_out: usize,
    bits: usize,
    bps: usize,
    le: bool,
    msb: bool,
    nperiods: usize,
    sync_mode: bool,
    input_latency_frames: usize,
    output_latency_frames: usize,
    capture_buffer: Vec<u8>,
    playback_buffer: Vec<u8>,
    capture_f32_buffer: Vec<f32>,
    playback_f32_buffer: Vec<f32>,
    capture_temp_i16: Vec<i16>,
    capture_temp_i32: Vec<i32>,
    playback_temp_i16: Vec<i16>,
    playback_temp_i32: Vec<i32>,
    playing: bool,
}

impl std::fmt::Debug for HwDriver {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("HwDriver")
            .field("audio_ins", &self.audio_ins.len())
            .field("audio_outs", &self.audio_outs.len())
            .field("output_gain_linear", &self.output_gain_linear)
            .field("output_balance", &self.output_balance)
            .field("sample_rate", &self.sample_rate)
            .field("period_frames", &self.period_frames)
            .field("channels_in", &self.channels_in)
            .field("channels_out", &self.channels_out)
            .field("bits", &self.bits)
            .field("bps", &self.bps)
            .field("le", &self.le)
            .field("msb", &self.msb)
            .field("nperiods", &self.nperiods)
            .field("sync_mode", &self.sync_mode)
            .field("input_latency_frames", &self.input_latency_frames)
            .field("output_latency_frames", &self.output_latency_frames)
            .finish()
    }
}

impl Drop for HwDriver {
    fn drop(&mut self) {
        if self.hdl.is_null() {
            return;
        }
        unsafe {
            let _ = sio_stop(self.hdl);
            sio_close(self.hdl);
        }
        self.hdl = std::ptr::null_mut();
    }
}

impl Default for HwOptions {
    fn default() -> Self {
        Self {
            exclusive: false,
            period_frames: 1024,
            nperiods: 2,
            ignore_hwbuf: false,
            sync_mode: false,
            input_latency_frames: 0,
            output_latency_frames: 0,
        }
    }
}

impl HwDriver {
    pub fn new_with_options(
        device: &str,
        rate: i32,
        bits: i32,
        options: HwOptions,
    ) -> Result<Self, String> {
        let requested_device = if device.trim().is_empty() {
            "default"
        } else {
            device
        };
        let name = CString::new(requested_device)
            .map_err(|e| error_fmt::backend_open_error("sndio", "duplex", requested_device, e))?;

        let mode = SIO_PLAY | SIO_REC;
        let hdl = if requested_device.starts_with("/dev/audio") {
            let fd = unsafe { sio_sun_getfd(name.as_ptr(), mode, 0) };
            if fd < 0 {
                std::ptr::null_mut()
            } else {
                unsafe { sio_sun_fdopen(fd, mode, 0) }
            }
        } else {
            unsafe { sio_open(name.as_ptr(), mode, 0) }
        };
        if hdl.is_null() {
            return Err(error_fmt::backend_open_error(
                "sndio",
                "duplex",
                requested_device,
                "sio_open returned null",
            ));
        }

        let mut par = Self::desired_par(rate, bits, options);
        let configured = unsafe { sio_setpar(hdl, &mut par) };
        if configured != 1 {
            unsafe {
                sio_close(hdl);
            }
            return Err(error_fmt::backend_io_error(
                "sndio",
                "duplex",
                "sio_setpar failed",
            ));
        }

        let got = unsafe { sio_getpar(hdl, &mut par) };
        if got != 1 {
            unsafe {
                sio_close(hdl);
            }
            return Err(error_fmt::backend_io_error(
                "sndio",
                "duplex",
                "sio_getpar failed",
            ));
        }

        let started = unsafe { sio_start(hdl) };
        if started != 1 {
            unsafe {
                sio_close(hdl);
            }
            return Err(error_fmt::backend_io_error(
                "sndio",
                "duplex",
                "sio_start failed",
            ));
        }

        let sample_rate = par.rate.max(1) as i32;
        let period_frames = par.round.max(1) as usize;
        let channels_in = par.rchan.max(1) as usize;
        let channels_out = par.pchan.max(1) as usize;
        let bps = par.bps.max(1) as usize;
        let bits = par.bits.max(8) as usize;
        let le = par.le != 0;
        let msb = par.msb != 0;

        let audio_ins: Vec<Arc<AudioIO>> = (0..channels_in)
            .map(|_| Arc::new(AudioIO::new(period_frames)))
            .collect();
        let audio_outs: Vec<Arc<AudioIO>> = (0..channels_out)
            .map(|_| Arc::new(AudioIO::new(period_frames)))
            .collect();

        let capture_buffer = vec![0_u8; period_frames * channels_in * bps];
        let playback_buffer = vec![0_u8; period_frames * channels_out * bps];
        let capture_f32_buffer = vec![0.0_f32; period_frames * channels_in];
        let playback_f32_buffer = vec![0.0_f32; period_frames * channels_out];

        Ok(Self {
            hdl,
            audio_ins,
            audio_outs,
            output_gain_linear: 1.0,
            output_balance: 0.0,
            sample_rate,
            period_frames,
            channels_in,
            channels_out,
            bits,
            bps,
            le,
            msb,
            nperiods: options.nperiods.max(1),
            sync_mode: options.sync_mode,
            input_latency_frames: options.input_latency_frames,
            output_latency_frames: options.output_latency_frames,
            capture_buffer,
            playback_buffer,
            capture_f32_buffer,
            playback_f32_buffer,
            capture_temp_i16: vec![0; period_frames * channels_in],
            capture_temp_i32: vec![0; period_frames * channels_in],
            playback_temp_i16: vec![0; period_frames * channels_out],
            playback_temp_i32: vec![0; period_frames * channels_out],
            playing: false,
        })
    }

    pub fn input_channels(&self) -> usize {
        self.channels_in
    }

    pub fn output_channels(&self) -> usize {
        self.channels_out
    }

    pub fn sample_rate(&self) -> i32 {
        self.sample_rate
    }

    pub fn cycle_samples(&self) -> usize {
        self.period_frames
    }

    pub fn input_port(&self, idx: usize) -> Option<Arc<AudioIO>> {
        self.audio_ins.get(idx).cloned()
    }

    pub fn output_port(&self, idx: usize) -> Option<Arc<AudioIO>> {
        self.audio_outs.get(idx).cloned()
    }

    pub fn set_output_gain_balance(&mut self, gain: f32, balance: f32) {
        self.output_gain_linear = gain.max(0.0);
        self.output_balance = balance.clamp(-1.0, 1.0);
    }

    pub fn set_playing(&mut self, playing: bool) {
        self.playing = playing;
    }

    pub fn output_meter_linear(&self, gain: f32, balance: f32) -> Vec<f32> {
        common::output_meter_linear(&self.audio_outs, gain, balance)
    }

    pub fn latency_ranges(&self) -> ((usize, usize), (usize, usize)) {
        latency::latency_ranges(
            self.cycle_samples(),
            self.nperiods,
            self.sync_mode,
            self.input_latency_frames,
            self.output_latency_frames,
        )
    }

    pub fn channel(&mut self) -> SndioChannel<'_> {
        SndioChannel { driver: self }
    }

    fn desired_par(rate: i32, bits: i32, options: HwOptions) -> SioPar {
        let effective_bits = normalize_bits(bits) as u32;
        let bps = default_bps(effective_bits) as u32;
        let period = options.period_frames.max(1) as u32;
        let appbuf = period.saturating_mul(options.nperiods.max(1) as u32);
        let mut par = unsafe { std::mem::zeroed::<SioPar>() };
        unsafe {
            sio_initpar(&mut par);
        }
        par.bits = effective_bits;
        par.bps = bps;
        par.sig = 1;
        par.le = SIO_LE_NATIVE;
        par.msb = 1;
        par.rchan = 2;
        par.pchan = 2;
        par.rate = rate.max(1) as u32;
        par.round = period;
        par.appbufsz = appbuf;
        par.bufsz = appbuf;
        par.xrun = if options.sync_mode {
            SIO_SYNC
        } else {
            SIO_IGNORE
        };
        par
    }

    fn run_cycle_inner(&mut self) -> Result<(), String> {
        Self::read_exact(self.hdl, &mut self.capture_buffer)?;

        let bits = self.bits;
        let bps = self.bps;
        let le = self.le;
        let msb = self.msb;
        let channels_in = self.channels_in;
        let channels_out = self.channels_out;
        let frames = self.period_frames;
        let total_in = frames.saturating_mul(channels_in);
        let total_out = frames.saturating_mul(channels_out);

        match bps {
            1 if bits == 8 => {
                let src = unsafe {
                    std::slice::from_raw_parts(self.capture_buffer.as_ptr() as *const i8, total_in)
                };
                crate::simd::convert_i8_to_f32(
                    src,
                    &mut self.capture_f32_buffer[..total_in],
                    1.0 / 128.0,
                );
                ports::fill_ports_from_interleaved_buffer(
                    &self.audio_ins,
                    frames,
                    false,
                    &self.capture_f32_buffer[..total_in],
                    channels_in,
                );
            }
            2 if bits == 16 => {
                if !le {
                    for i in 0..total_in {
                        let o = i * 2;
                        self.capture_temp_i16[i] = i16::from_be_bytes([
                            self.capture_buffer[o],
                            self.capture_buffer[o + 1],
                        ]);
                    }
                } else {
                    let src = unsafe {
                        std::slice::from_raw_parts(
                            self.capture_buffer.as_ptr() as *const i16,
                            total_in,
                        )
                    };
                    self.capture_temp_i16[..total_in].copy_from_slice(src);
                }
                crate::simd::convert_i16_to_f32(
                    &self.capture_temp_i16[..total_in],
                    &mut self.capture_f32_buffer[..total_in],
                    1.0 / 32768.0,
                );
                ports::fill_ports_from_interleaved_buffer(
                    &self.audio_ins,
                    frames,
                    false,
                    &self.capture_f32_buffer[..total_in],
                    channels_in,
                );
            }
            4 if bits == 32 => {
                if !le {
                    for i in 0..total_in {
                        let o = i * 4;
                        self.capture_temp_i32[i] = i32::from_be_bytes([
                            self.capture_buffer[o],
                            self.capture_buffer[o + 1],
                            self.capture_buffer[o + 2],
                            self.capture_buffer[o + 3],
                        ]);
                    }
                } else {
                    let src = unsafe {
                        std::slice::from_raw_parts(
                            self.capture_buffer.as_ptr() as *const i32,
                            total_in,
                        )
                    };
                    self.capture_temp_i32[..total_in].copy_from_slice(src);
                }
                crate::simd::convert_i32_to_f32(
                    &self.capture_temp_i32[..total_in],
                    &mut self.capture_f32_buffer[..total_in],
                    1.0 / 2147483648.0,
                );
                ports::fill_ports_from_interleaved_buffer(
                    &self.audio_ins,
                    frames,
                    false,
                    &self.capture_f32_buffer[..total_in],
                    channels_in,
                );
            }
            4 if bits == 24 && msb => {
                if !le {
                    for i in 0..total_in {
                        let o = i * 4;
                        self.capture_temp_i32[i] = i32::from_be_bytes([
                            self.capture_buffer[o],
                            self.capture_buffer[o + 1],
                            self.capture_buffer[o + 2],
                            self.capture_buffer[o + 3],
                        ]);
                    }
                } else {
                    let src = unsafe {
                        std::slice::from_raw_parts(
                            self.capture_buffer.as_ptr() as *const i32,
                            total_in,
                        )
                    };
                    self.capture_temp_i32[..total_in].copy_from_slice(src);
                }
                for s in &mut self.capture_temp_i32[..total_in] {
                    *s >>= 8;
                }
                crate::simd::convert_i24_to_f32(
                    &self.capture_temp_i32[..total_in],
                    &mut self.capture_f32_buffer[..total_in],
                    1.0 / 8388608.0,
                );
                ports::fill_ports_from_interleaved_buffer(
                    &self.audio_ins,
                    frames,
                    false,
                    &self.capture_f32_buffer[..total_in],
                    channels_in,
                );
            }
            _ => {
                ports::fill_ports_from_interleaved(&self.audio_ins, frames, false, |ch, frame| {
                    let idx = (frame * channels_in + ch) * bps;
                    decode_sample(&self.capture_buffer[idx..idx + bps], bits, bps, le, msb)
                });
            }
        }

        self.playback_buffer.fill(0);
        match bps {
            1 if bits == 8 => {
                self.playback_f32_buffer[..total_out].fill(0.0);
                ports::write_interleaved_from_ports(
                    &self.audio_outs,
                    frames,
                    self.output_gain_linear,
                    self.output_balance,
                    false,
                    |ch, frame, sample| {
                        let idx = frame * channels_out + ch;
                        if let Some(dst) = self.playback_f32_buffer.get_mut(idx) {
                            *dst = sample;
                        }
                    },
                );
                crate::simd::convert_f32_to_i8(
                    &self.playback_f32_buffer[..total_out],
                    unsafe {
                        std::slice::from_raw_parts_mut(
                            self.playback_buffer.as_mut_ptr() as *mut i8,
                            total_out,
                        )
                    },
                    127.0,
                );
            }
            2 if bits == 16 => {
                self.playback_f32_buffer[..total_out].fill(0.0);
                ports::write_interleaved_from_ports(
                    &self.audio_outs,
                    frames,
                    self.output_gain_linear,
                    self.output_balance,
                    false,
                    |ch, frame, sample| {
                        let idx = frame * channels_out + ch;
                        if let Some(dst) = self.playback_f32_buffer.get_mut(idx) {
                            *dst = sample;
                        }
                    },
                );
                crate::simd::convert_f32_to_i16(
                    &self.playback_f32_buffer[..total_out],
                    &mut self.playback_temp_i16[..total_out],
                    32767.0,
                );
                if !le {
                    for i in 0..total_out {
                        let o = i * 2;
                        let bytes = self.playback_temp_i16[i].to_be_bytes();
                        self.playback_buffer[o] = bytes[0];
                        self.playback_buffer[o + 1] = bytes[1];
                    }
                } else {
                    unsafe {
                        std::ptr::copy_nonoverlapping(
                            self.playback_temp_i16.as_ptr(),
                            self.playback_buffer.as_mut_ptr() as *mut i16,
                            total_out,
                        );
                    }
                }
            }
            4 if bits == 32 => {
                self.playback_f32_buffer[..total_out].fill(0.0);
                ports::write_interleaved_from_ports(
                    &self.audio_outs,
                    frames,
                    self.output_gain_linear,
                    self.output_balance,
                    false,
                    |ch, frame, sample| {
                        let idx = frame * channels_out + ch;
                        if let Some(dst) = self.playback_f32_buffer.get_mut(idx) {
                            *dst = sample;
                        }
                    },
                );
                crate::simd::convert_f32_to_i32(
                    &self.playback_f32_buffer[..total_out],
                    &mut self.playback_temp_i32[..total_out],
                    2147483647.0,
                );
                if !le {
                    for i in 0..total_out {
                        let o = i * 4;
                        let bytes = self.playback_temp_i32[i].to_be_bytes();
                        self.playback_buffer[o] = bytes[0];
                        self.playback_buffer[o + 1] = bytes[1];
                        self.playback_buffer[o + 2] = bytes[2];
                        self.playback_buffer[o + 3] = bytes[3];
                    }
                } else {
                    unsafe {
                        std::ptr::copy_nonoverlapping(
                            self.playback_temp_i32.as_ptr(),
                            self.playback_buffer.as_mut_ptr() as *mut i32,
                            total_out,
                        );
                    }
                }
            }
            4 if bits == 24 && msb => {
                self.playback_f32_buffer[..total_out].fill(0.0);
                ports::write_interleaved_from_ports(
                    &self.audio_outs,
                    frames,
                    self.output_gain_linear,
                    self.output_balance,
                    false,
                    |ch, frame, sample| {
                        let idx = frame * channels_out + ch;
                        if let Some(dst) = self.playback_f32_buffer.get_mut(idx) {
                            *dst = sample;
                        }
                    },
                );
                crate::simd::convert_f32_to_i24(
                    &self.playback_f32_buffer[..total_out],
                    &mut self.playback_temp_i32[..total_out],
                    8388607.0,
                );
                for s in &mut self.playback_temp_i32[..total_out] {
                    *s <<= 8;
                }
                if !le {
                    for i in 0..total_out {
                        let o = i * 4;
                        let bytes = self.playback_temp_i32[i].to_be_bytes();
                        self.playback_buffer[o] = bytes[0];
                        self.playback_buffer[o + 1] = bytes[1];
                        self.playback_buffer[o + 2] = bytes[2];
                        self.playback_buffer[o + 3] = bytes[3];
                    }
                } else {
                    unsafe {
                        std::ptr::copy_nonoverlapping(
                            self.playback_temp_i32.as_ptr(),
                            self.playback_buffer.as_mut_ptr() as *mut i32,
                            total_out,
                        );
                    }
                }
            }
            _ => {
                ports::write_interleaved_from_ports(
                    &self.audio_outs,
                    frames,
                    self.output_gain_linear,
                    self.output_balance,
                    false,
                    |ch, frame, sample| {
                        let idx = (frame * channels_out + ch) * bps;
                        encode_sample(
                            sample,
                            bits,
                            bps,
                            le,
                            msb,
                            &mut self.playback_buffer[idx..idx + bps],
                        );
                    },
                );
            }
        }

        Self::write_exact(self.hdl, &self.playback_buffer)
    }

    fn read_exact(hdl: *mut SioHdl, out: &mut [u8]) -> Result<(), String> {
        let mut offset = 0;
        while offset < out.len() {
            let got = unsafe {
                sio_read(
                    hdl,
                    out[offset..].as_mut_ptr().cast::<std::os::raw::c_void>(),
                    out.len() - offset,
                )
            };
            if got == 0 {
                let eof = unsafe { sio_eof(hdl) } != 0;
                return Err(error_fmt::backend_io_error(
                    "sndio",
                    "capture",
                    if eof { "stream closed" } else { "short read" },
                ));
            }
            offset += got;
        }
        Ok(())
    }

    fn write_exact(hdl: *mut SioHdl, data: &[u8]) -> Result<(), String> {
        let mut offset = 0;
        while offset < data.len() {
            let wrote = unsafe {
                sio_write(
                    hdl,
                    data[offset..].as_ptr().cast::<std::os::raw::c_void>(),
                    data.len() - offset,
                )
            };
            if wrote == 0 {
                let eof = unsafe { sio_eof(hdl) } != 0;
                return Err(error_fmt::backend_io_error(
                    "sndio",
                    "playback",
                    if eof { "stream closed" } else { "short write" },
                ));
            }
            offset += wrote;
        }
        Ok(())
    }
}

pub struct SndioChannel<'a> {
    driver: &'a mut HwDriver,
}

impl<'a> SndioChannel<'a> {
    pub fn run_cycle(&mut self) -> Result<(), String> {
        self.driver.run_cycle_inner()
    }

    pub fn run_assist_step(&mut self) -> Result<bool, String> {
        Ok(false)
    }
}

fn normalize_bits(bits: i32) -> usize {
    match bits {
        8 => 8,
        16 => 16,
        24 => 24,
        32 => 32,
        _ => 32,
    }
}

fn default_bps(bits: u32) -> usize {
    if bits <= 8 {
        1
    } else if bits <= 16 {
        2
    } else {
        4
    }
}

fn decode_sample(src: &[u8], bits: usize, bps: usize, le: bool, msb: bool) -> f32 {
    let container_bits = bps.saturating_mul(8);
    let mut value = decode_container(src, bps, le);

    if bits < container_bits && msb {
        value >>= (container_bits - bits) as u32;
    }
    if bits < 32 {
        let shift = (32 - bits) as u32;
        value = (value << shift) >> shift;
    }

    let denom = if bits >= 32 {
        2_147_483_648.0_f32
    } else {
        ((1_i64 << (bits - 1)) as f32).max(1.0)
    };
    (value as f32 / denom).clamp(-1.0, 1.0)
}

fn encode_sample(sample: f32, bits: usize, bps: usize, le: bool, msb: bool, dst: &mut [u8]) {
    let container_bits = bps.saturating_mul(8);
    let max = if bits >= 32 {
        i32::MAX as i64
    } else {
        (1_i64 << (bits - 1)) - 1
    };
    let min = if bits >= 32 {
        i32::MIN as i64
    } else {
        -(1_i64 << (bits - 1))
    };

    let mut value = (sample.clamp(-1.0, 1.0) * max as f32).round() as i64;
    value = value.clamp(min, max);

    let mut stored = value as i32;
    if bits < container_bits && msb {
        stored <<= (container_bits - bits) as u32;
    }
    encode_container(stored, bps, le, dst);
}

fn decode_container(src: &[u8], bps: usize, le: bool) -> i32 {
    match bps {
        1 => i8::from_ne_bytes([src[0]]) as i32,
        2 => {
            let v = [src[0], src[1]];
            if le {
                i16::from_le_bytes(v) as i32
            } else {
                i16::from_be_bytes(v) as i32
            }
        }
        4 => {
            let v = [src[0], src[1], src[2], src[3]];
            if le {
                i32::from_le_bytes(v)
            } else {
                i32::from_be_bytes(v)
            }
        }
        _ => 0,
    }
}

fn encode_container(value: i32, bps: usize, le: bool, dst: &mut [u8]) {
    match bps {
        1 => {
            dst[0] = value as i8 as u8;
        }
        2 => {
            let bytes = if le {
                (value as i16).to_le_bytes()
            } else {
                (value as i16).to_be_bytes()
            };
            dst[0] = bytes[0];
            dst[1] = bytes[1];
        }
        4 => {
            let bytes = if le {
                value.to_le_bytes()
            } else {
                value.to_be_bytes()
            };
            dst[0] = bytes[0];
            dst[1] = bytes[1];
            dst[2] = bytes[2];
            dst[3] = bytes[3];
        }
        _ => {}
    }
}

crate::impl_hw_worker_traits_for_driver!(HwDriver);
crate::impl_hw_device_for_driver!(HwDriver);
crate::impl_hw_midi_hub_traits!(MidiHub);

unsafe impl Send for HwDriver {}
unsafe impl Sync for HwDriver {}
