// Copyright 2017 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include "host-common/opengl/NativeGpuInfo.h"

#include "host-common/opengl/macTouchOpenGL.h"

#include <CoreGraphics/CGDirectDisplay.h>
#include <IOKit/graphics/IOGraphicsLib.h>
#include <IOKit/IOTypes.h>
#include <IOKit/IOKitLib.h>

#include <string>
#include <vector>

#include <stdio.h>

typedef std::pair<uint32_t, uint32_t> GpuVendorDeviceId;
typedef std::vector<GpuVendorDeviceId> GpuVendorDeviceIdList;

// Based on code from:
// https://chromium.googlesource.com/chromium/src/gpu/+/e626016b34c63b7ff51bf9a6c20b37bcc18150c4/config/gpu_info_collector_mac.mm
// https://github.com/glfw/glfw/blob/e0a6772e5e4c672179fc69a90bcda3369792ed1f/src/cocoa_monitor.m

// GetEntryProperty():
// Return 0 if we couldn't find the property.
// The property values we use should not be 0, so it's OK to use 0 as failure.
uint32_t GetEntryProperty(io_registry_entry_t entry, CFStringRef property_name) {
    CFDataRef ref(
        static_cast<CFDataRef>(IORegistryEntrySearchCFProperty(
            entry,
            kIOServicePlane,
            property_name,
            kCFAllocatorDefault,
            kIORegistryIterateRecursively | kIORegistryIterateParents)));

    if (!ref)
        return 0;

    uint32_t value = 0;
    const uint32_t* value_pointer =
        reinterpret_cast<const uint32_t*>(CFDataGetBytePtr(ref));

    if (value_pointer != NULL)
        value = *value_pointer;

    CFRelease(ref);
    return value;
}

static auto GetGPUInfoMac() {

    GpuVendorDeviceIdList res;

    io_iterator_t iter;
    io_service_t serv = 0;

    CFMutableDictionaryRef matching = IOServiceMatching("IODisplayConnect");
    kern_return_t err = IOServiceGetMatchingServices(kIOMasterPortDefault,
                             matching,
                             &iter);

    if (err) return res;

    while ((serv = IOIteratorNext(iter)) != 0) {
         uint32_t vendor_id = GetEntryProperty(serv, CFSTR("vendor-id"));
         if (vendor_id) {
             uint32_t device_id = GetEntryProperty(serv, CFSTR("device-id"));
             if (device_id) {
                res.push_back(std::make_pair(vendor_id, device_id));
             }
         }
     }

    return res;
}

void getGpuInfoListNative(GpuInfoList* out) {
    // This call initializes a pixel format,
    // which should update the IOKit stuff to the
    // correct GPU that will actually be used
    // while the emulator is running.
    macTouchOpenGL();

    auto gpulist = GetGPUInfoMac();

    char vendoridbuf[64] = {};
    char deviceidbuf[64] = {};

    for (const auto& elt : gpulist) {
        snprintf(vendoridbuf, sizeof(vendoridbuf), "%04x", elt.first);
        snprintf(deviceidbuf, sizeof(deviceidbuf), "%04x", elt.second);
        out->infos.emplace_back(
                std::string(vendoridbuf), // make -> vendorid
                std::string(deviceidbuf), // model -> deviceid
                std::string(deviceidbuf), // device_id -> deviceid
                "", "", "" // revision, version, renderer blank
                );
    }
}

// macOS: Disable Vulkan for now unless on M1.
bool isVulkanSafeToUseNative() {
#ifdef __aarch64__
    return true;
#else
    return false;
#endif
}