use core::{
    ffi::{c_char, c_int, c_long, c_void},
    ptr::{null_mut, NonNull},
};

use alloc::boxed::Box;
use yggdrasil_rt::{
    io::{OpenOptions, RawFd, SeekFrom},
    path::Path,
};

use crate::{
    error::{
        CEofResult, CFdResult, CIntZeroResult, COffsetResult, CPtrResult, CResult, EResult,
        ResultExt, TryFromExt,
    },
    headers::{errno::Errno, sys_types::off_t},
    io::{
        self,
        managed::{FileOpenSource, FILE},
        Seek, Write,
    },
    util::{PointerExt, PointerStrExt},
};

use super::{fpos_t, SEEK_SET, _IOFBF, _IOLBF, _IONBF};

fn open_inner<O: FileOpenSource>(source: O, mode_str: &[u8]) -> EResult<NonNull<FILE>> {
    let opts = match mode_str {
        b"r" | b"rb" => OpenOptions::READ,
        b"r+" | b"rb+" => OpenOptions::WRITE | OpenOptions::READ,
        b"w" | b"wb" => OpenOptions::WRITE | OpenOptions::TRUNCATE | OpenOptions::CREATE,
        b"w+" | b"wb+" => {
            OpenOptions::WRITE | OpenOptions::TRUNCATE | OpenOptions::CREATE | OpenOptions::READ
        }
        b"a" | b"ab" => {
            OpenOptions::APPEND | OpenOptions::CREATE | OpenOptions::READ | OpenOptions::WRITE
        }
        b"a+" | b"ab+" => {
            OpenOptions::APPEND | OpenOptions::CREATE | OpenOptions::READ | OpenOptions::WRITE
        }
        // TODO
        _ => todo!(),
    };

    let file = source.open_with(opts)?;
    let file = NonNull::from(Box::leak(Box::new(file)));
    io::managed::register_file(file);
    EResult::Ok(file)
}

#[no_mangle]
unsafe extern "C" fn clearerr(fp: *mut FILE) {
    let fp = fp.ensure_mut();
    fp.clear_error();
}

#[no_mangle]
unsafe extern "C" fn fclose(fp: *mut FILE) -> CEofResult {
    let fp = NonNull::new(fp).expect("fclose(): fp == NULL");

    if !io::managed::deregister_file(fp) {
        log::error!("fclose() on non-registered file: {fp:p}");
    }

    FILE::close(fp)?;
    CEofResult::success(0)
}

#[no_mangle]
unsafe extern "C" fn fdopen(fd: c_int, mode: *const c_char) -> CPtrResult<FILE> {
    let fd = RawFd::e_try_from(fd)?;
    let mode = mode.ensure_cstr();
    let file = open_inner(fd, mode.to_bytes())?;
    CPtrResult::success(file)
}

#[no_mangle]
unsafe extern "C" fn feof(fp: *mut FILE) -> c_int {
    let fp = fp.ensure_mut();
    fp.is_eof() as _
}

#[no_mangle]
unsafe extern "C" fn ferror(fp: *mut FILE) -> c_int {
    let fp = fp.ensure_mut();
    fp.is_error() as _
}

#[no_mangle]
unsafe extern "C" fn fflush(fp: *mut FILE) -> CIntZeroResult {
    let fp = fp.ensure_mut();
    fp.flush()?;
    CIntZeroResult::SUCCESS
}

#[no_mangle]
unsafe extern "C" fn fileno(fp: *mut FILE) -> CFdResult {
    let fp = fp.ensure_mut();
    let fd = fp.fd();
    match fd {
        Some(fd) => CFdResult::success(fd),
        None => CFdResult::ERROR,
    }
}

#[no_mangle]
unsafe extern "C" fn flockfile(fp: *mut FILE) {
    let fp = fp.ensure_mut();
    fp.lock();
}

#[no_mangle]
unsafe extern "C" fn fgetpos(fp: *mut FILE, pos: *mut fpos_t) -> CIntZeroResult {
    let fp = fp.ensure_mut();
    let pos = pos.ensure_mut();
    let p = fp.stream_position()?;
    *pos = p;
    CIntZeroResult::SUCCESS
}

#[no_mangle]
unsafe extern "C" fn fmemopen(
    _buffer: *mut c_void,
    _size: usize,
    _mode: *const c_char,
) -> CPtrResult<FILE> {
    unimplemented!()
}

#[no_mangle]
unsafe extern "C" fn freopen(
    _path: *const c_char,
    _mode: *const c_char,
    _fp: *mut FILE,
) -> CPtrResult<FILE> {
    unimplemented!()
}

#[no_mangle]
unsafe extern "C" fn fopen(path: *const c_char, mode: *const c_char) -> CPtrResult<FILE> {
    let path = path.ensure_str();
    let mode = mode.ensure_cstr();
    let file = open_inner(Path::from_str(path), mode.to_bytes())?;
    CPtrResult::success(file)
}

#[no_mangle]
unsafe extern "C" fn fseek(fp: *mut FILE, offset: c_long, whence: c_int) -> CIntZeroResult {
    let offset: off_t = offset.try_into().e_map_err(|_| Errno::EINVAL)?;
    fseeko(fp, offset, whence)
}

#[no_mangle]
unsafe extern "C" fn fseeko(fp: *mut FILE, offset: off_t, whence: c_int) -> CIntZeroResult {
    let seek = SeekFrom::e_try_from((offset, whence))?;
    let fp = fp.ensure_mut();
    fp.seek(seek)?;
    CIntZeroResult::SUCCESS
}

#[no_mangle]
unsafe extern "C" fn fsetpos(fp: *mut FILE, pos: *const fpos_t) -> CIntZeroResult {
    let fp = fp.ensure_mut();
    let pos = *pos.ensure();
    fp.seek(SeekFrom::Start(pos))?;
    CIntZeroResult::SUCCESS
}

#[no_mangle]
unsafe extern "C" fn ftell(fp: *mut FILE) -> c_long {
    match ftello(fp) {
        COffsetResult::ERROR => -1,
        COffsetResult(off) => off.try_into().unwrap(),
    }
}

#[no_mangle]
unsafe extern "C" fn ftello(fp: *mut FILE) -> COffsetResult {
    let fp = fp.ensure_mut();
    let pos = fp.stream_position()?;
    COffsetResult::success(pos)
}

#[no_mangle]
unsafe extern "C" fn ftrylockfile(fp: *mut FILE) -> c_int {
    let fp = fp.ensure_mut();
    if fp.try_lock() {
        0
    } else {
        -1
    }
}

#[no_mangle]
unsafe extern "C" fn funlockfile(fp: *mut FILE) {
    let fp = fp.ensure_mut();
    fp.unlock();
}

#[no_mangle]
unsafe extern "C" fn open_memstream(
    _ptr: *mut *mut c_char,
    _sizeloc: *mut usize,
) -> CPtrResult<FILE> {
    unimplemented!()
}

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

#[no_mangle]
unsafe extern "C" fn popen(_command: *const c_char, _ty: *const c_char) -> CPtrResult<FILE> {
    todo!("popen()")
}

#[no_mangle]
unsafe extern "C" fn rewind(fp: *mut FILE) {
    fseek(fp, 0, SEEK_SET);
}

#[no_mangle]
unsafe extern "C" fn setbuf(fp: *mut FILE, buf: *mut c_char) {
    setbuffer(fp, buf, 0);
}

#[no_mangle]
unsafe extern "C" fn setbuffer(fp: *mut FILE, buf: *mut c_char, size: usize) {
    let mode = if buf.is_null() { _IONBF } else { _IOFBF };
    setvbuf(fp, buf, mode, size);
}

#[no_mangle]
unsafe extern "C" fn setlinebuf(fp: *mut FILE) {
    setvbuf(fp, null_mut(), _IOLBF, 0);
}

#[no_mangle]
unsafe extern "C" fn setvbuf(
    fp: *mut FILE,
    buffer: *mut c_char,
    mode: c_int,
    size: usize,
) -> CIntZeroResult {
    let fp = fp.ensure_mut();
    fp.setvbuf(mode, buffer, size)?;
    CIntZeroResult::SUCCESS
}

#[no_mangle]
unsafe extern "C" fn __fpurge(_fp: *mut FILE) {
    todo!("__fpurge()")
}

#[no_mangle]
unsafe extern "C" fn __freading(fp: *mut FILE) -> c_int {
    let fp = fp.ensure_mut();
    fp.reading() as c_int
}

#[no_mangle]
unsafe extern "C" fn __freadable(fp: *mut FILE) -> c_int {
    let fp = fp.ensure_mut();
    fp.is_readable() as c_int
}

#[no_mangle]
unsafe extern "C" fn __fwritable(fp: *mut FILE) -> c_int {
    let fp = fp.ensure_mut();
    fp.is_writeable() as c_int
}

#[no_mangle]
unsafe extern "C" fn __fpending(fp: *mut FILE) -> usize {
    let fp = fp.ensure_mut();
    fp.pending_bytes()
}

#[no_mangle]
unsafe extern "C" fn __fbufsize(fp: *mut FILE) -> usize {
    let fp = fp.ensure_mut();
    fp.buffer_size()
}

#[no_mangle]
unsafe extern "C" fn __flbf(fp: *mut FILE) -> c_int {
    let fp = fp.ensure_mut();
    fp.is_line_buffered() as c_int
}

#[no_mangle]
unsafe extern "C" fn __fwriting(fp: *mut FILE) -> c_int {
    let fp = fp.ensure_mut();
    fp.writing() as c_int
}

#[no_mangle]
unsafe extern "C" fn __fseterr(fp: *mut FILE) {
    let fp = fp.ensure_mut();
    fp.set_error();
}
