#![cfg_attr(test, allow(dead_code))] // why is this necessary?

use super::abi::usercalls;
use super::unsupported;
use crate::ffi::CStr;
use crate::io;
use crate::num::NonZero;
use crate::time::{Duration, Instant};

pub struct Thread(task_queue::JoinHandle);

pub const DEFAULT_MIN_STACK_SIZE: usize = 4096;

pub use self::task_queue::JoinNotifier;

mod task_queue {
    use super::wait_notify;
    use crate::sync::{Mutex, MutexGuard};

    pub type JoinHandle = wait_notify::Waiter;

    pub struct JoinNotifier(Option<wait_notify::Notifier>);

    impl Drop for JoinNotifier {
        fn drop(&mut self) {
            self.0.take().unwrap().notify();
        }
    }

    pub(super) struct Task {
        p: Box<dyn FnOnce() + Send>,
        done: JoinNotifier,
    }

    impl Task {
        pub(super) fn new(p: Box<dyn FnOnce() + Send>) -> (Task, JoinHandle) {
            let (done, recv) = wait_notify::new();
            let done = JoinNotifier(Some(done));
            (Task { p, done }, recv)
        }

        pub(super) fn run(self) -> JoinNotifier {
            (self.p)();
            self.done
        }
    }

    // Specifying linkage/symbol name is solely to ensure a single instance between this crate and its unit tests
    #[cfg_attr(test, linkage = "available_externally")]
    #[unsafe(export_name = "_ZN16__rust_internals3std3sys3pal3sgx6thread10TASK_QUEUEE")]
    static TASK_QUEUE: Mutex<Vec<Task>> = Mutex::new(Vec::new());

    pub(super) fn lock() -> MutexGuard<'static, Vec<Task>> {
        TASK_QUEUE.lock().unwrap()
    }
}

/// This module provides a synchronization primitive that does not use thread
/// local variables. This is needed for signaling that a thread has finished
/// execution. The signal is sent once all TLS destructors have finished at
/// which point no new thread locals should be created.
pub mod wait_notify {
    use crate::pin::Pin;
    use crate::sync::Arc;
    use crate::sys::sync::Parker;

    pub struct Notifier(Arc<Parker>);

    impl Notifier {
        /// Notify the waiter. The waiter is either notified right away (if
        /// currently blocked in `Waiter::wait()`) or later when it calls the
        /// `Waiter::wait()` method.
        pub fn notify(self) {
            Pin::new(&*self.0).unpark()
        }
    }

    pub struct Waiter(Arc<Parker>);

    impl Waiter {
        /// Wait for a notification. If `Notifier::notify()` has already been
        /// called, this will return immediately, otherwise the current thread
        /// is blocked until notified.
        pub fn wait(self) {
            // SAFETY:
            // This is only ever called on one thread.
            unsafe { Pin::new(&*self.0).park() }
        }
    }

    pub fn new() -> (Notifier, Waiter) {
        let inner = Arc::new(Parker::new());
        (Notifier(inner.clone()), Waiter(inner))
    }
}

impl Thread {
    // unsafe: see thread::Builder::spawn_unchecked for safety requirements
    pub unsafe fn new(
        _stack: usize,
        _name: Option<&str>,
        p: Box<dyn FnOnce() + Send>,
    ) -> io::Result<Thread> {
        let mut queue_lock = task_queue::lock();
        unsafe { usercalls::launch_thread()? };
        let (task, handle) = task_queue::Task::new(p);
        queue_lock.push(task);
        Ok(Thread(handle))
    }

    pub(super) fn entry() -> JoinNotifier {
        let mut pending_tasks = task_queue::lock();
        let task = rtunwrap!(Some, pending_tasks.pop());
        drop(pending_tasks); // make sure to not hold the task queue lock longer than necessary
        task.run()
    }

    pub fn yield_now() {
        let wait_error = rtunwrap!(Err, usercalls::wait(0, usercalls::raw::WAIT_NO));
        rtassert!(wait_error.kind() == io::ErrorKind::WouldBlock);
    }

    /// SGX should protect in-enclave data from the outside (attacker),
    /// so there should be no data leakage to the OS,
    /// and therefore also no 1-1 mapping between SGX thread names and OS thread names.
    ///
    /// This is why the method is intentionally No-Op.
    pub fn set_name(_name: &CStr) {
        // Note that the internally visible SGX thread name is already provided
        // by the platform-agnostic (target-agnostic) Rust thread code.
        // This can be observed in the [`std::thread::tests::test_named_thread`] test,
        // which succeeds as-is with the SGX target.
    }

    pub fn sleep(dur: Duration) {
        usercalls::wait_timeout(0, dur, || true);
    }

    pub fn sleep_until(deadline: Instant) {
        let now = Instant::now();

        if let Some(delay) = deadline.checked_duration_since(now) {
            Self::sleep(delay);
        }
    }

    pub fn join(self) {
        self.0.wait();
    }
}

pub fn available_parallelism() -> io::Result<NonZero<usize>> {
    unsupported()
}
