#pragma once
#include <stdint.h>

#define PCI_VENDOR_FIELD 0x0
#define PCI_DEVICE_FIELD 0x2
#define PCI_SUBCLASS_FIELD 0xa
#define PCI_CLASS_FIELD 0xb
#define PCI_REVISION_ID_FIELD 0x8
#define PCI_PROG_IF_FIELD 0x9
#define PCI_HEADER_TYPE_FIELD 0xe
#define PCI_SECONDARY_BUS_NUMBER_FIELD 0x19
#define PCI_BAR0_FIELD 0x10
#define PCI_BAR1_FIELD 0x14
#define PCI_BAR2_FIELD 0x18
#define PCI_BAR3_FIELD 0x1C
#define PCI_BAR4_FIELD 0x20
#define PCI_BAR5_FIELD 0x24

namespace PCI
{
    struct DeviceID
    {
        uint16_t vendor;
        uint16_t device;
    };

    struct DeviceType
    {
        uint8_t dev_class;
        uint8_t dev_subclass;
        uint8_t prog_if;
        uint8_t revision;
    };

    struct Device
    {
        void write8(int32_t offset, uint8_t value);
        void write16(int32_t offset, uint16_t value);
        void write32(int32_t offset, uint32_t value);
        uint8_t read8(int32_t offset);
        uint16_t read16(int32_t offset);
        uint32_t read32(int32_t offset);

        uint32_t getBAR0();
        uint32_t getBAR1();
        uint32_t getBAR2();
        uint32_t getBAR3();
        uint32_t getBAR4();
        uint32_t getBAR5();

        uint8_t bus()
        {
            return m_bus;
        }

        uint8_t slot()
        {
            return m_slot;
        }

        uint8_t function()
        {
            return m_function;
        }

        DeviceID id()
        {
            return m_id;
        }

        DeviceType type()
        {
            return m_type;
        }

        Device(DeviceID id, DeviceType type, uint8_t bus, uint8_t slot, uint8_t function);
        Device(uint8_t bus, uint8_t slot, uint8_t function);
        Device(const Device& other);

      private:
        DeviceID m_id;
        DeviceType m_type;
        uint8_t m_bus;
        uint8_t m_slot;
        uint8_t m_function;
    };

    uint32_t raw_address(uint32_t bus, uint32_t slot, uint32_t function, int32_t offset);
    void raw_write8(uint32_t bus, uint32_t slot, uint32_t function, int32_t offset, uint8_t value);
    void raw_write16(uint32_t bus, uint32_t slot, uint32_t function, int32_t offset, uint16_t value);
    void raw_write32(uint32_t bus, uint32_t slot, uint32_t function, int32_t offset, uint32_t value);
    uint8_t raw_read8(uint32_t bus, uint32_t slot, uint32_t function, int32_t offset);
    uint16_t raw_read16(uint32_t bus, uint32_t slot, uint32_t function, int32_t offset);
    uint32_t raw_read32(uint32_t bus, uint32_t slot, uint32_t function, int32_t offset);
    DeviceID get_device_id(uint32_t bus, uint32_t slot, uint32_t function);
    DeviceType get_device_type(uint32_t bus, uint32_t slot, uint32_t function);
    void scan(void (*callback)(PCI::Device&));
}