use core::sync::atomic::Ordering;

use alloc::sync::Arc;
use crossbeam_queue::ArrayQueue;
use libk_util::{sync::IrqGuard, OneTimeInit};
use yggdrasil_abi::error::Error;

use crate::task::thread::Thread;

use super::task::Task;

pub(super) static TASK_QUEUE: OneTimeInit<TaskQueue> = OneTimeInit::new();

pub(super) struct TaskQueue {
    // Queue of workers waiting for an item
    pending_workers: ArrayQueue<Arc<Thread>>,
    task_queue: ArrayQueue<Arc<Task>>,
}

impl TaskQueue {
    pub fn new(task_capacity: usize) -> Self {
        assert!(task_capacity > 0);
        Self {
            pending_workers: ArrayQueue::new(16),
            task_queue: ArrayQueue::new(task_capacity),
        }
    }

    fn wakeup_one(&self) {
        if let Some(worker) = self.pending_workers.pop() {
            worker.enqueue();
        }
    }

    pub fn enqueue(&self, task: Arc<Task>) -> Result<(), Error> {
        // Already enqueued
        if task.enqueued.swap(true, Ordering::Acquire) {
            return Ok(());
        }
        let _irq = IrqGuard::acquire();
        if self.task_queue.push(task).is_err() {
            return Err(Error::WouldBlock);
        }
        self.wakeup_one();
        Ok(())
    }

    pub fn dequeue(&self) -> Result<Arc<Task>, Error> {
        let thread = Thread::current();
        // assert!(PlatformImpl::interrupt_mask());
        loop {
            if let Some(task) = self.task_queue.pop() {
                task.enqueued.store(false, Ordering::Release);
                return Ok(task);
            }

            if self.pending_workers.push(thread.clone()).is_err() {
                panic!("Pending worker queue overflow");
            }

            // This must not fail. Signals must not be raised.
            thread.suspend().unwrap();
        }
    }
}

/// Initializes the global async/await task queue
pub fn init_task_queue() {
    TASK_QUEUE.init(TaskQueue::new(256));
}

pub(super) fn push_task(task: Arc<Task>) -> Result<(), Error> {
    TASK_QUEUE.get().enqueue(task)
}

pub(super) fn pop_task() -> Result<Arc<Task>, Error> {
    TASK_QUEUE.get().dequeue()
}
