use core::mem::size_of;

use alloc::sync::Arc;
use bytemuck::Pod;
use libk::dma::DmaBuffer;
use yggdrasil_abi::{
    error::Error,
    net::{
        protocols::{EtherType, EthernetFrame},
        types::NetValueImpl,
        MacAddress,
    },
};

use crate::{interface::NetworkInterface, l3, socket::RawSocket};

#[derive(Clone)]
pub struct L2Packet {
    pub interface_id: u32,

    pub source_address: MacAddress,
    pub destination_address: MacAddress,

    pub l2_offset: usize,
    pub l3_offset: usize,

    pub data: Arc<DmaBuffer<[u8]>>,
}

impl L2Packet {
    pub fn ethernet_frame(&self) -> &EthernetFrame {
        bytemuck::from_bytes(
            &self.data[self.l2_offset..self.l2_offset + size_of::<EthernetFrame>()],
        )
    }

    pub fn l2_data(&self) -> &[u8] {
        &self.data[self.l3_offset..]
    }
}

pub fn send_l2<T: Pod>(
    interface: &NetworkInterface,
    source_mac: MacAddress,
    destination_mac: MacAddress,
    ethertype: EtherType,
    l2_data: &T,
) -> Result<(), Error> {
    let l2_frame = EthernetFrame {
        source_mac,
        destination_mac,
        ethertype: ethertype.to_network_order(),
    };

    log::debug!(
        "send_l2: {} -> {}",
        l2_frame.source_mac,
        l2_frame.destination_mac
    );

    interface.send_l2(&l2_frame, bytemuck::bytes_of(l2_data))
}

pub fn handle(packet: L2Packet) {
    let frame = packet.ethernet_frame();
    let ty = EtherType::from_network_order(frame.ethertype);

    RawSocket::handle_rx(packet.clone());

    match ty {
        EtherType::ARP => l3::arp::handle_packet(packet),
        EtherType::IPV4 => l3::ip::handle_v4_packet(packet),
        EtherType(0x0027) => (),
        p => {
            log::debug!(
                "Unrecognized L2 protocol: {:#06x}",
                bytemuck::cast::<_, u16>(p)
            );
        }
    }
}
