use libk::dma::BusAddress;
use tock_registers::{
    interfaces::{ReadWriteable, Readable, Writeable},
    register_bitfields, register_structs,
    registers::{ReadOnly, ReadWrite},
};
use yggdrasil_abi::error::Error;

use crate::error::AhciError;

register_bitfields! {
    u32,
    pub CAP [
        NP OFFSET(0) NUMBITS(5) [],
        NCS OFFSET(8) NUMBITS(5) [],
        SAM OFFSET(18) NUMBITS(1) [],
        S64A OFFSET(31) NUMBITS(1) [],
    ],
    pub GHC [
        HR OFFSET(0) NUMBITS(1) [],
        IE OFFSET(1) NUMBITS(1) [],
        AE OFFSET(31) NUMBITS(1) [],
    ],

    // Read/write 1 to clear
    pub IS [
        TFES OFFSET(30) NUMBITS(1) [],
        HBFS OFFSET(29) NUMBITS(1) [],
        HBDS OFFSET(28) NUMBITS(1) [],
        IFS OFFSET(27) NUMBITS(1) [],
        OFS OFFSET(24) NUMBITS(1) [],
    ],
    pub IE [
        TFEE OFFSET(30) NUMBITS(1) [],
        HBFE OFFSET(29) NUMBITS(1) [],
        HBDE OFFSET(28) NUMBITS(1) [],
        IFE OFFSET(27) NUMBITS(1) [],
        OFE OFFSET(24) NUMBITS(1) [],
        DPE OFFSET(5) NUMBITS(1) [],
        DHRE OFFSET(0) NUMBITS(1) [],
    ],
    pub CMD [
        CR OFFSET(15) NUMBITS(1) [],
        FR OFFSET(14) NUMBITS(1) [],
        CCS OFFSET(8) NUMBITS(5) [],
        FRE OFFSET(4) NUMBITS(1) [],
        POD OFFSET(2) NUMBITS(1) [],
        ST OFFSET(0) NUMBITS(1) [],
    ],
    pub SSTS [
        IPM OFFSET(8) NUMBITS(4) [
            NotPresent = 0,
            Active = 1,
        ],
        DET OFFSET(0) NUMBITS(4) [
            NotPresent = 0,
            Online = 3,
        ],
    ],
    pub TFD [
        BSY OFFSET(7) NUMBITS(1) [],
        DRQ OFFSET(3) NUMBITS(1) [],
        ERR OFFSET(0) NUMBITS(1) [],
    ]
}

register_structs! {
    #[allow(non_snake_case)]
    pub Regs {
        (0x0000 => pub CAP: ReadOnly<u32, CAP::Register>),
        (0x0004 => pub GHC: ReadWrite<u32, GHC::Register>),
        (0x0008 => pub IS: ReadWrite<u32>),
        (0x000C => pub PI: ReadOnly<u32>),
        (0x0010 => pub VS: ReadOnly<u32>),
        (0x0014 => _0),
        (0x0100 => pub PORTS: [PortRegs; 30]),
        (0x1000 => @END),
    }
}

register_structs! {
    #[allow(non_snake_case)]
    pub PortRegs {
        (0x00 => pub CLB: ReadWrite<u32>),
        (0x04 => pub CLBU: ReadWrite<u32>),
        (0x08 => pub FB: ReadWrite<u32>),
        (0x0C => pub FBU: ReadWrite<u32>),
        (0x10 => pub IS: ReadWrite<u32, IS::Register>),
        (0x14 => pub IE: ReadWrite<u32, IE::Register>),
        (0x18 => pub CMD: ReadWrite<u32, CMD::Register>),
        (0x1C => _0),
        (0x20 => pub TFD: ReadWrite<u32, TFD::Register>),
        (0x24 => pub SIG: ReadOnly<u32>),
        (0x28 => pub SSTS: ReadOnly<u32, SSTS::Register>),
        (0x2C => pub SCTL: ReadOnly<u32>),
        (0x30 => pub SERR: ReadOnly<u32>),
        (0x34 => pub SACT: ReadOnly<u32>),
        (0x38 => pub CI: ReadWrite<u32>),
        (0x3C => pub SNTF: ReadOnly<u32>),
        (0x40 => _1),
        (0x80 => @END),
    }
}

#[derive(Clone, Copy, PartialEq, Debug)]
pub enum Version {
    V0_95,
    V1_0,
    V1_1,
    V1_2,
    V1_3,
    V1_3_1,
}

pub const CMD_PENDING: u32 = 1;
pub const CMD_READY: u32 = 0;
pub const CMD_ERROR: u32 = 2;

impl PortRegs {
    pub const SIG_SATA: u32 = 0x101;

    // NOTE: usually doesn't take long, so not async, I guess
    pub fn stop(&self) -> Result<(), AhciError> {
        self.CMD.modify(CMD::ST::CLEAR + CMD::FRE::CLEAR);

        // TODO timeout here
        while self.CMD.matches_any(&[CMD::FR::SET, CMD::CR::SET]) {
            core::hint::spin_loop();
        }

        Ok(())
    }

    pub fn start(&self) -> Result<(), AhciError> {
        while self.CMD.matches_all(CMD::CR::SET) {
            core::hint::spin_loop();
        }

        self.CMD.modify(CMD::ST::SET + CMD::FRE::SET);

        Ok(())
    }

    pub fn set_received_fis_address_64(&self, address: BusAddress) {
        let address: u64 = address.into_u64();
        self.FB.set(address as u32);
        self.FBU.set((address >> 32) as u32);
    }

    pub fn set_command_list_address_64(&self, address: BusAddress) {
        let address: u64 = address.into_u64();
        self.CLB.set(address as u32);
        self.CLBU.set((address >> 32) as u32);
    }

    pub fn clear_interrupt(&self) -> Option<u32> {
        let is = self.IS.extract();

        if is.get() == 0 {
            return None;
        }

        self.IS.set(u32::MAX);

        if is.matches_any(&[IS::HBDS::SET, IS::HBFS::SET]) {
            todo!("Host communication error unhandled");
        }

        if is.matches_any(&[IS::TFES::SET, IS::IFS::SET, IS::OFS::SET]) {
            Some(CMD_ERROR)
        } else {
            Some(CMD_READY)
        }
    }
}

impl TryFrom<u32> for Version {
    type Error = Error;

    fn try_from(value: u32) -> Result<Self, Self::Error> {
        match value {
            0x00000905 => Ok(Self::V0_95),
            0x00010000 => Ok(Self::V1_0),
            0x00010100 => Ok(Self::V1_1),
            0x00010200 => Ok(Self::V1_2),
            0x00010300 => Ok(Self::V1_3),
            0x00010301 => Ok(Self::V1_3_1),
            _ => Err(Error::InvalidArgument),
        }
    }
}
