#define CL_TARGET_OPENCL_VERSION 300
#include <CL/cl.h>
#include <iostream>
#include <vector>
#include <cstring>

static const char* clErrorToStr(cl_int err)
{
    switch(err) {
        case CL_SUCCESS:                            return "CL_SUCCESS";
        case CL_INVALID_VALUE:                      return "CL_INVALID_VALUE";
        case CL_INVALID_CONTEXT:                    return "CL_INVALID_CONTEXT";
        case CL_INVALID_MEM_OBJECT:                 return "CL_INVALID_MEM_OBJECT";
        case CL_OUT_OF_HOST_MEMORY:                 return "CL_OUT_OF_HOST_MEMORY";
        case CL_INVALID_OPERATION:                  return "CL_INVALID_OPERATION";
        case CL_MEM_OBJECT_ALLOCATION_FAILURE:      return "CL_MEM_OBJECT_ALLOCATION_FAILURE";
        default:                                    return "UNKNOWN_ERROR";
    }
}

// Try creating a USE_HOST_PTR buffer on this device
bool testUseHostPtr(cl_context ctx, cl_device_id dev)
{
    const size_t bufSize = 1024;
    std::vector<char> host(bufSize, 0);

    cl_int err = 0;
    cl_mem buf = clCreateBuffer(
        ctx,
        CL_MEM_USE_HOST_PTR | CL_MEM_READ_WRITE,
        bufSize,
        host.data(),
        &err
    );

    if(err != CL_SUCCESS) {
        std::cerr << "  clCreateBuffer(CL_MEM_USE_HOST_PTR) failed: "
                  << clErrorToStr(err) << "\n";
        return false;
    }

    // Try to enqueue a trivial write to verify it works
    cl_queue_properties queueProps[] = {CL_QUEUE_PROPERTIES, 0, 0};
    cl_command_queue q = clCreateCommandQueueWithProperties(ctx, dev, queueProps, &err);
    if(err != CL_SUCCESS){
        std::cerr << "  Failed to create command queue: "
                  << clErrorToStr(err) << "\n";
        clReleaseMemObject(buf);
        return false;
    }

    err = clEnqueueWriteBuffer(q, buf, CL_TRUE, 0, bufSize, host.data(), 0, nullptr, nullptr);
    clFinish(q);

    bool ok = (err == CL_SUCCESS);

    if(!ok) {
        std::cerr << "  clEnqueueWriteBuffer failed: " << clErrorToStr(err) << "\n";
    }

    clReleaseCommandQueue(q);
    clReleaseMemObject(buf);

    return ok;
}

int main()
{
    cl_uint numPlatforms = 0;
    clGetPlatformIDs(0, nullptr, &numPlatforms);

    if(numPlatforms == 0){
        std::cout << "No OpenCL platforms.\n";
        return 0;
    }

    std::vector<cl_platform_id> plats(numPlatforms);
    clGetPlatformIDs(numPlatforms, plats.data(), nullptr);

    for(cl_uint p = 0; p < numPlatforms; ++p)
    {
        char buf[256];

        clGetPlatformInfo(plats[p], CL_PLATFORM_NAME, sizeof(buf), buf, nullptr);
        std::cout << "Platform: " << buf << "\n";

        cl_uint numDevs = 0;
        clGetDeviceIDs(plats[p], CL_DEVICE_TYPE_ALL, 0, nullptr, &numDevs);

        if(numDevs == 0) {
            std::cout << "  No devices found on this platform.\n";
            continue;
        }

        std::vector<cl_device_id> devs(numDevs);
        clGetDeviceIDs(plats[p], CL_DEVICE_TYPE_ALL, numDevs, devs.data(), nullptr);

        for(cl_uint d = 0; d < numDevs; ++d)
        {
            clGetDeviceInfo(devs[d], CL_DEVICE_NAME, sizeof(buf), buf, nullptr);
            std::cout << "  Device: " << buf << "\n";

            // Create a context for this device
            cl_int err;
            cl_context ctx = clCreateContext(nullptr, 1, &devs[d], nullptr, nullptr, &err);

            if(err != CL_SUCCESS) {
                std::cout << "    Failed to create context: "
                          << clErrorToStr(err) << "\n";
                continue;
            }

            bool supported = testUseHostPtr(ctx, devs[d]);

            if(supported)
                std::cout << "    HOST_PTR appears supported.\n";
            else
                std::cout << "    HOST_PTR appears NOT supported.\n";

            clReleaseContext(ctx);
        }
    }

    return 0;
}
