// get*

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

use crate::{
    allocator,
    error::{CEofResult, CIsizeResult, CPtrResult, CResult, EResult},
    headers::errno::Errno,
    io::{
        managed::{stdin, stdout, FILE},
        Read, Write,
    },
    util::{PointerExt, PointerStrExt},
};

#[no_mangle]
unsafe extern "C" fn fgetc(fp: *mut FILE) -> CEofResult {
    let fp = fp.ensure_mut();
    let mut buf = [0];
    fp.read_exact(&mut buf)?;
    CEofResult::success(buf[0] as _)
}

#[no_mangle]
unsafe extern "C" fn fgets(s: *mut c_char, size: c_int, fp: *mut FILE) -> CPtrResult<c_char> {
    let fp = fp.ensure_mut();
    let mut s = NonNull::new(s).unwrap();

    if size <= 0 {
        return CPtrResult::ERROR;
    }
    // Nothing to read
    if size == 1 {
        *s.as_mut() = 0;
        return CPtrResult::success(s);
    }

    let size = size as usize;
    let mut pos = 0;
    let mut buf = [0];
    let slice = NonNull::slice_from_raw_parts(s, size).as_mut();

    while pos < size - 1 {
        let ch = match fp.read(&mut buf)? {
            1 => buf[0],
            _ => break,
        };

        slice[pos] = ch as _;
        pos += 1;

        if ch == b'\n' {
            break;
        }
    }

    if pos == 0 {
        CPtrResult::ERROR
    } else {
        slice[pos] = 0;
        CPtrResult::success(s)
    }
}

#[no_mangle]
unsafe extern "C" fn getc(fp: *mut FILE) -> CEofResult {
    fgetc(fp)
}

#[no_mangle]
unsafe extern "C" fn getchar() -> CEofResult {
    fgetc(stdin)
}

#[no_mangle]
unsafe extern "C" fn fgetc_unlocked(fp: *mut FILE) -> CEofResult {
    let fp = fp.ensure_mut();
    let mut buf = [0];
    match fp.read_unlocked(&mut buf)? {
        1 => CEofResult::success(buf[0] as _),
        _ => CEofResult::ERROR,
    }
}

#[no_mangle]
unsafe extern "C" fn getc_unlocked(fp: *mut FILE) -> CEofResult {
    fgetc_unlocked(fp)
}

#[no_mangle]
unsafe extern "C" fn getchar_unlocked() -> CEofResult {
    getc_unlocked(stdin)
}

// put*

#[no_mangle]
unsafe extern "C" fn fputc(ch: c_int, fp: *mut FILE) -> CEofResult {
    let fp = fp.ensure_mut();
    let ch = ch as u8;
    fp.write_all(&[ch])?;
    CEofResult::success(ch as c_int)
}

#[no_mangle]
unsafe extern "C" fn fputs(str: *const c_char, fp: *mut FILE) -> CEofResult {
    let fp = fp.ensure_mut();
    let str = str.ensure_cstr();
    fp.write_all(str.to_bytes())?;
    CEofResult::success(0)
}

#[no_mangle]
unsafe extern "C" fn putc(ch: c_int, fp: *mut FILE) -> CEofResult {
    fputc(ch, fp)
}

#[no_mangle]
unsafe extern "C" fn putchar(ch: c_int) -> CEofResult {
    fputc(ch, stdout)
}

#[no_mangle]
unsafe extern "C" fn putc_unlocked(ch: c_int, fp: *mut FILE) -> CEofResult {
    let fp = fp.ensure_mut();
    let ch = ch as u8;
    match fp.write_unlocked(&[ch])? {
        1 => CEofResult::success(ch as _),
        _ => CEofResult::ERROR,
    }
}

#[no_mangle]
unsafe extern "C" fn putchar_unlocked(ch: c_int) -> CEofResult {
    putc_unlocked(ch, stdout)
}

#[no_mangle]
unsafe extern "C" fn puts(str: *const c_char) -> CEofResult {
    let str = str.ensure_cstr();
    let out = match stdout.as_mut() {
        Some(out) => out,
        None => {
            // TODO this hack is needed because currently global constructors are called
            //      before ygglibc is entered and I/O streams are initialized.
            //      this will be removed when I properly implement passing DT_INIT_*** ranges
            //      from dyn-loader to ygglibc.
            use yggdrasil_rt::io::RawFd;
            unsafe {
                yggdrasil_rt::sys::write(RawFd::STDOUT, str.to_bytes()).ok();
                yggdrasil_rt::sys::write(RawFd::STDOUT, b"\n").ok();
            }

            return CEofResult::success(0);
        }
    };
    out.write_all(str.to_bytes())?;
    out.write_all(b"\n")?;
    CEofResult::success(0)
}

// ungetc

#[no_mangle]
unsafe extern "C" fn ungetc(ch: c_int, fp: *mut FILE) -> CEofResult {
    let fp = fp.ensure_mut();
    fp.ungetc(ch as _)?;
    CEofResult::success(ch)
}

// getdelim

struct MallocBufferWriter {
    buffer: Option<NonNull<c_char>>,
    capacity: usize,
    position: usize,
}

impl MallocBufferWriter {
    unsafe fn new(buffer: Option<NonNull<c_char>>, mut capacity: usize) -> Self {
        if buffer.is_none() {
            capacity = 0;
        }
        Self {
            buffer,
            capacity,
            position: 0,
        }
    }

    fn try_reserve(&mut self) -> EResult<&mut c_char> {
        if self.position == self.capacity {
            self.capacity = (self.capacity + 64) & !63;
            self.buffer = Some(
                unsafe { allocator::c_realloc(self.buffer.map(NonNull::cast), self.capacity) }?
                    .cast(),
            );
        }
        let buffer = self.buffer.unwrap();
        EResult::Ok(unsafe { buffer.add(self.position).as_mut() })
    }

    fn putc(&mut self, ch: c_int) -> EResult<()> {
        let item = self.try_reserve()?;
        *item = ch as _;
        self.position += 1;
        EResult::Ok(())
    }
}

fn getdelim_inner(
    buffer: Option<NonNull<c_char>>,
    capacity: usize,
    delim: c_int,
    stream: &mut FILE,
) -> (MallocBufferWriter, EResult<()>) {
    let mut writer = unsafe { MallocBufferWriter::new(buffer, capacity) };
    let mut buf = [0];

    loop {
        let ch = match stream.read(&mut buf) {
            EResult::Ok(1) => buf[0] as c_int,
            EResult::Ok(_) => break,
            EResult::Err(err) => return (writer, EResult::Err(err)),
        };

        match writer.putc(ch) {
            EResult::Ok(()) => (),
            EResult::Err(err) => {
                return (writer, EResult::Err(err));
            }
        }

        if ch == delim {
            break;
        }
    }

    if writer.position == 0 {
        // EOF reached before anything could be read
        return (writer, EResult::Err(Errno::ESUCCESS));
    }

    match writer.putc(0) {
        EResult::Ok(()) => (),
        EResult::Err(err) => {
            return (writer, EResult::Err(err));
        }
    }

    (writer, EResult::Ok(()))
}
#[no_mangle]
unsafe extern "C" fn getdelim(
    lineptr: *mut *mut c_char,
    n: *mut usize,
    delim: c_int,
    fp: *mut FILE,
) -> CIsizeResult {
    let lineptr = lineptr.ensure_mut();

    let buffer = NonNull::new(*lineptr);
    let n = n.ensure_mut();
    let fp = fp.ensure_mut();

    let (writer, result) = getdelim_inner(buffer, *n, delim, fp);

    match writer.buffer {
        Some(buffer) => *lineptr = buffer.as_ptr(),
        None => *lineptr = null_mut(),
    }
    *n = writer.capacity;

    result?;
    assert_ne!(writer.position, 0);

    CIsizeResult::success(writer.position - 1)
}

#[no_mangle]
unsafe extern "C" fn getline(
    lineptr: *mut *mut c_char,
    size: *mut usize,
    fp: *mut FILE,
) -> CIsizeResult {
    getdelim(lineptr, size, b'\n' as _, fp)
}

#[no_mangle]
unsafe extern "C" fn gets(_buf: *mut c_char) -> *mut c_char {
    unimplemented!("DO NOT USE")
}
