//! This would've been called `io`, but rust-analyzer refuses to see
//! the submodules of this module when it's named `io`.

#![allow(unused_variables)]

use core::{
    ffi::{CStr, VaList, c_char, c_int, c_long, c_size_t, c_void},
    ptr::null_mut,
};

use alloc::string::String;
use log::{info, warn};
use printf_compat::output::fmt_write;

use crate::ffi::{
    inout::file::{FILE, STDERR, STDOUT},
    string::__xkbc_memcpy,
};

pub mod dir;
pub mod file;

// File management

#[unsafe(no_mangle)]
pub unsafe extern "C" fn __xkbc_fopen(filename: *const c_char, mode: *const c_char) -> *mut FILE {
    warn!(
        "The xkbcommon library is attempting to open a file at path: {:?}",
        if filename.is_null() {
            None
        } else {
            Some(unsafe { CStr::from_ptr(filename) })
        }
    );
    null_mut()
}

#[unsafe(no_mangle)]
pub unsafe extern "C" fn __xkbc_fclose(file: *mut FILE) -> c_int {
    todo!()
}

#[unsafe(no_mangle)]
pub unsafe extern "C" fn __xkbc_fseek(stream: *mut FILE, offset: c_long, whence: c_int) -> c_int {
    todo!()
}

#[unsafe(no_mangle)]
pub unsafe extern "C" fn __xkbc_ftell(stream: *mut FILE) -> c_long {
    todo!()
}

#[unsafe(no_mangle)]
pub unsafe extern "C" fn __xkbc_fread(
    ptr: *mut c_void,
    size: c_size_t,
    nobj: c_size_t,
    stream: *mut FILE,
) -> c_size_t {
    todo!()
}

#[unsafe(no_mangle)]
unsafe extern "C" fn __xkbc_fputs(s: *const c_char, stream: *mut FILE) -> c_int {
    todo!()
}

// Printing

#[unsafe(no_mangle)]
pub unsafe extern "C" fn __xkbc_fprintf(
    stream: *mut FILE,
    format: *const c_char,
    mut args: ...
) -> c_int {
    unsafe { __xkbc_vfprintf(stream, format, args.as_va_list()) }
}

#[unsafe(no_mangle)]
pub unsafe extern "C" fn __spre_fprintf(
    stream: *mut FILE,
    format: *const c_char,
    args: ...
) -> c_int {
    unsafe { __xkbc_fprintf(stream, format) }
}

#[unsafe(no_mangle)]
pub unsafe extern "C" fn __xkbc_vfprintf(
    stream: *mut FILE,
    format: *const c_char,
    ap: VaList,
) -> c_int {
    if stream == STDOUT || stream == STDERR {
        let string = ::alloc::format!(
            "vfprintf({:?}, {:?}, {:?})",
            stream,
            unsafe { CStr::from_ptr(format) },
            ap
        );
        info!("{}", string);
        string.len() as c_int
    } else {
        -1
    }
}

#[unsafe(no_mangle)]
pub unsafe extern "C" fn __xkbc_vasprintf(
    strp: *mut *mut c_char,
    fmt: *const c_char,
    ap: VaList,
) -> c_int {
    todo!()
}

#[unsafe(no_mangle)]
pub unsafe extern "C" fn __xkbc_snprintf(
    string: *mut c_char,
    size: c_size_t,
    format: *const c_char,
    mut args: ...
) -> c_int {
    unsafe { __xkbc_vsnprintf(string, size, format, args.as_va_list()) }
}

#[unsafe(no_mangle)]
pub unsafe extern "C" fn __xkbc_vsnprintf(
    string: *mut c_char,
    // Length in bytes **including the terminating null byte**.
    size: c_size_t,
    format: *const c_char,
    ap: VaList,
) -> c_int {
    if size == 0 {
        return 0;
    }

    let mut rust_buffer = String::new();
    unsafe {
        printf_compat::format(format, ap, fmt_write(&mut rust_buffer));
        let string_length = core::cmp::min(rust_buffer.len(), size - 1);
        // __xkbc_strncpy would be preferrable, if it was available
        __xkbc_memcpy(
            string as *mut _,
            rust_buffer.as_ptr() as *mut _,
            string_length,
        );
        *string.add(string_length) = 0; // Add terminating null byte.
        string_length as c_int
    }
}

#[unsafe(no_mangle)]
pub unsafe extern "C" fn __spre_sprintf(
    string: *mut c_char,
    format: *const c_char,
    mut args: ...
) -> c_int {
    unsafe { vsprintf(string, format, args.as_va_list()) }
}

pub unsafe fn vsprintf(string: *mut c_char, format: *const c_char, ap: VaList) -> c_int {
    let mut rust_buffer = String::new();
    unsafe {
        printf_compat::format(format, ap, fmt_write(&mut rust_buffer));
        // __xkbc_strncpy would be preferrable, if it was available
        __xkbc_memcpy(
            string as *mut _,
            rust_buffer.as_ptr() as *mut _,
            rust_buffer.len(),
        );
        *string.add(rust_buffer.len()) = 0; // Add terminating null byte.
        rust_buffer.len() as c_int
    }
}

#[unsafe(no_mangle)]
pub unsafe extern "C" fn __spre_vsnprintf(
    string: *mut c_char,
    // Length in bytes **including the terminating null byte**.
    size: c_size_t,
    format: *const c_char,
    ap: VaList,
) -> c_int {
    unsafe { __xkbc_vsnprintf(string, size, format, ap) }
}

#[unsafe(no_mangle)]
pub unsafe extern "C" fn __spre_sscanf(s: *const c_char, format: *const c_char, ...) -> c_int {
    todo!()
}
