use core::{
    mem::size_of,
    task::{Context, Poll},
    time::Duration,
};

use alloc::boxed::Box;

use futures_util::FutureExt;
use libk_util::sync::LockMethod;
use yggdrasil_abi::error::Error;

use crate::{
    task::{
        runtime::{self, SleepFuture},
        sync::Mutex,
    },
    vfs::{FileReadiness, Write},
};

/// File-like structure to generate periodic or one-shit events at certain intervals
pub struct TimerFile {
    inner: Box<Mutex<Option<SleepFuture>>>,
    _repeat: bool,
}

impl TimerFile {
    /// Creates a new inert timer
    pub fn new(repeat: bool) -> Self {
        Self {
            _repeat: repeat,
            inner: Box::new(Mutex::new(None)),
        }
    }

    pub fn read(&self, buf: &mut [u8], non_blocking: bool) -> Result<usize, Error> {
        if buf.len() < size_of::<u8>() {
            log::warn!("TimerFile: BufferTooSmall");
            return Err(Error::BufferTooSmall);
        }
        if non_blocking {
            let inner = self.inner.lock()?;
            if inner.as_ref().and_then(SleepFuture::remaining).is_some() {
                return Err(Error::WouldBlock);
            }

            buf[0] = 1;
            Ok(1)
        } else {
            todo!()
        }
    }
}

impl FileReadiness for TimerFile {
    fn poll_read(&self, cx: &mut Context<'_>) -> Poll<Result<(), Error>> {
        // TODO repeat function
        let mut lock = self.inner.lock()?;
        let future = lock.as_mut().ok_or(Error::InvalidOperation)?;
        future.poll_unpin(cx).map(Ok)
    }
}

impl Write for TimerFile {
    fn write(&self, buf: &[u8]) -> Result<usize, Error> {
        if buf.len() != size_of::<u128>() {
            return Err(Error::InvalidArgument);
        }
        let mut bytes = [0; 16];
        bytes.copy_from_slice(buf);
        let tval = u128::from_ne_bytes(bytes);

        let mut lock = self.inner.lock()?;
        if tval == 0 {
            *lock = None;
        } else {
            let duration = Duration::from_micros(tval.try_into().unwrap());
            *lock = Some(runtime::sleep(duration));
        }

        Ok(buf.len())
    }
}
