use core::{mem::MaybeUninit, time::Duration};

use alloc::sync::Arc;
use device_api::{
    clock::ClockHandle,
    device::{Device, DeviceInitContext},
    dma::DmaAllocator,
    interrupt::{InterruptHandler, IrqHandle, IrqVector},
};
use device_tree::driver::{
    InitSequence, Node, ProbeContext, device_tree_driver, util::read_mac_address,
};
use futures_util::task::AtomicWaker;
use libk::{
    dma::DmaBuffer,
    error::Error,
    task::runtime::{self, with_timeout},
};
use libk_mm::device::DeviceMemoryIo;
use libk_util::{OneTimeInit, event::BitmapEvent, sync::IrqSafeSpinlock};
use tock_registers::{
    LocalRegisterCopy,
    interfaces::{ReadWriteable, Readable, Writeable},
};
use ygg_driver_net_core::{
    RxPacket,
    ephy::PhyAccess,
    interface::{NetworkDevice, NetworkInterfaceType},
    util::GenericQueue,
};
use yggdrasil_abi::net::{MacAddress, link::LinkState};

use crate::ethernet::{
    queue::Descriptor,
    regs::{ControlRegs, Regs},
};

const RX_BUF_SIZE_MUL: usize = 64;

mod queue;
mod regs;

// TODO multiqueue
struct Ethernet {
    // Device tree info
    name: &'static str,
    irq: IrqHandle,
    clk_pclk: ClockHandle,
    clk_hclk: ClockHandle,
    local_mac_address: Option<MacAddress>,

    // Memory/HW
    // caps: Capabilities,
    dma: OneTimeInit<Arc<dyn DmaAllocator>>,
    control_regs: IrqSafeSpinlock<DeviceMemoryIo<'static, ControlRegs>>,
    regs: IrqSafeSpinlock<DeviceMemoryIo<'static, Regs>>,

    // Operation
    queue: OneTimeInit<GenericQueue<Descriptor, Descriptor>>,
    softirq: BitmapEvent<AtomicWaker>,
    iface_id: OneTimeInit<u32>,
}

// bitflags! {
//     pub struct CapFlags: u32 {
//         const MACB_IS_GEM: bit 0;
//     }
// }

// struct Capabilities {
//     flags: CapFlags,
//     queue_mask: u32,
//     queue_count: usize,
// }

impl Ethernet {
    fn probe(node: &Arc<Node>, context: &mut ProbeContext) -> Option<Arc<dyn Device>> {
        let name = node.name().unwrap_or("gemgxl");
        let irq = node.interrupt(0)?;
        let clk_pclk = node.named_clock("pclk")?;
        let clk_hclk = node.named_clock("hclk")?;
        let base = node.map_base(context, 0)?;
        let base_control = node.map_base(context, 1)?;
        let local_mac_address = read_mac_address(node);

        let regs = unsafe { DeviceMemoryIo::<Regs>::map(base, Default::default()) }.ok()?;
        let control_regs = unsafe { DeviceMemoryIo::map(base_control, Default::default()) }.ok()?;

        // let caps = regs.probe_capabilities();

        let gem = Arc::new(Ethernet {
            name,
            clk_pclk,
            clk_hclk,
            irq,
            local_mac_address,

            // caps,
            dma: OneTimeInit::new(),
            regs: IrqSafeSpinlock::new(regs),
            control_regs: IrqSafeSpinlock::new(control_regs),

            queue: OneTimeInit::new(),
            softirq: BitmapEvent::new(AtomicWaker::new()),
            iface_id: OneTimeInit::new(),
        });

        context.sequence = Some(InitSequence::Late);

        Some(gem)
    }

    async fn softirq(self: Arc<Self>) {
        let iface = *self.iface_id.get();
        let queue = self.queue.get();

        loop {
            let event_fut = self.softirq.wait();
            let bits = match with_timeout(event_fut, Duration::from_millis(100)).await {
                Ok(bits) => bits as u32,
                Err(_) => {
                    queue.consume_tx().ok();
                    continue;
                }
            };
            let status = LocalRegisterCopy::<_, regs::Interrupt::Register>::new(bits);

            if status.matches_all(regs::Interrupt::RCOMP::SET) {
                queue
                    .consume_rx(&**self.dma.get(), None, |packet, _len| {
                        let packet = RxPacket::new(packet, 0, iface);
                        ygg_driver_net_core::receive_packet(packet).ok();
                    })
                    .ok();

                let regs = self.regs.lock();
                regs.interrupt_enable.write(regs::Interrupt::RCOMP::SET);
            }
            if status.matches_all(regs::Interrupt::TCOMP::SET) {
                queue.consume_tx().ok();

                let regs = self.regs.lock();
                regs.interrupt_enable.write(regs::Interrupt::RCOMP::SET);
            }
            if status.matches_all(regs::Interrupt::LINK::SET) {
                log::info!("Link status changed");
                let regs = self.regs.lock();
                regs.interrupt_enable.write(regs::Interrupt::LINK::SET);
            }
        }
    }

    fn start_xmit(&self, frame: DmaBuffer<[u8]>) -> Result<(), Error> {
        let queue = self.queue.get();

        queue.try_push_xmit(frame)?;

        let regs = self.regs.lock();
        regs.network_control
            .modify(regs::NetworkControl::TSTART::SET);

        Ok(())
    }
}

impl Device for Ethernet {
    unsafe fn init(self: Arc<Self>, cx: DeviceInitContext) -> Result<(), Error> {
        let _ = (&self.clk_pclk, &self.clk_hclk, &self.control_regs);
        log::info!("{}: hw address {:?}", self.name, self.local_mac_address);
        // TODO enable clocks
        let dma = self.dma.init(cx.dma_allocator);
        let regs = self.regs.lock();

        let queue = self.queue.init(GenericQueue::with_capacity(
            &**dma,
            256,
            256,
            queue::RX_BUFFER_SIZE,
        )?);

        let rx_queue_base = queue.rx_buffer_base().try_into_u32().unwrap();
        let tx_queue_base = queue.tx_buffer_base().try_into_u32().unwrap();

        regs.interrupt_disable.set(0xFFFFFFFF);

        regs.rx_buffer_queue_base.set(rx_queue_base);
        regs.tx_buffer_queue_base.set(tx_queue_base);
        regs.dma_config.write(
            regs::DmaConfig::RX_BUF_SIZE.val((queue::RX_BUFFER_SIZE / RX_BUF_SIZE_MUL) as _),
        );

        regs.network_control.modify(regs::NetworkControl::MPE::SET);

        {
            let phy = PhyAccess::new(&**regs, 0);
            let phy_id = phy.id()?;
            log::info!("{}: PHY {:04x}:{:04x}", self.name, phy_id.0, phy_id.1);
            phy.setup_link(false, None)?;
        }

        regs.network_config.write(regs::NetworkConfig::PROMISC::SET);
        regs.network_control
            .modify(regs::NetworkControl::TE::SET + regs::NetworkControl::RE::SET);

        // if (GEM_BFEXT(DAW64, gem_readl(...)))
        //      hw_dma_cap |= HW_DMA_CAP_64B

        regs.interrupt_enable.write(
            regs::Interrupt::LINK::SET + regs::Interrupt::RCOMP::SET + regs::Interrupt::TCOMP::SET,
        );

        let iface =
            ygg_driver_net_core::register_interface(NetworkInterfaceType::Ethernet, self.clone());
        self.iface_id.init(iface.id());

        runtime::spawn(self.clone().softirq())?;

        Ok(())
    }

    unsafe fn init_irq(self: Arc<Self>) -> Result<(), Error> {
        self.irq.register(self.clone())?;
        self.irq.enable()?;
        Ok(())
    }

    fn display_name(&self) -> &str {
        self.name
    }
}

impl NetworkDevice for Ethernet {
    fn allocate_transmit_buffer(&self, len: usize) -> Result<DmaBuffer<[MaybeUninit<u8>]>, Error> {
        DmaBuffer::new_uninit_slice(&**self.dma.get(), len)
    }

    fn transmit_buffer(&self, buffer: DmaBuffer<[u8]>) -> Result<(), Error> {
        self.start_xmit(buffer)
    }

    fn packet_prefix_size(&self) -> usize {
        0
    }

    fn read_hardware_address(&self) -> MacAddress {
        // TODO from registers
        self.local_mac_address.unwrap()
    }

    fn link_state(&self) -> LinkState {
        self.regs.lock().link_state()
    }
}

impl InterruptHandler for Ethernet {
    fn handle_irq(self: Arc<Self>, _vector: IrqVector) -> bool {
        const MASK: u32 = regs::Interrupt::RCOMP::SET.value
            | regs::Interrupt::TCOMP::SET.value
            | regs::Interrupt::LINK::SET.value;

        let regs = self.regs.lock();
        let status = regs.interrupt_status.get();
        // Mask the IRQ and send it to softirq handler
        regs.interrupt_disable.set(MASK & status);
        self.softirq.signal(status as u64);

        status != 0
    }
}

device_tree_driver! {
    compatible: ["sifive,fu540-c000-gem"],
    driver: {
        fn probe(&self, node: &Arc<Node>, context: &mut ProbeContext) -> Option<Arc<dyn Device>> {
            Ethernet::probe(node, context)
        }
    }
}
