use core::{
    cmp::Ordering,
    ffi::{c_int, c_void},
};

struct Array {
    base: *mut c_void,
    size: usize,
    nmemb: usize,
}

impl Array {
    unsafe fn new(base: *mut c_void, nmemb: usize, size: usize) -> Option<Self> {
        if nmemb == 0 || size == 0 || base.is_null() {
            return None;
        }
        Some(Self { base, nmemb, size })
    }

    fn element(&mut self, i: usize) -> *mut c_void {
        if i >= self.nmemb {
            panic!("i = {}, nmemb = {}", i, self.nmemb);
        }
        unsafe { self.base.add(i * self.size) }
    }

    fn partition<F: Fn(*mut c_void, *mut c_void) -> Ordering>(
        &mut self,
        lower: usize,
        upper: usize,
        cmp: &F,
    ) -> usize {
        if lower >= upper {
            panic!()
        }
        let pivot_ptr = self.element(lower);
        let mut i = lower;
        let mut j = upper;

        while i < j {
            while i < upper && cmp(self.element(i), pivot_ptr).is_le() {
                i += 1;
            }
            while j > lower && cmp(self.element(j), pivot_ptr).is_gt() {
                j -= 1;
            }

            if i < j {
                self.swap(i, j);
            }
        }
        self.swap(lower, j);
        j
    }

    fn swap(&mut self, i: usize, j: usize) {
        let el_i = self.element(i) as *mut u8;
        let el_j = self.element(j) as *mut u8;
        unsafe {
            for i in 0..self.size {
                core::ptr::swap(el_i.add(i), el_j.add(i));
            }
        }
    }

    fn sort_inner<F: Fn(*mut c_void, *mut c_void) -> Ordering>(
        &mut self,
        lower: usize,
        upper: usize,
        cmp: &F,
    ) {
        if lower < upper {
            let partition = self.partition(lower, upper, cmp);

            if partition != 0 {
                self.sort_inner(lower, partition - 1, cmp);
            }
            self.sort_inner(partition + 1, upper, cmp);
        }
    }

    fn sort<F: Fn(*mut c_void, *mut c_void) -> Ordering>(&mut self, cmp: &F) {
        if self.nmemb != 0 {
            self.sort_inner(0, self.nmemb - 1, cmp);
        }
    }
}

pub type qsort_compar_fn_t = extern "C" fn(*const c_void, *const c_void) -> c_int;
pub type qsort_compar_r_fn_t = extern "C" fn(*const c_void, *const c_void, *mut c_void) -> c_int;

pub type bsearch_compar_fn_t = extern "C" fn(*const c_void, *const c_void) -> c_int;

#[no_mangle]
unsafe extern "C" fn bsearch(
    _key: *const c_void,
    _base: *const c_void,
    _nmemb: usize,
    _size: usize,
    _compar: bsearch_compar_fn_t,
) -> *mut c_void {
    todo!()
}

#[no_mangle]
unsafe extern "C" fn qsort(
    base: *mut c_void,
    nmemb: usize,
    size: usize,
    compar: qsort_compar_fn_t,
) {
    let Some(mut array) = Array::new(base, nmemb, size) else {
        return;
    };

    array.sort(&|a, b| compar(a, b).cmp(&0));
}

#[no_mangle]
unsafe extern "C" fn qsort_r(
    base: *mut c_void,
    nmemb: usize,
    size: usize,
    compar: qsort_compar_r_fn_t,
    arg: *mut c_void,
) {
    let Some(mut array) = Array::new(base, nmemb, size) else {
        return;
    };

    array.sort(&|a, b| compar(a, b, arg).cmp(&0));
}

// int           getsubopt(char **, char *const *, char **);
