// link to OpenCL
#pragma comment (lib, "OpenCL.lib")

#define CL_USE_DEPRECATED_OPENCL_1_1_APIS
// activate exceptions from OpenCL C++ API
#define __CL_ENABLE_EXCEPTIONS

#include <vector>
#include <map>

#pragma warning(push)
#pragma warning(disable: 4100) // unreferenced formal parameter
#pragma warning(disable: 4245) // signed/unsigned mismatch
#pragma warning(disable: 4510) // default constructor could not be generated
#pragma warning(disable: 4512) // assignment operator could not be generated
#pragma warning(disable: 4610) // struct can never be instantiated - user defined constructor required
#include <CL/cl.hpp>
#pragma warning(pop)

#include "opencl.hpp"


/** OpenCL error-code checker. */
static void CL_CHECK_ERROR (cl_int val) {
    if (val != CL_SUCCESS)
        throw std::runtime_error("OpenCL error");
}





static std::vector<cl::Device> GetOpenCLDevices (cl_device_type type) {
    // retrieve platform list
    std::vector<cl::Platform> platforms;
    cl::Platform::get(&platforms);
    if (platforms.empty())
        throw std::runtime_error("No OpenCL platforms found");

    // retrieve all devices (of correct type) for all platforms
    std::vector<cl::Device> devices;
    for (size_t p = 0; p < platforms.size(); p++) {
        // query platform name
        std::string platform_name;
        platforms[p].getInfo(CL_PLATFORM_NAME, &platform_name);

        // get all devices for the platform
        cl_uint dev_count = 0;
        cl_int err = ::clGetDeviceIDs(platforms[p](), type, 0, 0, &dev_count);
        if ( (err != CL_SUCCESS) || (dev_count == 0) )
            continue;

        // retrieve all devices
        std::vector<cl_device_id> tmp_dev(dev_count);
        CL_CHECK_ERROR(::clGetDeviceIDs(platforms[p](), type, dev_count, &tmp_dev[0], NULL));

        for (size_t i = 0; i < tmp_dev.size(); i++) {
            cl_uint cl_vendor_id = 0;
            CL_CHECK_ERROR(::clGetDeviceInfo(tmp_dev[i], CL_DEVICE_VENDOR_ID, sizeof(cl_vendor_id), &cl_vendor_id, nullptr));

            if (cl_vendor_id == 0x8086) // only intel devices
                devices.push_back(tmp_dev[i]);
        }
    }

    return devices;
}



static std::shared_ptr<cl::Context> GetDirect3dOpenCLContext (std::vector<cl::Device> & devices) {
    // persistent context object
#ifdef INTEL_CONTEXT_REF_COUNT_WORK_AROUND
    std::shared_ptr<cl::Context> context;
#else
    static std::shared_ptr<cl::Context> context;
#endif

    // re-use existing context object if possible
    if (context)
        return context;

    context = std::make_shared<cl::Context>(devices);

    return context;
}


ClContextHelper::ClContextHelper () {
    // retrieve OpenCL device list
    std::vector<cl::Device> devices = GetOpenCLDevices(CL_DEVICE_TYPE_CPU);
    if (devices.empty())
        throw std::runtime_error("No OpenCL devices found");

    // create context
    std::shared_ptr<cl::Context> context = GetDirect3dOpenCLContext(devices);
}


ClContextHelper::~ClContextHelper () {
    // auto-cleanup
}
