use core::{
    sync::atomic::{AtomicU64, Ordering},
    time::Duration,
};

use alloc::{boxed::Box, collections::BTreeMap, sync::Arc, vec::Vec};

use device_api::gpio::{GpioInterrupt, GpioInterruptEvent, GpioOutput, PinHandle};
use libk_util::{ring::LossyRingQueue, sync::spin_rwlock::IrqSafeRwLock};
use yggdrasil_abi::error::Error;

use crate::task::runtime;

static GPIO_EVENT_QUEUE: LossyRingQueue<u64> = LossyRingQueue::with_capacity(64);
static GPIO_EVENT_MAP: IrqSafeRwLock<BTreeMap<u64, Box<dyn Fn() + Send>>> =
    IrqSafeRwLock::new(BTreeMap::new());
static GPIO_EVENT_ID: AtomicU64 = AtomicU64::new(1);

static GPIO_HEARTBEATS: IrqSafeRwLock<Vec<Arc<dyn GpioOutput>>> = IrqSafeRwLock::new(Vec::new());

pub trait GpioEvent {
    fn configure_with_interrupt(&self, int: GpioInterrupt) -> Result<(), Error>;
}

impl GpioEvent for PinHandle {
    fn configure_with_interrupt(&self, int: GpioInterrupt) -> Result<(), Error> {
        let event = bind_gpio_event(int.handler);
        self.parent.setup_gpio(
            self,
            Some(GpioInterruptEvent {
                mode: int.mode,
                event,
            }),
        )
    }
}

pub async fn gpio_event_handler_task() {
    loop {
        let id = GPIO_EVENT_QUEUE.read().await;

        let lock = GPIO_EVENT_MAP.read();
        if let Some(handler) = lock.get(&id) {
            handler();
        }
    }
}

pub async fn heartbeat_handler_task() {
    const LEVELS: &[(Duration, bool)] = &[
        (Duration::from_millis(50), true),
        (Duration::from_millis(200), false),
        (Duration::from_millis(50), true),
        (Duration::from_millis(700), false),
    ];
    let mut index = 0;
    loop {
        let (timeout, level) = LEVELS[index];

        for pin in GPIO_HEARTBEATS.read().iter() {
            pin.write(level).ok();
        }
        runtime::sleep(timeout).await;

        index = (index + 1) % LEVELS.len();
    }
}

pub fn register_heartbeat_gpio(gpio: Arc<dyn GpioOutput>) {
    GPIO_HEARTBEATS.write().push(gpio);
}

pub fn bind_gpio_event(handler: Box<dyn Fn() + Send>) -> u64 {
    let id = GPIO_EVENT_ID.fetch_add(1, Ordering::Acquire);
    GPIO_EVENT_MAP.write().insert(id, handler);
    id
}

pub fn signal_gpio_event(ev: u64) {
    GPIO_EVENT_QUEUE.write(ev);
}
