use core::{
    cmp::Ordering,
    ffi::{c_char, c_int},
    ptr::{null_mut, NonNull},
};

use crate::{
    allocator,
    error::CPtrResult,
    headers::{errno::Errno, locale::locale_t, signal::signal_string},
};

use super::mem::{memcpy, mempcpy, memset};

#[no_mangle]
unsafe extern "C" fn stpcpy(dst: *mut c_char, src: *const c_char) -> *mut c_char {
    if dst.is_null() || src.is_null() {
        panic!();
    }
    let ptr = mempcpy(dst.cast(), src.cast(), strlen(src)).cast::<c_char>();
    *ptr = 0;
    ptr
}

#[no_mangle]
unsafe extern "C" fn stpncpy(dst: *mut c_char, src: *const c_char, n: usize) -> *mut c_char {
    if dst.is_null() || src.is_null() {
        panic!();
    }
    let dlen = strnlen(src.cast(), n);
    let r = mempcpy(dst.cast(), src.cast(), n);
    memset(r.cast(), 0, n - dlen).cast()
}

#[no_mangle]
unsafe extern "C" fn strcat(dst: *mut c_char, src: *const c_char) -> *mut c_char {
    if dst.is_null() {
        panic!();
    }
    stpcpy(dst.add(strlen(dst)), src);
    dst
}

#[no_mangle]
unsafe extern "C" fn strchr(mut s: *const c_char, c: c_int) -> *mut c_char {
    if s.is_null() {
        panic!();
    }

    loop {
        if *s == c as _ {
            return s.cast_mut();
        }

        if *s == 0 {
            break;
        }

        s = s.add(1);
    }

    null_mut()
}

#[no_mangle]
unsafe extern "C" fn strcmp(a: *const c_char, b: *const c_char) -> c_int {
    strncmp(a, b, usize::MAX)
}

#[no_mangle]
pub unsafe extern "C" fn strcpy(dst: *mut c_char, src: *const c_char) -> *mut c_char {
    let mut i = 0;
    loop {
        let c = src.add(i).read();
        dst.add(i).write(c);
        if c == 0 {
            break;
        }
        i += 1;
    }
    dst
}

#[no_mangle]
unsafe extern "C" fn strcspn(mut s: *const c_char, reject: *const c_char) -> usize {
    if s.is_null() || reject.is_null() {
        panic!();
    }
    let mut n = 0;
    while *s != 0 {
        if !strchr(reject, *s as _).is_null() {
            return n;
        }
        n += 1;
        s = s.add(1);
    }
    n
}

#[no_mangle]
unsafe extern "C" fn strdup(s: *const c_char) -> CPtrResult<c_char> {
    let len = strlen(s);
    let data = allocator::c_alloc(len + 1, 1, false)?;
    memcpy(data.cast().as_ptr(), s.cast(), len + 1);
    CPtrResult::success(data.cast())
}

unsafe fn strerror_inner(e: Errno) -> *const c_char {
    e.to_c_str().as_ptr()
}

#[no_mangle]
unsafe extern "C" fn strerror(e: Errno) -> *mut c_char {
    static mut BUF: [c_char; 128] = [0; 128];
    #[allow(static_mut_refs)]
    strerror_r(e, BUF.as_mut_ptr(), BUF.len())
}

#[no_mangle]
unsafe extern "C" fn strerror_r(e: Errno, buf: *mut c_char, n: usize) -> *mut c_char {
    let source = strerror_inner(e);
    strncpy(buf, source, n)
}

#[no_mangle]
pub unsafe extern "C" fn strlen(s: *const c_char) -> usize {
    compiler_builtins::mem::strlen(s.cast())
}

#[no_mangle]
unsafe extern "C" fn strncat(dst: *mut c_char, src: *const c_char, n: usize) -> *mut c_char {
    if dst.is_null() || src.is_null() {
        panic!();
    }
    let len = strnlen(src, n);
    let ptr = dst.add(strlen(dst));
    let ptr = mempcpy(ptr.cast(), src.cast(), len).cast::<c_char>();
    *ptr = 0;

    dst
}

#[no_mangle]
unsafe extern "C" fn strncmp(mut a: *const c_char, mut b: *const c_char, mut n: usize) -> c_int {
    if a.is_null() {
        panic!();
    }
    if b.is_null() {
        panic!();
    }
    if a == b {
        return 0;
    }

    while n != 0 {
        match Ord::cmp(&*a, &*b) {
            Ordering::Less => return -1,
            Ordering::Greater => return 1,
            Ordering::Equal => (),
        }

        if *a == 0 {
            break;
        }

        a = a.add(1);
        b = b.add(1);
        n -= 1;
    }

    0
}

#[no_mangle]
unsafe extern "C" fn strncpy(dst: *mut c_char, src: *const c_char, n: usize) -> *mut c_char {
    if dst.is_null() || src.is_null() {
        panic!();
    }
    let mut i = 0;
    while i != n {
        let c = *src.add(i);
        *dst.add(i) = c;
        if c == 0 {
            break;
        }
        i += 1;
    }
    dst
    // stpncpy(dst, src, n);
    // dst
}

#[no_mangle]
unsafe extern "C" fn strndup(s: *const c_char, n: usize) -> CPtrResult<c_char> {
    let src = NonNull::new(s.cast_mut()).unwrap();
    let len = strnlen(s, n);
    let dst = allocator::c_alloc(len + 1, 1, false)?.cast();
    dst.copy_from_nonoverlapping(src, len);
    dst.add(len).write(0);
    CPtrResult::success(dst)
}

#[no_mangle]
unsafe extern "C" fn strnlen(s: *const c_char, n: usize) -> usize {
    if s.is_null() {
        panic!();
    }
    for i in 0..n {
        if *s.add(i) == 0 {
            return i;
        }
    }

    n
}

#[no_mangle]
unsafe extern "C" fn strpbrk(mut a: *const c_char, b: *const c_char) -> *mut c_char {
    if a.is_null() || b.is_null() {
        return null_mut();
    }

    loop {
        let c = *a;

        if c == 0 {
            break;
        }

        if !strchr(b, c as _).is_null() {
            return a.cast_mut();
        }

        a = a.add(1);
    }

    null_mut()
}

#[no_mangle]
unsafe extern "C" fn strrchr(a: *const c_char, c: c_int) -> *mut c_char {
    if a.is_null() {
        return null_mut();
    }

    let n = strnlen(a, usize::MAX);
    for i in (0..n).rev() {
        if *a.add(i) == c as _ {
            return a.add(i).cast_mut();
        }
    }

    null_mut()
}

#[no_mangle]
unsafe extern "C" fn strsignal(signum: c_int) -> *mut c_char {
    match signal_string(signum) {
        Some(name) => name.as_ptr().cast_mut(),
        None => null_mut(),
    }
}

#[no_mangle]
unsafe extern "C" fn strspn(mut s: *const c_char, accept: *const c_char) -> usize {
    if s.is_null() || accept.is_null() {
        panic!();
    }
    let mut n = 0;
    while *s != 0 {
        if strchr(accept, *s as _).is_null() {
            return n;
        }
        n += 1;
        s = s.add(1);
    }
    n
}

#[no_mangle]
unsafe extern "C" fn strstr(mut a: *const c_char, b: *const c_char) -> *mut c_char {
    if a.is_null() || b.is_null() {
        panic!();
    }
    let n = strnlen(b, usize::MAX);
    if *a == 0 && *b == 0 {
        return a.cast_mut();
    }

    while *a != 0 {
        if strncmp(a, b, n) == 0 {
            return a.cast_mut();
        }
        a = a.add(1);
    }

    null_mut()
}

#[no_mangle]
unsafe extern "C" fn strtok(str: *mut c_char, delim: *const c_char) -> *mut c_char {
    static mut STRTOK_BUF: *mut c_char = null_mut();
    if !str.is_null() {
        STRTOK_BUF = null_mut();
    }
    strtok_r(str, delim, &raw mut STRTOK_BUF)
}

#[no_mangle]
unsafe extern "C" fn strtok_r(
    mut str: *mut c_char,
    delim: *const c_char,
    saveptr: *mut *mut c_char,
) -> *mut c_char {
    if saveptr.is_null() || delim.is_null() {
        panic!();
    }

    if str.is_null() {
        str = *saveptr;
    }

    str = str.add(strspn(str, delim));

    if *str == 0 {
        *saveptr = null_mut();
        return null_mut();
    }

    let len = strcspn(str, delim);
    if *str.add(len) != 0 {
        *saveptr = str.add(len + 1);
    } else {
        *saveptr = null_mut();
    }
    *str.add(len) = 0;
    str.cast()
}

// TODO locales
#[no_mangle]
unsafe extern "C" fn strcoll(_a: *const c_char, _b: *const c_char) -> c_int {
    unimplemented!()
}

#[no_mangle]
unsafe extern "C" fn strcoll_l(_a: *const c_char, _b: *const c_char, _l: locale_t) -> c_int {
    unimplemented!()
}

#[no_mangle]
unsafe extern "C" fn strerror_l(_e: c_int, _l: locale_t) -> *mut c_char {
    unimplemented!()
}

#[no_mangle]
unsafe extern "C" fn strxfrm(_a: *mut c_char, _b: *const c_char, _n: usize) -> usize {
    unimplemented!()
}

#[no_mangle]
unsafe extern "C" fn strxfrm_l(
    _a: *mut c_char,
    _b: *const c_char,
    _n: usize,
    _l: locale_t,
) -> usize {
    unimplemented!()
}
