//! Ethernet PHY utilities

use core::time::Duration;

use libk::{
    error::Error,
    task::runtime::{psleep, pwait_try},
};
use yggdrasil_abi::bitflags;

bitflags! {
    pub struct BMCR: u16 {
        const RESET: bit 15;
        const LOOPBACK: bit 14;
        const SPEED0: bit 13;
        const ANE: bit 12;
        const PWD: bit 11;
        const ISOLATE: bit 10;
        const RESTART_AN: bit 9;
        const DUPLEX: bit 8;
        const SPEED1: bit 6;
    }
}

bitflags! {
    pub struct BMSR: u16 {
        const HAVE_100BASET4: bit 15;
        const HAVE_100BASETX_FULL: bit 14;
        const HAVE_100BASETX_HALF: bit 13;
        const HAVE_10BASET_FULL: bit 12;
        const HAVE_10BASET_HALF: bit 11;
        const HAVE_100BASET2_FULL: bit 10;
        const HAVE_100BASET2_HALF: bit 9;
        const EXT_STATUS_1000BASET: bit 8;
        const PREAMBLE_SUPP: bit 6;
        const AN_COMPLETE: bit 5;
        const REMOTE_FAULT: bit 4;
        const AN_AVAILABLE: bit 3;
        const LINK_STATUS: bit 2;
        const JABBER: bit 1;
        const EXTENDED_CAP: bit 0;
    }
}

bitflags! {
    pub struct ANAR: u16 {
        const HAVE_10BASET: bit 5;
        const HAVE_10BASET_FULL: bit 6;
        const HAVE_100BASETX: bit 7;
        const HAVE_100BASETX_FULL: bit 8;
        const HAVE_100BASET4: bit 9;
        const HAVE_PAUSE: bit 10;
        const ASM_DIR: bit 11;
        const REMOTE_FAULT: bit 13;
        const NEXT_PAGE: bit 15;
    }
}

bitflags! {
    pub struct GBCR: u16 {
        const HAVE_1000BASET_HALF: bit 8;
        const HAVE_1000BASET_FULL: bit 9;
    }
}

bitflags! {
    pub struct GBESR: u16 {
        const HAVE_1000BASET_HALF: bit 12;
        const HAVE_1000BASET_FULL: bit 13;
    }
}

pub trait MdioBus {
    fn mii_read(&self, phyaddr: u8, regaddr: u8) -> Result<u16, Error>;
    fn mii_write(&self, phyaddr: u8, regaddr: u8, value: u16) -> Result<(), Error>;
}

pub struct PhyAccess<'a, M: MdioBus> {
    address: u8,
    mii: &'a M,
}

pub const REG_BMCR: u8 = 0x00;
pub const REG_BMSR: u8 = 0x01;
pub const REG_PHYAD1: u8 = 0x02;
pub const REG_PHYAD2: u8 = 0x03;
pub const REG_ANAR: u8 = 0x04;
pub const REG_GBCR: u8 = 0x09;
pub const REG_GBESR: u8 = 0x0F;

pub const ANAR_SELECTOR_802_3: u16 = 0b00001;
pub const ANAR_SELECTOR_MASK: u16 = 0b11111;

impl<'a, M: MdioBus> PhyAccess<'a, M> {
    pub fn new(mii: &'a M, address: u8) -> Self {
        Self { address, mii }
    }

    pub fn probe(_mii: &'a M, _address: u8) -> Result<Self, Error> {
        todo!()
    }

    pub fn read_reg(&self, reg: u8) -> Result<u16, Error> {
        self.mii.mii_read(self.address, reg)
    }

    pub fn write_reg(&self, reg: u8, value: u16) -> Result<(), Error> {
        self.mii.mii_write(self.address, reg, value)
    }

    pub fn status(&self) -> Result<BMSR, Error> {
        self.read_reg(REG_BMSR).map(BMSR::from)
    }

    pub fn id(&self) -> Result<(u16, u16), Error> {
        let id0 = self.read_reg(REG_PHYAD1)?;
        let id1 = self.read_reg(REG_PHYAD2)?;
        Ok((id0, id1))
    }

    pub fn reset(&self, timeout: Duration) -> Result<(), Error> {
        self.write_reg(REG_BMCR, BMCR::RESET.bits())?;
        pwait_try(timeout, Duration::from_millis(10), || {
            Ok(self.read_reg(REG_BMCR)? & BMCR::RESET.bits() == 0)
        })
    }

    pub fn setup_link(&self, have_pause: bool, force_gbesr: Option<GBESR>) -> Result<(), Error> {
        let bmsr = BMSR::from(self.read_reg(REG_BMSR)?);
        let gbesr = if let Some(force_gbesr) = force_gbesr {
            let mut gbesr = if bmsr.contains(BMSR::EXT_STATUS_1000BASET) {
                GBESR::from(self.read_reg(REG_GBESR)?)
            } else {
                GBESR::empty()
            };
            gbesr |= force_gbesr;
            Some(gbesr)
        } else {
            None
        };
        let mut anar = ANAR::from_capabilities(bmsr);
        if have_pause {
            anar |= ANAR::HAVE_PAUSE | ANAR::ASM_DIR;
        }
        let mut gbcr = GBCR::empty();
        if let Some(gbesr) = gbesr {
            if gbesr.contains(GBESR::HAVE_1000BASET_HALF) {
                gbcr |= GBCR::HAVE_1000BASET_HALF;
            }
            if gbesr.contains(GBESR::HAVE_1000BASET_FULL) {
                gbcr |= GBCR::HAVE_1000BASET_FULL;
            }
        }

        self.write_reg(REG_ANAR, anar.bits())?;
        psleep(Duration::from_millis(10));

        self.write_reg(REG_GBCR, gbcr.bits())?;
        psleep(Duration::from_millis(10));

        self.write_reg(REG_BMCR, (BMCR::ANE | BMCR::RESTART_AN).bits())?;
        psleep(Duration::from_millis(10));

        Ok(())
    }
}

impl ANAR {
    pub fn from_capabilities(bmsr: BMSR) -> ANAR {
        let mut anar = ANAR::from(ANAR_SELECTOR_802_3);
        if bmsr.contains(BMSR::HAVE_10BASET_HALF) {
            anar |= ANAR::HAVE_10BASET;
        }
        if bmsr.contains(BMSR::HAVE_10BASET_FULL) {
            anar |= ANAR::HAVE_10BASET_FULL;
        }
        if bmsr.contains(BMSR::HAVE_100BASETX_HALF) {
            anar |= ANAR::HAVE_100BASETX;
        }
        if bmsr.contains(BMSR::HAVE_100BASETX_FULL) {
            anar |= ANAR::HAVE_100BASETX_FULL;
        }
        if bmsr.contains(BMSR::HAVE_100BASET4) {
            anar |= ANAR::HAVE_100BASET4;
        }
        anar
    }
}
