#![unstable(feature = "yggdrasil_os", issue = "none")]

use yggdrasil_rt::io::{device::DeviceRequest, terminal as rt_terminal};
use yggdrasil_rt::sys as syscall;

use crate::fs::File;
use crate::io;
use crate::mem::MaybeUninit;
use crate::os::fd::{AsRawFd, FromRawFd};
use crate::path::Path;
use crate::sys::cvt_io;

#[unstable(feature = "yggdrasil_os", issue = "none")]
pub type TerminalOptions = rt_terminal::TerminalOptions;

#[unstable(feature = "yggdrasil_os", issue = "none")]
pub type TerminalSize = rt_terminal::TerminalSize;

#[unstable(feature = "yggdrasil_os", issue = "none")]
pub unsafe fn set_terminal_options<F: AsRawFd>(fd: F, opt: TerminalOptions) -> io::Result<()> {
    let mut req = DeviceRequest::SetTerminalOptions(opt);
    cvt_io(syscall::device_request(fd.as_raw_fd(), &mut req))
}

#[unstable(feature = "yggdrasil_os", issue = "none")]
pub unsafe fn get_terminal_options<F: AsRawFd>(fd: F) -> io::Result<TerminalOptions> {
    let mut req = DeviceRequest::GetTerminalOptions(MaybeUninit::uninit());
    cvt_io(syscall::device_request(fd.as_raw_fd(), &mut req))?;
    let DeviceRequest::GetTerminalOptions(opt) = req else {
        unreachable!();
    };
    Ok(opt.assume_init())
}

#[unstable(feature = "yggdrasil_os", issue = "none")]
pub fn get_terminal_size<F: AsRawFd>(fd: F) -> io::Result<TerminalSize> {
    let mut req = DeviceRequest::GetTerminalSize(MaybeUninit::uninit());
    cvt_io(unsafe { syscall::device_request(fd.as_raw_fd(), &mut req) })?;
    let DeviceRequest::GetTerminalSize(opt) = req else {
        unreachable!();
    };
    Ok(unsafe { opt.assume_init() })
}

#[unstable(feature = "yggdrasil_os", issue = "none")]
pub unsafe fn update_terminal_options<F: AsRawFd, M: Fn(TerminalOptions) -> TerminalOptions>(
    fd: F,
    mutator: M,
) -> io::Result<TerminalOptions> {
    let fd = fd.as_raw_fd();
    let old = get_terminal_options(fd)?;
    let new = mutator(old);
    set_terminal_options(fd, new)?;
    Ok(old)
}

#[unstable(feature = "yggdrasil_os", issue = "none")]
pub unsafe fn start_terminal_session<P: AsRef<Path>>(_terminal: P) -> io::Result<()> {
    todo!()
    // let terminal = terminal.as_ref().to_str().unwrap();

    // cvt_io(syscall::start_session())?;

    // // TODO implement open flags to explicitly set stdin/stdout/stderr

    // cvt_io(syscall::open(None, terminal, OpenOptions::READ, FileMode::empty()))?;
    // cvt_io(syscall::open(None, terminal, OpenOptions::WRITE, FileMode::empty()))?;
    // cvt_io(syscall::open(None, terminal, OpenOptions::WRITE, FileMode::empty()))?;

    // Ok(())
}

#[unstable(feature = "yggdrasil_os", issue = "none")]
pub fn create_pty(
    options: Option<TerminalOptions>,
    size: TerminalSize,
) -> io::Result<(File, File)> {
    let options = options.unwrap_or_default();
    let mut fds = MaybeUninit::uninit();

    cvt_io(unsafe { syscall::create_pty(&options, &size, &mut fds) })?;

    let (master_raw_fd, slave_raw_fd) = unsafe { fds.assume_init() };
    let master = unsafe { File::from_raw_fd(master_raw_fd) };
    let slave = unsafe { File::from_raw_fd(slave_raw_fd) };

    Ok((master, slave))
}
