use std::ffi::c_uint;

type RubberBandOptions = i32;
type RubberBandLiveState = *mut RubberBandLiveStateOpaque;

#[repr(C)]
struct RubberBandLiveStateOpaque {
    _private: [u8; 0],
}

const RUBBERBAND_OPTION_PROCESS_REAL_TIME: RubberBandOptions = 0x00000001;
const RUBBERBAND_OPTION_THREADING_NEVER: RubberBandOptions = 0x00010000;
const RUBBERBAND_OPTION_WINDOW_SHORT: RubberBandOptions = 0x00100000;
const RUBBERBAND_OPTION_FORMANT_PRESERVED: RubberBandOptions = 0x01000000;
const RUBBERBAND_OPTION_PITCH_HIGH_CONSISTENCY: RubberBandOptions = 0x04000000;
const RUBBERBAND_OPTION_CHANNELS_TOGETHER: RubberBandOptions = 0x10000000;

#[link(name = "rubberband")]
unsafe extern "C" {
    fn rubberband_live_new(
        sample_rate: c_uint,
        channels: c_uint,
        options: RubberBandOptions,
    ) -> RubberBandLiveState;
    fn rubberband_live_delete(state: RubberBandLiveState);
    fn rubberband_live_reset(state: RubberBandLiveState);
    fn rubberband_live_set_pitch_scale(state: RubberBandLiveState, scale: f64);
    fn rubberband_live_set_formant_option(state: RubberBandLiveState, options: RubberBandOptions);
    fn rubberband_live_get_start_delay(state: RubberBandLiveState) -> c_uint;
    fn rubberband_live_get_block_size(state: RubberBandLiveState) -> c_uint;
    fn rubberband_live_shift(
        state: RubberBandLiveState,
        input: *const *const f32,
        output: *const *mut f32,
    );
}

#[derive(Debug)]
pub struct LivePitchShifter {
    state: RubberBandLiveState,
    block_size: usize,
    start_delay_remaining: usize,
    pending_offset: usize,
    pending_len: usize,
    next_input_frame: usize,
    next_output_frame: usize,
    formant_preserved: bool,
    input: Vec<Vec<f32>>,
    output: Vec<Vec<f32>>,
    input_ptrs: Vec<*const f32>,
    output_ptrs: Vec<*mut f32>,
}

unsafe impl Send for LivePitchShifter {}

impl LivePitchShifter {
    pub fn new(
        sample_rate: usize,
        channels: usize,
        formant_preserved: bool,
    ) -> Result<Self, String> {
        let mut options = RUBBERBAND_OPTION_PROCESS_REAL_TIME
            | RUBBERBAND_OPTION_THREADING_NEVER
            | RUBBERBAND_OPTION_WINDOW_SHORT
            | RUBBERBAND_OPTION_PITCH_HIGH_CONSISTENCY;
        if channels > 1 {
            options |= RUBBERBAND_OPTION_CHANNELS_TOGETHER;
        }
        if formant_preserved {
            options |= RUBBERBAND_OPTION_FORMANT_PRESERVED;
        }
        let state =
            unsafe { rubberband_live_new(sample_rate as c_uint, channels as c_uint, options) };
        if state.is_null() {
            return Err("Failed to initialize Rubber Band live shifter".to_string());
        }
        let block_size = unsafe { rubberband_live_get_block_size(state) as usize }.max(1);
        let start_delay_remaining = unsafe { rubberband_live_get_start_delay(state) as usize };
        let input = vec![vec![0.0; block_size]; channels.max(1)];
        let output = vec![vec![0.0; block_size]; channels.max(1)];
        let input_ptrs = input.iter().map(|channel| channel.as_ptr()).collect();
        let output_ptrs = output
            .iter()
            .map(|channel| channel.as_ptr() as *mut f32)
            .collect();
        Ok(Self {
            state,
            block_size,
            start_delay_remaining,
            pending_offset: 0,
            pending_len: 0,
            next_input_frame: 0,
            next_output_frame: 0,
            formant_preserved,
            input,
            output,
            input_ptrs,
            output_ptrs,
        })
    }

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

    pub fn reset(&mut self, output_frame: usize) {
        unsafe { rubberband_live_reset(self.state) };
        self.start_delay_remaining =
            unsafe { rubberband_live_get_start_delay(self.state) as usize };
        self.pending_offset = 0;
        self.pending_len = 0;
        self.next_input_frame = output_frame;
        self.next_output_frame = output_frame;
    }

    pub fn set_formant_preserved(&mut self, formant_preserved: bool) {
        if self.formant_preserved == formant_preserved {
            return;
        }
        self.formant_preserved = formant_preserved;
        let option = if formant_preserved {
            RUBBERBAND_OPTION_FORMANT_PRESERVED
        } else {
            0
        };
        unsafe { rubberband_live_set_formant_option(self.state, option) };
    }

    pub fn render<F>(
        &mut self,
        request_start_frame: usize,
        frames: usize,
        mut fill_input: F,
    ) -> Vec<Vec<f32>>
    where
        F: FnMut(usize, &mut [Vec<f32>]) -> f64,
    {
        let channels = self.output.len();
        let mut rendered = vec![vec![0.0; frames]; channels];
        if self.next_output_frame != request_start_frame {
            self.reset(request_start_frame);
        }

        let mut written = 0usize;
        while written < frames {
            if self.pending_offset < self.pending_len {
                let available = self.pending_len - self.pending_offset;
                let copy_len = available.min(frames - written);
                for (dst, src) in rendered.iter_mut().zip(self.output.iter()) {
                    dst[written..written + copy_len]
                        .copy_from_slice(&src[self.pending_offset..self.pending_offset + copy_len]);
                }
                self.pending_offset += copy_len;
                self.next_output_frame = self.next_output_frame.saturating_add(copy_len);
                written += copy_len;
                continue;
            }

            let pitch_scale = fill_input(self.next_input_frame, &mut self.input).max(0.01);
            unsafe {
                rubberband_live_set_pitch_scale(self.state, pitch_scale);
                rubberband_live_shift(
                    self.state,
                    self.input_ptrs.as_ptr(),
                    self.output_ptrs.as_ptr(),
                );
            }
            self.next_input_frame = self.next_input_frame.saturating_add(self.block_size);
            let drop = self.start_delay_remaining.min(self.block_size);
            self.start_delay_remaining = self.start_delay_remaining.saturating_sub(drop);
            self.pending_offset = drop;
            self.pending_len = self.block_size;
        }

        rendered
    }
}

impl Drop for LivePitchShifter {
    fn drop(&mut self) {
        if !self.state.is_null() {
            unsafe { rubberband_live_delete(self.state) };
        }
    }
}
