#include "bios.h"
#include "common/smbios.h"

#ifdef _WIN32
    #include "common/windows/registry.h"

    #include <ntstatus.h>
    #include "common/windows/nt.h"
#elif __OpenBSD__
    #include "common/io.h"

    #include <fcntl.h>
    #include <unistd.h>
#elif __sun
    #include <libdevinfo.h>
    #include <sys/sunddi.h>
#elif __APPLE__
    #include "common/apple/cf_helpers.h"
    #include <IOKit/IOKitLib.h>
#endif

typedef struct FFSmbiosBios {
    FFSmbiosHeader Header;

    uint8_t Vendor;                      // string
    uint8_t BiosVersion;                 // string
    uint16_t BiosStartingAddressSegment; // varies
    uint8_t BiosReleaseDate;             // string
    uint8_t BiosRomSize;                 // string
    uint64_t BiosCharacteristics;        // bit field

    // 2.4+
    uint8_t BiosCharacteristicsExtensionBytes[2];   // bit field
    uint8_t SystemBiosMajorRelease;                 // varies
    uint8_t SystemBiosMinorRelease;                 // varies
    uint8_t EmbeddedControllerFirmwareMajorRelease; // varies
    uint8_t EmbeddedControllerFirmwareMinorRelease; // varies

    // 3.1+
    uint16_t ExtendedBiosRomSize; // bit field
} FF_A_PACKED FFSmbiosBios;

static_assert(offsetof(FFSmbiosBios, ExtendedBiosRomSize) == 0x18,
    "FFSmbiosBios: Wrong struct alignment");

const char* ffDetectBios(FFBiosResult* bios) {
    const FFSmbiosHeaderTable* smbiosTable = ffGetSmbiosHeaderTable();
    if (!smbiosTable) {
        return "Failed to get SMBIOS data";
    }

    const FFSmbiosBios* data = (const FFSmbiosBios*) (*smbiosTable)[FF_SMBIOS_TYPE_BIOS];
    if (!data) {
        return "BIOS section is not found in SMBIOS data";
    }

    const char* strings = (const char*) data + data->Header.Length;

    ffStrbufSetStatic(&bios->version, ffSmbiosLocateString(strings, data->BiosVersion));
    ffCleanUpSmbiosValue(&bios->version);
    ffStrbufSetStatic(&bios->vendor, ffSmbiosLocateString(strings, data->Vendor));
    ffCleanUpSmbiosValue(&bios->vendor);
    ffStrbufSetStatic(&bios->date, ffSmbiosLocateString(strings, data->BiosReleaseDate));
    ffCleanUpSmbiosValue(&bios->date);

    if (data->Header.Length > offsetof(FFSmbiosBios, SystemBiosMajorRelease)) {
        ffStrbufSetF(&bios->release, "%u.%u", data->SystemBiosMajorRelease, data->SystemBiosMinorRelease);
    }

#ifdef _WIN32
    // Same as GetFirmwareType, but support (?) Windows 7
    // https://ntdoc.m417z.com/system_information_class
    SYSTEM_BOOT_ENVIRONMENT_INFORMATION sbei;
    if (NT_SUCCESS(NtQuerySystemInformation(SystemBootEnvironmentInformation, &sbei, sizeof(sbei), NULL))) {
        switch (sbei.FirmwareType) {
            case FirmwareTypeBios:
                ffStrbufSetStatic(&bios->type, "BIOS");
                break;
            case FirmwareTypeUefi:
                ffStrbufSetStatic(&bios->type, "UEFI");
                break;
            default:
                break;
        }
    }
#elif __sun
    di_node_t rootNode = di_init("/", DINFOPROP);
    if (rootNode != DI_NODE_NIL) {
        char* efiVersion = NULL;
        if (di_prop_lookup_strings(DDI_DEV_T_ANY, rootNode, "efi-version", &efiVersion) > 0) {
            ffStrbufSetStatic(&bios->type, "UEFI");
        } else {
            ffStrbufSetStatic(&bios->type, "BIOS");
        }
    }
    di_fini(rootNode);
#elif __HAIKU__ || __OpenBSD__
    // Currently SMBIOS detection is supported in legacy BIOS only
    ffStrbufSetStatic(&bios->type, "BIOS");
#elif __APPLE__
    // Intel Macs use UEFI from day one
    FF_IOOBJECT_AUTO_RELEASE io_registry_entry_t deviceEfi = IORegistryEntryFromPath(MACH_PORT_NULL, "IODeviceTree:/efi");
    ffStrbufSetStatic(&bios->type, deviceEfi ? "UEFI" : "BIOS");
#endif

    return NULL;
}
