use alloc::sync::Arc;
use core::{
    fmt,
    task::{Context, Poll},
};

use yggdrasil_abi::{
    error::Error,
    process::{ExitCode, ProcessId, ProcessWait, WaitFlags},
};

use crate::task::process::Process;

use super::FileReadiness;

#[derive(Clone)]
pub struct PidFileProcess {
    parent: Arc<Process>,
    child: Arc<Process>,
    non_blocking: bool,
}

#[allow(unused)]
#[derive(Clone)]
pub struct PidFileAnyChild {
    parent: Arc<Process>,
    non_blocking: bool,
}

#[derive(Clone)]
pub enum PidFile {
    Process(PidFileProcess),
    AnyChild(PidFileAnyChild),
}

impl PidFileProcess {
    pub fn new(parent: Arc<Process>, child: ProcessId, flags: WaitFlags) -> Result<Self, Error> {
        let child = parent.strong_child(child).ok_or(Error::ProcessNotFound)?;
        Ok(Self {
            parent,
            child,
            non_blocking: flags.contains(WaitFlags::NON_BLOCKING),
        })
    }

    pub fn read(&self, buf: &mut [u8]) -> Result<usize, Error> {
        if let Some(status) = self.status() {
            return Ok(PidFile::write_completion(buf, self.child.id, status));
        }

        if self.non_blocking {
            return Err(Error::WouldBlock);
        }

        block! {
            loop {
                if let Some(status) = self.status() {
                    break PidFile::write_completion(buf, self.child.id, status);
                }

                self.parent.child_exit_notify.wait_reset().await;
            }
        }
    }

    fn poll_read(&self, cx: &mut Context<'_>) -> Poll<Result<(), Error>> {
        self.parent.child_exit_notify.poll_reset(cx).map(Ok)
    }

    fn status(&self) -> Option<ExitCode> {
        self.child.get_exit_status()
    }
}

impl PidFile {
    pub fn new(parent: Arc<Process>, wait: &ProcessWait, flags: WaitFlags) -> Result<Self, Error> {
        match wait {
            &ProcessWait::Process(id) => PidFileProcess::new(parent, id, flags).map(Self::Process),
            &ProcessWait::Group(_id) => todo!(),
            ProcessWait::AnyChild => todo!(),
        }
    }

    fn write_completion(buf: &mut [u8], pid: ProcessId, status: ExitCode) -> usize {
        debug_assert!(buf.len() >= size_of::<u32>() + size_of::<i32>());
        buf[..size_of::<u32>()].copy_from_slice(&pid.bits().to_ne_bytes());
        match status {
            ExitCode::Exited(code) => buf[size_of::<u32>()..size_of::<u32>() + size_of::<i32>()]
                .copy_from_slice(&code.to_ne_bytes()),
            _ => todo!(),
        }
        size_of::<u32>() + size_of::<i32>()
    }

    pub fn read(&self, buf: &mut [u8], _non_blocking: bool) -> Result<usize, Error> {
        if buf.len() < size_of::<u32>() + size_of::<i32>() {
            log::warn!(
                "PidFd: BufferTooSmall (need {}, got {})",
                size_of::<u32>() + size_of::<i32>(),
                buf.len()
            );
            return Err(Error::BufferTooSmall);
        }
        match self {
            Self::Process(pid) => pid.read(buf),
            Self::AnyChild(_) => todo!(),
        }
    }
}

// impl Read for PidFile {
// }

impl FileReadiness for PidFile {
    fn poll_read(&self, cx: &mut Context<'_>) -> Poll<Result<(), Error>> {
        match self {
            Self::Process(pid) => pid.poll_read(cx),
            Self::AnyChild(_) => todo!(),
        }
    }
}

impl fmt::Debug for PidFile {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let mut s = f.debug_struct("PidFile");
        match self {
            Self::Process(pid) => s
                .field("child", &pid.child.id)
                .field("non_blocking", &pid.non_blocking),
            Self::AnyChild(pid) => s.field("non_blocking", &pid.non_blocking),
        };
        s.finish()
    }
}
