#![allow(non_snake_case)]
use core::time::Duration;

use libk::{
    error::Error,
    task::runtime::{psleep, pwait},
};
use libk_mm::{address::PhysicalAddress, device::RawDeviceMemoryMapping};
use tock_registers::{fields::FieldValue, register_bitfields, LocalRegisterCopy, RegisterLongName};
use ygg_driver_net_core::ephy::{MdioBus, PhyAccess, GBESR};
use ygg_driver_pci::PciBaseAddress;
use yggdrasil_abi::net::{
    link::{Duplex, EthernetLinkState, EthernetSpeed},
    MacAddress,
};

use crate::{RxRing, TxRing};

enum Inner {
    Memory(RawDeviceMemoryMapping),
    // TODO I/O register access not yet implemented
    #[allow(unused)]
    Io(u16),
}

enum Eeprom {
    // TODO not yet handled
    #[allow(unused)]
    Present,
    MappedMemory(RawDeviceMemoryMapping),
    // TODO I/O register access not yet implemented
    #[allow(unused)]
    MappedIo(u16),
}

pub struct Regs {
    inner: Inner,
    eeprom: Eeprom,
}

pub trait Reg {
    const OFFSET: u16;
}

#[derive(Debug, Clone, Copy, PartialEq)]
pub enum Revision {
    I8254x,
    I82574L,
    I82576,
    I82579LM,
}

register_bitfields! {
    u32,
    pub CTRL [
        SLU     OFFSET(6)   NUMBITS(1)  [],
        RST     OFFSET(26)  NUMBITS(1)  [],
        RFCE    OFFSET(27)  NUMBITS(1)  [],
        TFCE    OFFSET(28)  NUMBITS(1)  [],
    ],
    pub STATUS [
        FD      OFFSET(0)   NUMBITS(1)  [],
        LU      OFFSET(1)   NUMBITS(1)  [],
        SPEED   OFFSET(6)   NUMBITS(2)  [],
    ],
    pub MDIC [
        DATA    OFFSET(0)   NUMBITS(16) [],
        REGADD  OFFSET(16)  NUMBITS(5)  [],
        PHYADD  OFFSET(21)  NUMBITS(5)  [],
        OP      OFFSET(26)  NUMBITS(2)  [
            Write = 0b01,
            Read = 0b10
        ],
        R       OFFSET(28)  NUMBITS(1)  [],
        I       OFFSET(29)  NUMBITS(1)  [],
        E       OFFSET(30)  NUMBITS(1)  [],
    ],
    pub ICR [
        TXDW    OFFSET(0)   NUMBITS(1)  [],
        TXQE    OFFSET(1)   NUMBITS(1)  [],
        LSC     OFFSET(2)   NUMBITS(1)  [],
        RXT0    OFFSET(7)   NUMBITS(1)  [],
    ],
    pub IMS [
        TXDW    OFFSET(0)   NUMBITS(1)  [],
        TXQE    OFFSET(1)   NUMBITS(1)  [],
        LSC     OFFSET(2)   NUMBITS(1)  [],
        RXT0    OFFSET(7)   NUMBITS(1)  [],
    ],
    pub RCTL [
        EN      OFFSET(1)   NUMBITS(1)  [],
        UPE     OFFSET(3)   NUMBITS(1)  [],
        MPE     OFFSET(4)   NUMBITS(1)  [],
        BAM     OFFSET(15)  NUMBITS(1)  [],
        BSIZE   OFFSET(16)  NUMBITS(2)  [
            SIZE_2048 = 0b00,
            SIZE_1024_16384 = 0b01,
            SIZE_512_8192 = 0b10,
            SIZE_256_4096 = 0b11
        ],
        BSEX    OFFSET(25)  NUMBITS(1)  [],
    ],
    pub RDLEN0 [
        LEN0    OFFSET(7)   NUMBITS(13) [],
    ],
    pub CPUVEC [
        CPUVEC  OFFSET(0)   NUMBITS(32) [],
    ],
    pub TCTL [
        EN      OFFSET(1)   NUMBITS(1)  [],
        PSP     OFFSET(3)   NUMBITS(1)  [],
        CT      OFFSET(4)   NUMBITS(8)  [],
        COLD    OFFSET(12)  NUMBITS(10) [],
        MULR    OFFSET(28)  NUMBITS(1)  [],
    ],
    pub TDLEN [
        LEN     OFFSET(7)   NUMBITS(13) [],
    ],
    pub TIDV [
        IDV     OFFSET(0)   NUMBITS(16) [],
    ],
    pub TXDCTL [
        PTHRESH     OFFSET(0)   NUMBITS(6)  [],
        HTHRESH     OFFSET(8)   NUMBITS(8)  [],
        WTHRESH     OFFSET(16)  NUMBITS(6)  [],
        GRAN        OFFSET(24)  NUMBITS(1)  [],
        LWTHRESH    OFFSET(25)  NUMBITS(7)  [],
    ],
    pub TADV [
        IDV     OFFSET(0)   NUMBITS(16) [],
    ],
}
pub mod IMC {
    pub struct Register;
}
pub mod RDBAL0 {
    pub struct Register;
}
pub mod RDBAH0 {
    pub struct Register;
}
pub mod RDH0 {
    pub struct Register;
}
pub mod RDT0 {
    pub struct Register;
}
pub mod TIPG {
    pub struct Register;
}
pub mod TDBAL {
    pub struct Register;
}
pub mod TDBAH {
    pub struct Register;
}
pub mod TDH {
    pub struct Register;
}
pub mod TDT {
    pub struct Register;
}
pub mod IVAR {
    pub struct Register;
}

impl Reg for CTRL::Register {
    const OFFSET: u16 = 0x0000;
}
impl Reg for STATUS::Register {
    const OFFSET: u16 = 0x0008;
}
impl Reg for MDIC::Register {
    const OFFSET: u16 = 0x0020;
}
impl Reg for ICR::Register {
    const OFFSET: u16 = 0x00C0;
}
impl Reg for IMS::Register {
    const OFFSET: u16 = 0x00D0;
}
impl Reg for IMC::Register {
    const OFFSET: u16 = 0x00D8;
}
impl Reg for RCTL::Register {
    const OFFSET: u16 = 0x0100;
}
impl Reg for RDBAL0::Register {
    const OFFSET: u16 = 0x2800;
}
impl Reg for RDBAH0::Register {
    const OFFSET: u16 = 0x2804;
}
impl Reg for RDLEN0::Register {
    const OFFSET: u16 = 0x2808;
}
impl Reg for RDH0::Register {
    const OFFSET: u16 = 0x2810;
}
impl Reg for RDT0::Register {
    const OFFSET: u16 = 0x2818;
}
impl Reg for CPUVEC::Register {
    const OFFSET: u16 = 0x2C10;
}
impl Reg for TCTL::Register {
    const OFFSET: u16 = 0x0400;
}
impl Reg for TIPG::Register {
    const OFFSET: u16 = 0x0410;
}
impl Reg for TDBAL::Register {
    const OFFSET: u16 = 0x3800;
}
impl Reg for TDBAH::Register {
    const OFFSET: u16 = 0x3804;
}
impl Reg for TDLEN::Register {
    const OFFSET: u16 = 0x3808;
}
impl Reg for TDH::Register {
    const OFFSET: u16 = 0x3810;
}
impl Reg for TDT::Register {
    const OFFSET: u16 = 0x3818;
}
impl Reg for TIDV::Register {
    const OFFSET: u16 = 0x3820;
}
impl Reg for TXDCTL::Register {
    const OFFSET: u16 = 0x3828;
}
impl Reg for TADV::Register {
    const OFFSET: u16 = 0x382C;
}
impl Reg for IVAR::Register {
    const OFFSET: u16 = 0x1700;
}

impl Inner {
    unsafe fn map(bar: PciBaseAddress) -> Result<Self, Error> {
        let memory_base = match bar {
            PciBaseAddress::Memory64(base) => PhysicalAddress::from_u64(base),
            PciBaseAddress::Memory32(base) => PhysicalAddress::from_u32(base),
            PciBaseAddress::Io(io_base) => {
                return Ok(Self::Io(io_base));
            }
        };

        unsafe {
            RawDeviceMemoryMapping::map(memory_base.into_u64(), 0x4000, Default::default())
                .map(Self::Memory)
        }
    }

    fn get<R: Reg>(&self) -> u32 {
        match self {
            Self::Memory(mapping) => {
                let ptr: *const u32 =
                    core::ptr::with_exposed_provenance(mapping.address + R::OFFSET as usize);
                unsafe { ptr.read_volatile() }
            }
            Self::Io(_io_base) => todo!(),
        }
    }

    fn extract<R: Reg + RegisterLongName>(&self) -> LocalRegisterCopy<u32, R> {
        LocalRegisterCopy::new(self.get::<R>())
    }

    fn write<R: Reg + RegisterLongName>(&self, value: FieldValue<u32, R>) {
        self.set::<R>(value.value);
    }

    fn set<R: Reg>(&self, value: u32) {
        match self {
            Self::Memory(mapping) => {
                let ptr: *mut u32 =
                    core::ptr::with_exposed_provenance_mut(mapping.address + R::OFFSET as usize);
                unsafe { ptr.write_volatile(value) };
            }
            Self::Io(_io_base) => todo!(),
        }
    }

    fn modify<R: Reg + RegisterLongName>(&self, modify: FieldValue<u32, R>) {
        let mut value = self.get::<R>();
        value &= !modify.mask();
        value |= modify.value;
        self.set::<R>(value);
    }

    fn matches_all<R: Reg + RegisterLongName>(&self, pattern: FieldValue<u32, R>) -> bool {
        pattern.matches_all(self.get::<R>())
    }

    fn matches_any<R: Reg + RegisterLongName>(&self, pattern: &[FieldValue<u32, R>]) -> bool {
        let value = self.get::<R>();
        for pattern in pattern {
            if pattern.matches_all(value) {
                return true;
            }
        }
        false
    }
}

impl Eeprom {
    fn read(&self, _inner: &mut Inner, addr: u8) -> u16 {
        match self {
            Self::Present => todo!(),
            Self::MappedMemory(mapping) => {
                // EEPROM registers are read in words, not bytes/halves
                let shift = (addr as usize & 1) << 4;
                let reg = (addr as usize & !1) << 1;
                let ptr: *const u32 = core::ptr::with_exposed_provenance(mapping.address + reg);
                // This is safe, region is mapped as device memory
                let word = unsafe { ptr.read_volatile() };
                (word >> shift) as u16
            }
            &Self::MappedIo(_io_base) => todo!(),
        }
    }
}

impl MdioBus for Regs {
    fn mii_read(&self, phyaddr: u8, regaddr: u8) -> Result<u16, Error> {
        self.inner.write(
            MDIC::PHYADD.val(phyaddr as u32) + MDIC::REGADD.val(regaddr as u32) + MDIC::OP::Read,
        );

        pwait(
            Duration::from_millis(100),
            Duration::from_millis(10),
            || self.inner.matches_any(&[MDIC::R::SET, MDIC::E::SET]),
        )?;

        let mdic = self.inner.extract();
        if mdic.matches_all(MDIC::E::SET) {
            log::warn!("MDIO read error: phyaddr={phyaddr:#x}, regaddr={regaddr:#x}");
            return Err(Error::InvalidOperation);
        }

        Ok(mdic.read(MDIC::DATA) as u16)
    }

    fn mii_write(&self, phyaddr: u8, regaddr: u8, value: u16) -> Result<(), Error> {
        self.inner.write(
            MDIC::PHYADD.val(phyaddr as u32)
                + MDIC::REGADD.val(regaddr as u32)
                + MDIC::DATA.val(value as u32)
                + MDIC::OP::Write,
        );

        pwait(
            Duration::from_millis(100),
            Duration::from_millis(10),
            || self.inner.matches_any(&[MDIC::R::SET, MDIC::E::SET]),
        )?;

        if self.inner.matches_all(MDIC::E::SET) {
            log::warn!(
                "MDIO write error: phyaddr={phyaddr:#x}, regaddr={regaddr:#x}, value={value:#x}"
            );
            return Err(Error::InvalidOperation);
        }

        Ok(())
    }
}

impl Regs {
    pub unsafe fn map(bar: PciBaseAddress) -> Result<Self, Error> {
        let inner = unsafe { Inner::map(bar) }?;

        // Map EEPROM registers
        let eeprom = match &inner {
            Inner::Memory(mapping) => {
                let base = mapping.physical_base + ((mapping.address & 0xFFF) + 0x5400) as u64;
                let mapping =
                    unsafe { RawDeviceMemoryMapping::map(base, 0x10, Default::default()) }?;
                Eeprom::MappedMemory(mapping)
            }
            &Inner::Io(_io_base) => todo!(),
        };

        Ok(Self { inner, eeprom })
    }

    fn read_eeprom(&mut self, addr: u8) -> u16 {
        self.eeprom.read(&mut self.inner, addr)
    }

    pub fn read_mac(&mut self) -> Result<MacAddress, Error> {
        let w0 = self.read_eeprom(0);
        let w1 = self.read_eeprom(1);
        let w2 = self.read_eeprom(2);
        if w0 == 0 && w1 == 0 && w2 == 0 {
            log::error!("Could not read EEPROM MAC address");
            return Err(Error::InvalidArgument);
        }
        let w0 = w0.to_le_bytes();
        let w1 = w1.to_le_bytes();
        let w2 = w2.to_le_bytes();

        let octets = [w0[0], w0[1], w1[0], w1[1], w2[0], w2[1]];
        Ok(MacAddress::from(octets))
    }

    pub fn reset(&mut self, timeout: Duration) -> Result<(), Error> {
        self.inner.write(CTRL::RST::SET);
        psleep(Duration::from_millis(10));
        pwait(timeout, Duration::from_millis(10), || {
            self.inner.matches_all(CTRL::RST::CLEAR)
        })
    }

    pub fn set_link_up(&mut self, chip: Revision) -> Result<(), Error> {
        self.inner
            .modify(CTRL::SLU::SET + CTRL::RFCE::SET + CTRL::TFCE::SET);

        let phy = PhyAccess::new(self, 0x01);

        let (id0, id1) = phy.id()?;
        log::info!("PHY {:04x}:{:04x}", id0, id1);

        phy.reset(Duration::from_millis(200))
            .inspect_err(|e| log::error!("PHY reset error {e:?}"))?;
        let force_gbesr = match chip {
            Revision::I82576 | Revision::I82579LM => Some(GBESR::empty()),
            _ => None,
        };
        phy.setup_link(true, force_gbesr)
            .inspect_err(|e| log::error!("PHY setup error: {e:?}"))?;

        Ok(())
    }

    pub fn read_link(&mut self) -> EthernetLinkState {
        let status = self.inner.extract::<STATUS::Register>();

        if status.matches_all(STATUS::LU::SET) {
            let duplex = if status.matches_all(STATUS::FD::SET) {
                Duplex::Full
            } else {
                Duplex::Half
            };

            let speed = match status.read(STATUS::SPEED) {
                0b00 => EthernetSpeed::Speed10,
                0b01 => EthernetSpeed::Speed100,
                0b10 | 0b11 => EthernetSpeed::Speed1000,
                _ => unreachable!(),
            };

            EthernetLinkState::Up(speed, duplex)
        } else {
            EthernetLinkState::Down
        }
    }

    pub fn disable_interrupts(&mut self) {
        self.inner.set::<IMC::Register>(0xFFFFFFFF);
    }

    pub fn clear_interrupts(&mut self, cause: u32) {
        let _ = self.inner.get::<CPUVEC::Register>();
        self.inner.set::<ICR::Register>(cause);
    }

    pub fn enable_interrupts(&mut self) {
        self.inner.set::<IMS::Register>(0xFFFFFFFF);
        // self.inner
        //     .modify(IMS::LSC::SET + IMS::RXT0::SET + IMS::TXDW::SET + IMS::TXQE::SET);
    }

    pub fn interrupt_cause(&mut self) -> LocalRegisterCopy<u32, ICR::Register> {
        self.inner.extract()
    }

    pub fn initialize_receiver(&mut self, rx_ring: &RxRing) {
        let rx_queue_base = rx_ring.descriptors.bus_address().into_u64();
        self.inner.set::<RDBAL0::Register>(rx_queue_base as u32);
        self.inner
            .set::<RDBAH0::Register>((rx_queue_base >> 32) as u32);

        self.inner
            .write(RDLEN0::LEN0.val((rx_ring.descriptors.len() / 8) as u32));
        self.inner.set::<RDH0::Register>(0);
        self.inner
            .set::<RDT0::Register>((rx_ring.descriptors.len() - 1) as u32);

        self.inner.write(
            RCTL::EN::SET
                + RCTL::BSIZE::SIZE_2048
                + RCTL::BAM::SET
                + RCTL::UPE::SET
                + RCTL::MPE::SET,
        );
    }

    pub fn initialize_transmitter(&mut self, tx_ring: &TxRing) {
        let tx_queue_base = tx_ring.descriptors.bus_address().into_u64();
        self.inner.set::<TDBAL::Register>(tx_queue_base as u32);
        self.inner
            .set::<TDBAH::Register>((tx_queue_base >> 32) as u32);

        self.inner
            .write(TDLEN::LEN.val((tx_ring.descriptors.len() / 8) as u32));
        self.inner.set::<TDT::Register>(0);
        self.inner.set::<TDH::Register>(0);

        self.inner.set::<TIPG::Register>(0x0060200A);
        self.inner.write(TIDV::IDV.val(100));
        self.inner.write(TADV::IDV.val(100));
        self.inner
            .write(TCTL::EN::SET + TCTL::CT.val(15) + TCTL::COLD.val(63) + TCTL::PSP::SET);
    }

    pub fn initialize_ivar(&mut self, vector: usize) {
        // Setup vector info for Tx/Rx queues 0
        let mut val = 0;
        for i in 0..4 {
            val |= (0x80 | (vector as u32 & 0xFF)) << (8 * i);
        }
        self.inner.set::<IVAR::Register>(val);
    }

    pub fn rx_queue_head(&mut self) -> u16 {
        self.inner.get::<RDH0::Register>() as u16
    }

    pub fn set_rx_queue_tail(&mut self, tail: u16) {
        self.inner.set::<RDT0::Register>(tail as u32);
    }

    pub fn tx_queue_head(&mut self) -> u16 {
        self.inner.get::<TDH::Register>() as u16
    }

    pub fn set_tx_queue_tail(&mut self, tail: u16) {
        self.inner.set::<TDT::Register>(tail as u32);
    }
}
