/*
 * Copyright (c) 2022, able <abl3theabove@gmail.com>
 * Copyright (c) 2022, Umut İnan Erdoğan <umutinanerdogan@pm.me>
 *
 * SPDX-License-Identifier: MPL-2.0
 */

use core::fmt;

use x86_64::instructions::port::Port;

use super::{
    vendors::Vendor::{self, *},
    PciClass, PciFullClass,
};

// FIXME: Unknown class
pub const S3_TRIO64V2: DeviceID = DeviceID::new(S3Inc, 0x8900);

// MassStorage_IDE (0x0101)
pub const INTEL_PIIX3_IDE: DeviceID = DeviceID::new(IntelCorp, 0x7010);
pub const INTEL_PIIX4_IDE: DeviceID = DeviceID::new(IntelCorp, 0x7111);

// Display_VGA (0x0300)
pub const VMWARE_SVGA2: DeviceID = DeviceID::new(VMWareInc, 0x0405);

#[derive(Copy, Clone, Debug)]
/// A struct containing info about a PCI device.
pub struct PciDeviceInfo {
    pub header_type: u8,
    pub device: u8,
    pub bus: u8,
    pub device_id: DeviceID,
    pub full_class: PciFullClass,
    pub rev_id: u8,
}

impl PciDeviceInfo {
    /// Get the class of the PCI device as a PciClass
    pub fn class(&self) -> PciClass {
        (((self.full_class.as_u16() >> 8) & 0xFF) as u8).into()
    }

    /// Get the bar, 0-indexed
    pub fn bar(&self, bar: u8) -> u32 {
        assert!(bar < 6);
        unsafe { self.read(0, 0x10 + bar * 4) }
    }

    /// Get the interrupt pin
    pub fn interrupt_pin(&self) -> u8 {
        let last_row = unsafe { self.read(0, 0x3C) };
        ((last_row >> 8) & 0xFF) as u8
    }

    /// Enable bus mastering. This allows the PCI device to do DMA
    pub fn enable_bus_mastering(&self) {
        let command = unsafe { self.read(0, 4) } | 1 << 2;
        unsafe { self.write(0, 4, command) }
    }

    /// Read from configuration space
    pub unsafe fn read(&self, func: u8, offset: u8) -> u32 {
        pci_config_read(self.bus, self.device, func, offset)
    }

    /// Write to IO space
    pub unsafe fn write(&self, func: u8, offset: u8, value: u32) {
        pci_config_write(self.bus, self.device, func, offset, value)
    }
}

impl fmt::Display for PciDeviceInfo {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
        let vendor_name = &self.device_id.vendor;
        let device_id = &self.device_id.id;
        writeln!(
            f,
            "Device: {} | Bus: 0x{:X} | Vendor: {} | Device ID: 0x{:X}",
            self.device, self.bus, vendor_name, device_id,
        )?;
        writeln!(f, "{}", self.full_class)?;
        writeln!(f, "    Header type: 0x{:X}", self.header_type)?;
        writeln!(f, "    Revision ID: {}", self.rev_id)?;
        // write!(f, "    Supported functions: 0")?;
        // for (i, b) in self.supported_fns.iter().enumerate().skip(1) {
        //     if *b {
        //         write!(f, ", {}", i)?;
        //     }
        // }
        // writeln!(f)?;
        // write!(f, "    BARs: [ ")?;
        // for i in self.bars.iter() {
        //     if *i == 0 {
        //         write!(f, "0x0 ")?;
        //     } else {
        //         write!(f, "{:#010X} ", i)?;
        //     }
        // }
        // writeln!(f, "]")?;
        // writeln!(
        //     f,
        //     "    Interrupt line / pin: {} / {}",
        //     self.interrupt_line, self.interrupt_pin
        // )?;
        Ok(())
    }
}

#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct DeviceID {
    pub vendor: Vendor,
    pub id: u16,
}

impl DeviceID {
    pub const fn new(vendor: Vendor, id: u16) -> Self {
        Self { vendor, id }
    }
}

pub fn check_device(bus: u8, device: u8) -> Option<PciDeviceInfo> {
    assert!(device < 32);
    let (device_id, vendor_id) = get_ids(bus, device, 0);
    if vendor_id == 0xFFFF {
        // Device doesn't exist
        return None;
    }

    let reg2 = unsafe { pci_config_read(bus, device, 0, 0x8) };
    let class = ((reg2 >> 16) & 0x0000FFFF) as u16;
    let pci_class = PciFullClass::from_u16(class);
    let header_type = get_header_type(bus, device, 0);

    Some(PciDeviceInfo {
        header_type,
        device,
        bus,
        device_id: DeviceID {
            vendor: vendor_id.into(),
            id: device_id,
        },
        full_class: pci_class,
        rev_id: (reg2 & 0x000000FF) as u8,
    })
}

unsafe fn pci_config_read(bus: u8, device: u8, func: u8, offset: u8) -> u32 {
    let bus = bus as u32;
    let device = device as u32;
    let func = func as u32;
    let offset = offset as u32;
    // construct address param
    let address =
        ((bus << 16) | (device << 11) | (func << 8) | (offset & 0xfc) | 0x80000000) as u32;

    // write address
    Port::new(0xCF8).write(address);

    // read data
    Port::new(0xCFC).read()
}

unsafe fn pci_config_write(bus: u8, device: u8, func: u8, offset: u8, value: u32) {
    let bus = bus as u32;
    let device = device as u32;
    let func = func as u32;
    let offset = offset as u32;
    // construct address param
    let address =
        ((bus << 16) | (device << 11) | (func << 8) | (offset & 0xfc) | 0x80000000) as u32;

    // write address
    Port::new(0xCF8).write(address);

    // write data
    Port::new(0xCFC).write(value);
}

fn get_header_type(bus: u8, device: u8, function: u8) -> u8 {
    assert!(device < 32);
    assert!(function < 8);
    let res = unsafe { pci_config_read(bus, device, function, 0x0C) };
    ((res >> 16) & 0xFF) as u8
}

fn get_ids(bus: u8, device: u8, function: u8) -> (u16, u16) {
    assert!(device < 32);
    assert!(function < 8);
    let res = unsafe { pci_config_read(bus, device, function, 0) };
    let dev_id = ((res >> 16) & 0xFFFF) as u16;
    let vnd_id = (res & 0xFFFF) as u16;
    (dev_id, vnd_id)
}
