use std::future::Future;
use std::marker::PhantomPinned;
use std::pin::{Pin, pin};
use std::task::{Context, Poll};
use std::time::{Duration, Instant};

use dope::fiber::Fiber;
use dope::runtime::park::{self, Parker};
use dope::runtime::token::{Epoch, LocalIdx, Token};
use dope::{Cqe, Dispatcher, Drive, Driver, Executor, Idle};
use pin_project::pin_project;

const ONESHOT_ROUTE: u8 = 255;
const PARK_CEILING: Duration = Duration::from_secs(1);

pub(crate) fn block_on<D: Dispatcher, F: Future>(
    exec: &mut Executor,
    mut dispatcher: Pin<&mut D>,
    fiber: Fiber<'_, F>,
) -> F::Output {
    let driver = exec.driver_mut();
    let mut one_shot = pin!(OneShot::new(fiber, ONESHOT_ROUTE, driver));
    let mut buf = [Cqe::ZERO; 256];
    let mut wake_buf: Vec<Token> = Vec::with_capacity(64);
    loop {
        let n = driver.drain(&mut buf);
        for cqe in &buf[..n] {
            if cqe.route() != ONESHOT_ROUTE
                && let Ok(ev) = dope::Event::try_from(*cqe)
            {
                Dispatcher::dispatch(dispatcher.as_mut(), ev, driver);
            }
        }
        wake_buf.clear();
        Parker::drain(driver, &mut wake_buf);
        for target in &wake_buf {
            if target.route() != ONESHOT_ROUTE {
                Dispatcher::on_wake(dispatcher.as_mut(), *target, driver);
            }
        }
        one_shot.as_mut().pre_park(driver);
        Dispatcher::pre_park(dispatcher.as_mut(), driver);
        if one_shot.as_ref().is_done() {
            return one_shot
                .as_mut()
                .take_output()
                .expect("OneShot reported done");
        }
        let timeout = if n == buf.len() || !Parker::is_empty(driver) {
            Duration::ZERO
        } else {
            match Dispatcher::idle(dispatcher.as_ref()) {
                Idle::Busy => Duration::ZERO,
                Idle::Park(None) => PARK_CEILING,
                Idle::Park(Some(d)) => {
                    PARK_CEILING.min(d.saturating_duration_since(Instant::now()))
                }
            }
        };
        let _ = driver.park(timeout);
    }
}

#[pin_project]
struct OneShot<F: Future> {
    #[pin]
    fut: Option<F>,
    output: Option<F::Output>,
    slot: park::Slot,
    _pin: PhantomPinned,
}

impl<F: Future> OneShot<F> {
    fn new(fut: F, route: u8, driver: &Driver) -> Self {
        let target = Token::new(route, LocalIdx::new(0), Epoch::INITIAL);
        Self {
            fut: Some(fut),
            output: None,
            slot: Parker::make_slot(driver, target),
            _pin: PhantomPinned,
        }
    }

    fn is_done(&self) -> bool {
        self.fut.is_none()
    }

    fn take_output(self: Pin<&mut Self>) -> Option<F::Output> {
        self.project().output.take()
    }

    fn pre_park(self: Pin<&mut Self>, _driver: &mut Driver) {
        let mut this = self.project();
        let Some(fut) = this.fut.as_mut().as_pin_mut() else {
            return;
        };
        let waker = this.slot.make_waker();
        let mut cx = Context::from_waker(&waker);
        if let Poll::Ready(v) = fut.poll(&mut cx) {
            *this.output = Some(v);
            this.fut.set(None);
        }
    }
}
