use std::{
    mem,
    num::NonZero,
    sync::Mutex,
    time::{Duration, Instant},
};

/// Tracks callbacks that are registered to run.
///
/// Callbacks may be scheduled to run after a duration of time or when there is
/// spare time to run them.
pub struct HandlerQueues {
    timer_handlers: Vec<Box<TimerHandler>>,
    idle_handlers: Vec<Box<IdleHandler>>,
}

/// Refers to a scheduled handler.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[repr(transparent)]
pub struct HandlerId(Option<NonZero<usize>>);

/// Returned by handler queue predicate(s) to describe if there are pending
/// callbacks.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum QueueStatus {
    Empty,
    Pending,
}

impl<'a> From<&'a Box<TimerHandler>> for HandlerId {
    fn from(handler: &'a Box<TimerHandler>) -> HandlerId {
        HandlerId(NonZero::new(
            (handler.as_ref() as *const TimerHandler).addr(),
        ))
    }
}

impl<'a> From<&'a Box<IdleHandler>> for HandlerId {
    fn from(handler: &'a Box<IdleHandler>) -> HandlerId {
        HandlerId(NonZero::new(
            (handler.as_ref() as *const IdleHandler).addr(),
        ))
    }
}

impl HandlerQueues {
    /// Creates a new `HandlerQueues` with no pending tasks.
    pub const fn new() -> Self {
        HandlerQueues {
            timer_handlers: Vec::new(),
            idle_handlers: Vec::new(),
        }
    }

    /// Enqueues `handler` and returns its id.
    fn enqueue_timer(&mut self, handler: Box<TimerHandler>) -> HandlerId {
        let id = HandlerId::from(&handler);
        let insert_at = match self
            .timer_handlers
            .binary_search_by(|t| handler.when.cmp(&t.when))
        {
            Ok(i) => i,
            Err(i) => i,
        };
        self.timer_handlers.insert(insert_at, handler);
        id
    }

    /// Enqueues `callback` to be called once, at `when`, and
    /// returns an id to refer to the scheduled task.
    pub fn enqueue_once_timer(
        &mut self,
        callback: Box<dyn FnMut() + Send>,
        when: Instant,
    ) -> HandlerId {
        self.enqueue_timer(Box::new(TimerHandler {
            callback,
            when,
            delay: Duration::ZERO,
        }))
    }

    /// Enqueues `callback` to be called repeatedly, first at `when` and
    /// subsequently every `delay` time units. Returns an id to refer to the
    /// schedule task.
    pub fn enqueue_persistent_timer(
        &mut self,
        callback: Box<dyn FnMut() + Send>,
        when: Instant,
        delay: Duration,
    ) -> HandlerId {
        self.enqueue_timer(Box::new(TimerHandler {
            callback,
            when,
            delay,
        }))
    }

    /// Runs all queued timer handlers which should have run before `now`.
    ///
    /// If a timer handler that is run should repeat, it will be scheduled
    /// again.
    pub fn check_timer_handlers(&mut self, now: Instant) {
        let reschedule = run_timer_handlers(mem::take(&mut self.timer_handlers), now);
        for t in reschedule.into_iter() {
            self.enqueue_timer(t);
        }
    }

    /// Drops `handler` if it is scheduled. Passing a `handler` that is not
    /// scheduled is safe and will no-op.
    pub fn delete_timer_handler(&mut self, handler: HandlerId) {
        self.timer_handlers
            .retain(|t| HandlerId::from(t) != handler);
    }

    /// Returns the time when the next timer handler would be run, or `None` if
    /// no timer handlers are scheduled.
    pub fn next_timer_handler_time(&self) -> Option<Instant> {
        self.timer_handlers.first().map(|t| t.when)
    }

    /// Schedules `callback` to be called once, when the event loop is idle, and
    /// returns an id for the scheduled handler.
    pub fn add_idle_handler(&mut self, callback: Box<dyn FnMut() + Send>) -> HandlerId {
        let handler = Box::new(IdleHandler { callback });
        let id = HandlerId::from(&handler);
        self.idle_handlers.push(handler);
        id
    }

    /// Deletes `handler` if it is scheduled. Passing a `handler` that is not
    /// scheduled is safe and will no-op.
    pub fn delete_idle_handler(&mut self, handler: HandlerId) {
        self.idle_handlers.retain(|h| HandlerId::from(h) != handler);
    }

    /// Runs any idle handlers that are pending when this function is called and
    /// returns the status of the idle handler queue.
    ///
    /// (Idle handlers may schedule new idle handlers, so the queue may be
    /// non-empty after pending handlers are run.)
    pub fn check_idle_handlers(&mut self) -> QueueStatus {
        if self.idle_handlers.is_empty() {
            return QueueStatus::Empty;
        }

        let mut pending = Vec::new();
        mem::swap(&mut pending, &mut self.idle_handlers);

        for mut h in pending.into_iter() {
            (h.callback)();
        }

        if self.idle_handlers.is_empty() {
            QueueStatus::Empty
        } else {
            QueueStatus::Pending
        }
    }

    /// Returns `true` if any idle handlers need to be run.
    pub fn has_idle_handlers(&self) -> bool {
        !self.idle_handlers.is_empty()
    }
}

/// Runs each of `handlers` that should run before `now` and returns those that
/// need to be rescheduled, plus any that weren't run, in arbitrary order.
fn run_timer_handlers(
    mut handlers: Vec<Box<TimerHandler>>,
    now: Instant,
) -> Vec<Box<TimerHandler>> {
    let mut reschedule = Vec::new();
    loop {
        let Some(mut t) = handlers.pop() else {
            break;
        };
        if t.when < now {
            (t.callback)();
            if !t.delay.is_zero() {
                t.when = now + t.delay;
                reschedule.push(t);
            }
        } else {
            reschedule.push(t);
            break;
        }
    }
    reschedule.extend(handlers);
    reschedule
}

/// Tracks a callback, a time when it should be called, and an interval for
/// optionally repeating the callback.
///
/// See [`HandlerQueues::enqueue_timer`] and
/// [`HandlerQueues::enqueue_persistent_timer`] for creation of `TimerHandler`s.
struct TimerHandler {
    /// Called after `when` has been reached.
    callback: Box<dyn FnMut() + Send>,
    /// The next time that the handler should be called.
    when: Instant,
    /// If non-zero indicates that this handler should repeat every `delay` time
    /// units. This delay is best-effort only.
    delay: Duration,
}

/// Tracks a callback which should be called when the Window Maker event loop is
/// otherwise idle.
struct IdleHandler {
    callback: Box<dyn FnMut() + Send>,
}

static DEFAULT_HANDLERS: Mutex<HandlerQueues> = Mutex::new(HandlerQueues::new());

pub fn with_global_handlers<R>(f: impl FnOnce(&mut HandlerQueues) -> R) -> R {
    f(&mut DEFAULT_HANDLERS.try_lock().unwrap())
}

pub fn run_global_idle_handlers() -> QueueStatus {
    // Move handlers out of the global queue because it is locked while
    // with_global_handlers is running, and a callback may try to schedule
    // another callback or perform some other operation on the global queue.
    let handlers = with_global_handlers(|handlers| {
        let mut idle_handlers = Vec::new();
        mem::swap(&mut idle_handlers, &mut handlers.idle_handlers);
        idle_handlers
    });
    for mut h in handlers.into_iter() {
        (h.callback)();
    }
    with_global_handlers(|handlers| {
        if handlers.has_idle_handlers() {
            QueueStatus::Pending
        } else {
            QueueStatus::Empty
        }
    })
}

#[cfg(test)]
mod tests {
    use super::*;
    use std::sync::atomic;

    #[test]
    fn timers_enqueued_in_order() {
        let mut handlers = HandlerQueues::new();

        assert!(handlers.timer_handlers.is_empty());

        let now = Instant::now();
        handlers.enqueue_once_timer(Box::new(|| {}), now);
        handlers.enqueue_once_timer(Box::new(|| {}), now + Duration::from_millis(2));
        handlers.enqueue_once_timer(Box::new(|| {}), now + Duration::from_millis(1));

        assert_eq!(handlers.timer_handlers.len(), 3);
        // Timers are popped off the end, so we want things to be in descending order.
        assert_eq!(handlers.timer_handlers[2].when, now);
        assert_eq!(
            handlers.timer_handlers[1].when,
            now + Duration::from_millis(1)
        );
        assert_eq!(
            handlers.timer_handlers[0].when,
            now + Duration::from_millis(2)
        );
    }

    #[test]
    fn run_timer_handlers_remembers_unexpired() {
        let mut handlers = HandlerQueues::new();
        static COUNTER_A: atomic::AtomicI32 = atomic::AtomicI32::new(0);
        static COUNTER_B: atomic::AtomicI32 = atomic::AtomicI32::new(0);

        let now = Instant::now();
        handlers.enqueue_persistent_timer(
            Box::new(|| {
                COUNTER_A.fetch_add(1, atomic::Ordering::SeqCst);
            }),
            now,
            Duration::from_millis(10),
        );
        handlers.enqueue_persistent_timer(
            Box::new(|| {
                COUNTER_B.fetch_add(1, atomic::Ordering::SeqCst);
            }),
            now + Duration::from_millis(10),
            Duration::from_millis(10),
        );

        // No timer handlers have run.
        assert_eq!(COUNTER_A.load(atomic::Ordering::SeqCst), 0);
        assert_eq!(COUNTER_B.load(atomic::Ordering::SeqCst), 0);

        handlers.check_timer_handlers(now + Duration::from_millis(1));

        // Only the timer that goes off immediately after `now` should have run.
        assert_eq!(COUNTER_A.load(atomic::Ordering::SeqCst), 1);
        assert_eq!(COUNTER_B.load(atomic::Ordering::SeqCst), 0);

        // Both timers are still in queue.
        assert_eq!(handlers.timer_handlers.len(), 2);

        for mut t in handlers.timer_handlers.into_iter() {
            (t.callback)();
        }

        // Both values should have been incremented.
        assert_eq!(COUNTER_A.load(atomic::Ordering::SeqCst), 2);
        assert_eq!(COUNTER_B.load(atomic::Ordering::SeqCst), 1);
    }

    #[test]
    fn repeating_timer_debounce() {
        let mut handlers = HandlerQueues::new();
        static COUNTER: atomic::AtomicI32 = atomic::AtomicI32::new(0);

        let now = Instant::now();
        handlers.enqueue_persistent_timer(
            Box::new(|| {
                COUNTER.fetch_add(1, atomic::Ordering::SeqCst);
            }),
            now,
            Duration::from_millis(2),
        );

        // Timer handler hasn't run.
        assert_eq!(COUNTER.load(atomic::Ordering::SeqCst), 0);

        // Timer should run every 2 ms, but 10 ms have passed.
        handlers.check_timer_handlers(now + Duration::from_millis(10));

        // Timer should only have run once.
        assert_eq!(COUNTER.load(atomic::Ordering::SeqCst), 1);

        // Timer has been rescheduled for an appropriate time.
        assert_eq!(handlers.timer_handlers.len(), 1);
        assert_eq!(
            handlers.timer_handlers[0].when,
            now + Duration::from_millis(12)
        );
    }
}

pub mod ffi {
    use super::*;
    use crate::sendable::Sendable;
    use std::{
        ffi::{c_int, c_uint, c_void},
        mem,
        time::Instant,
    };

    #[unsafe(no_mangle)]
    pub unsafe extern "C" fn WMAddTimerHandler(
        milliseconds: c_uint,
        callback: Option<unsafe extern "C" fn(data: *mut c_void)>,
        cdata: *mut c_void,
    ) -> HandlerId {
        let cdata = unsafe { Sendable::from_nullable(cdata) };
        let callback = match callback {
            None => return HandlerId(None),
            Some(f) => Box::new(move || unsafe { f(Sendable::as_ptr(cdata)) }),
        };
        with_global_handlers(|handlers| {
            handlers.enqueue_once_timer(
                callback,
                Instant::now() + Duration::from_millis(milliseconds as u64),
            )
        })
    }

    #[unsafe(no_mangle)]
    pub unsafe extern "C" fn WMAddPersistentTimerHandler(
        milliseconds: c_uint,
        callback: Option<unsafe extern "C" fn(data: *mut c_void)>,
        cdata: *mut c_void,
    ) -> HandlerId {
        let cdata = unsafe { Sendable::from_nullable(cdata) };
        let callback = match callback {
            None => return HandlerId(None),
            Some(f) => Box::new(move || unsafe { f(Sendable::as_ptr(cdata)) }),
        };
        let delay = Duration::from_millis(milliseconds as u64);
        with_global_handlers(move |handlers| {
            handlers.enqueue_persistent_timer(callback, Instant::now() + delay, delay)
        })
    }

    #[unsafe(no_mangle)]
    pub unsafe extern "C" fn WMDeleteTimerHandler(handler_id: HandlerId) {
        with_global_handlers(|handlers| handlers.delete_timer_handler(handler_id));
    }

    #[unsafe(no_mangle)]
    pub unsafe extern "C" fn WMAddIdleHandler(
        callback: Option<unsafe extern "C" fn(data: *mut c_void)>,
        cdata: *mut c_void,
    ) -> HandlerId {
        let cdata = unsafe { Sendable::from_nullable(cdata) };
        let callback = match callback {
            None => return HandlerId(None),
            Some(f) => Box::new(move || unsafe { (f)(Sendable::as_ptr(cdata)) }),
        };
        with_global_handlers(|handlers| handlers.add_idle_handler(callback))
    }

    #[unsafe(no_mangle)]
    pub unsafe extern "C" fn WMDeleteIdleHandler(handler_id: HandlerId) {
        with_global_handlers(|handlers| handlers.delete_idle_handler(handler_id));
    }

    #[unsafe(no_mangle)]
    pub unsafe extern "C" fn W_CheckIdleHandlers() -> c_int {
        c_int::from(run_global_idle_handlers() == QueueStatus::Pending)
    }

    #[unsafe(no_mangle)]
    pub unsafe extern "C" fn W_CheckTimerHandlers() {
        let reschedule = run_timer_handlers(
            with_global_handlers(|handlers| mem::take(&mut handlers.timer_handlers)),
            Instant::now(),
        );
        with_global_handlers(|handlers| {
            for t in reschedule.into_iter() {
                handlers.enqueue_timer(t);
            }
        });
    }

    #[unsafe(no_mangle)]
    pub unsafe extern "C" fn W_DelayUntilNextTimerEvent_millis() -> c_int {
        with_global_handlers(|handlers| {
            let now = Instant::now();
            match handlers.next_timer_handler_time() {
                Some(when) if when > now => {
                    c_int::try_from((when - now).as_millis()).unwrap_or(c_int::MAX)
                }
                Some(_) => 0,
                None => -1,
            }
        })
    }
}
