

#include "CL_context.h"

#include <vector>

#ifdef _WIN32

#include <GL/glew.h>
#include <GL/wglew.h>

namespace cl
{

bool CL_context::create()
{
    cl_int err;
    cl_uint num_platforms=0;
    cl_platform_id* platforms, selected_platform;
    cl_device_id* devices;
    size_t size;

    unsigned int i;

    err = clGetPlatformIDs(0, NULL, &num_platforms);
    if (err != CL_SUCCESS)
    {
        std::cout << "CL_context::create: [WARNING] Could not get platform count!" << std::endl;
        return false;
    }


    //for this: make sure gl context is current
    cl_context_properties ctx_props[] = {CL_CONTEXT_PLATFORM, (cl_context_properties) 0,
                                         CL_WGL_HDC_KHR, (cl_context_properties) wglGetCurrentDC(),
                                         CL_GL_CONTEXT_KHR,(cl_context_properties) wglGetCurrentContext(),
                                         0
                                        };

    platforms = new cl_platform_id[num_platforms];
    err = clGetPlatformIDs(num_platforms, platforms, NULL);
    for (i=0; i < num_platforms; ++i)
    {

        selected_platform = platforms[i];
        ctx_props[1] = (cl_context_properties) selected_platform;

        context_ = clCreateContextFromType(ctx_props, CL_DEVICE_TYPE_GPU, NULL, NULL, &err);

        if (context_)
            break;

    }
    delete[] platforms;


    if (err != CL_SUCCESS || context_ == 0)
    {
        std::cout << "CL_context::create: [WARNING] Could not create CL context! Error Code: "
                  << err << std::endl;
        return false;
    }


    err = clGetContextInfo(context_, CL_CONTEXT_DEVICES, 0, NULL, &size);
    if (err != CL_SUCCESS)
    {
        std::cout << "CL_context::create: [WARNING] Could not get device count for CL context!" << std::endl;
        return false;
    }
    devices = new cl_device_id[size/sizeof(cl_device_info)];
    clGetContextInfo(context_, CL_CONTEXT_DEVICES, size, devices, NULL);
    //select first/only device
    device_id_ = devices[0];
    delete[] devices;

    cmd_queue_ = clCreateCommandQueue(context_, device_id_, 0, &err);

    if (err != CL_SUCCESS || cmd_queue_ == 0)
    {
        std::cout << "CL_context::create: [WARNING] Could not create command-queue!" << std::endl;
        return false;
    }

    if (selected_platform != 0)
    {
        char buf[2048];
        //get some infos
        clGetPlatformInfo(selected_platform, CL_PLATFORM_VENDOR, 0, NULL, &size);
        clGetPlatformInfo(selected_platform, CL_PLATFORM_VENDOR, size, buf, &size);
        vendor_ = buf;

        clGetPlatformInfo(selected_platform, CL_PLATFORM_NAME, 0, NULL, &size);
        clGetPlatformInfo(selected_platform, CL_PLATFORM_NAME, size, buf, &size);
        name_ = buf;

        clGetPlatformInfo(selected_platform, CL_PLATFORM_VERSION, 0, NULL, &size);
        clGetPlatformInfo(selected_platform, CL_PLATFORM_VERSION, size, buf, &size);
        version_ = buf;
    }

    return true;
}

}
#elif __linux

#include <GL/glxew.h>


namespace cl
{

bool CL_context::query_extension(const std::string& extension_name, cl_platform_id* platforms, cl_uint num_platforms)
{
    bool found_extension = false;
    cl_int error;

    for (unsigned int i = 0; i < num_platforms; ++i)
    {
        size_t extension_buffer_size = 0;
        error = clGetPlatformInfo(platforms[i], CL_PLATFORM_EXTENSIONS, 0, nullptr, &extension_buffer_size);

        if (error != CL_SUCCESS)
        {
            printf("[ERROR] %s: clGetPlatformInfo failed.\n", __FUNCTION__);
            break;
        }

        char* extension_buffer = (char*)malloc(sizeof(char) * extension_buffer_size);
        error = clGetPlatformInfo(platforms[i], CL_PLATFORM_EXTENSIONS, extension_buffer_size, extension_buffer, nullptr);

        if (error != CL_SUCCESS)
        {
            printf("[ERROR] %s: clGetPlatformInfo failed.\n", __FUNCTION__);
            delete extension_buffer;
            break;
        }

        std::string extensions(extension_buffer);
        if (extensions.find(extension_name) != std::string::npos)
        {
            found_extension = true;
        }

        delete extension_buffer;
    }

    return found_extension;
}

bool CL_context::create(){
    cl_int err;
    cl_uint num_platforms=0;
    cl_platform_id* platforms, selected_platform=0;
    cl_device_id* devices;
    size_t size;

    unsigned int i;

    err = clGetPlatformIDs(0, NULL, &num_platforms);
    if (err != CL_SUCCESS)
    {
        std::cout << "CL_context::create: [ERROR] Could not get platform count! ErrorID " << err << std::endl;
        return false;
    }

    platforms = new cl_platform_id[num_platforms];
    err = clGetPlatformIDs(num_platforms, platforms, NULL);

    if (err != CL_SUCCESS)
    {
        std::cout << "CL_context::create: [ERROR] Could not get platform ids! ErrorID " << err << std::endl;
        return false;
    }

    bool cl_has_gl_sharing_extension = query_extension("cl_khr_gl_sharing", platforms, num_platforms);

    // Desired OpenCL Context Properties
    std::vector<cl_context_properties> ctx_props = {CL_CONTEXT_PLATFORM, (cl_context_properties) 0};

    if (cl_has_gl_sharing_extension)
    {
        // If OpenCL supports OpenGL Context Sharing, add it to our desired context properties
        // For this: make sure gl context is current
        ctx_props.push_back(CL_GLX_DISPLAY_KHR);
        ctx_props.push_back((cl_context_properties) glXGetCurrentDisplay());
        ctx_props.push_back(CL_GL_CONTEXT_KHR);
        ctx_props.push_back((cl_context_properties) glXGetCurrentContext());
    }

    // Mark ending of context_properties array
    ctx_props.push_back(0);

    for (i = 0; i < num_platforms; ++i)
    {
        selected_platform = platforms[i];
        ctx_props[1] = (cl_context_properties) selected_platform;

        context_ = clCreateContextFromType(ctx_props.data(), CL_DEVICE_TYPE_GPU, NULL, NULL, &err);

        if (context_)
            break;
    }

    delete[] platforms;


    //context_ = clCreateContextFromType(ctx_props, CL_DEVICE_TYPE_GPU, NULL, NULL, &err);

    if (err != CL_SUCCESS || context_ == 0)
    {
        std::cout << "CL_context::create: [WARNING] Could not create CL context!" << std::endl;
        return false;
    }


    err = clGetContextInfo(context_, CL_CONTEXT_DEVICES, 0, NULL, &size);
    if (err != CL_SUCCESS)
    {
        std::cout << "CL_context::create: [WARNING] Could not get device count for CL context!" << std::endl;
        return false;
    }
    devices = new cl_device_id[size/sizeof(cl_device_info)];
    clGetContextInfo(context_, CL_CONTEXT_DEVICES, size, devices, NULL);
    //select first/only device
    device_id_ = devices[0];
    delete[] devices;

    cmd_queue_ = clCreateCommandQueue(context_, device_id_, 0, &err);

    if (err != CL_SUCCESS || cmd_queue_ == 0)
    {
        std::cout << "CL_context::create: [WARNING] Could not create command-queue!" << std::endl;
        return false;
    }

    if (selected_platform != 0)
    {
        char buf[2048];
        //get some infos
        clGetPlatformInfo(selected_platform, CL_PLATFORM_VENDOR, 0, NULL, &size);
        clGetPlatformInfo(selected_platform, CL_PLATFORM_VENDOR, size, buf, &size);
        vendor_ = buf;

        clGetPlatformInfo(selected_platform, CL_PLATFORM_NAME, 0, NULL, &size);
        clGetPlatformInfo(selected_platform, CL_PLATFORM_NAME, size, buf, &size);
        name_ = buf;

        clGetPlatformInfo(selected_platform, CL_PLATFORM_VERSION, 0, NULL, &size);
        clGetPlatformInfo(selected_platform, CL_PLATFORM_VERSION, size, buf, &size);
        version_ = buf;
    }


    return true;
}

}

#elif __APPLE__

#include "../glew/include/GL/glew.h"
#include <OpenGL/OpenGL.h>
#include <OpenCL/cl_gl_ext.h>


namespace cl
{

bool CL_context::query_extension(const std::string& extension_name, cl_platform_id* platforms, cl_uint num_platforms)
{
    bool found_extension = false;
    cl_int error;

    for (unsigned int i = 0; i < num_platforms; ++i)
    {
        size_t extension_buffer_size = 0;
        error = clGetPlatformInfo(platforms[i], CL_PLATFORM_EXTENSIONS, 0, nullptr, &extension_buffer_size);

        if (error != CL_SUCCESS)
        {
            printf("[ERROR] %s: clGetPlatformInfo failed.\n", __FUNCTION__);
            break;
        }

        char* extension_buffer = (char*)malloc(sizeof(char) * extension_buffer_size);
        error = clGetPlatformInfo(platforms[i], CL_PLATFORM_EXTENSIONS, extension_buffer_size, extension_buffer, nullptr);

        if (error != CL_SUCCESS)
        {
            printf("[ERROR] %s: clGetPlatformInfo failed.\n", __FUNCTION__);
            delete extension_buffer;
            break;
        }

        std::string extensions(extension_buffer);
        if (extensions.find(extension_name) != std::string::npos)
        {
            found_extension = true;
        }

        delete extension_buffer;
    }

    return found_extension;
}

bool CL_context::create(){
    cl_int err;
    cl_uint num_platforms=0;
    cl_platform_id* platforms, selected_platform=0;
    cl_device_id* devices;
    size_t size;

    unsigned int i;

    err = clGetPlatformIDs(0, NULL, &num_platforms);
    if (err != CL_SUCCESS)
    {
        std::cout << "CL_context::create: [ERROR] Could not get platform count! ErrorID " << err << std::endl;
        return false;
    }

    platforms = new cl_platform_id[num_platforms];
    err = clGetPlatformIDs(num_platforms, platforms, NULL);

    if (err != CL_SUCCESS)
    {
        std::cout << "CL_context::create: [ERROR] Could not get platform ids! ErrorID " << err << std::endl;
        return false;
    }

    bool cl_has_gl_sharing_extension = query_extension("cl_khr_gl_sharing", platforms, num_platforms);

    // Desired OpenCL Context Properties
    std::vector<cl_context_properties> ctx_props = {CL_CONTEXT_PLATFORM, (cl_context_properties) 0};

    if (cl_has_gl_sharing_extension)
    {
        // MacOS
        CGLContextObj     kCGLContext     = CGLGetCurrentContext();
        CGLShareGroupObj  kCGLShareGroup  = CGLGetShareGroup(kCGLContext);

        ctx_props.push_back(CL_CONTEXT_PROPERTY_USE_CGL_SHAREGROUP_APPLE);
        ctx_props.push_back((cl_context_properties) kCGLShareGroup);

    }

    // Mark ending of context_properties array
    ctx_props.push_back(0);

    for (i = 0; i < num_platforms; ++i)
    {
        selected_platform = platforms[i];
        ctx_props[1] = (cl_context_properties) selected_platform;

        context_ = clCreateContextFromType(ctx_props.data(), CL_DEVICE_TYPE_GPU, NULL, NULL, &err);

        if (context_)
            break;
    }

    delete[] platforms;


    //context_ = clCreateContextFromType(ctx_props, CL_DEVICE_TYPE_GPU, NULL, NULL, &err);

    if (err != CL_SUCCESS || context_ == 0)
    {
        std::cout << "CL_context::create: [WARNING] Could not create CL context!" << std::endl;
        return false;
    }


    err = clGetContextInfo(context_, CL_CONTEXT_DEVICES, 0, NULL, &size);
    if (err != CL_SUCCESS)
    {
        std::cout << "CL_context::create: [WARNING] Could not get device count for CL context!" << std::endl;
        return false;
    }
    devices = new cl_device_id[size/sizeof(cl_device_info)];
    clGetContextInfo(context_, CL_CONTEXT_DEVICES, size, devices, NULL);
    //select first/only device
    device_id_ = devices[0];
    delete[] devices;

    cmd_queue_ = clCreateCommandQueue(context_, device_id_, 0, &err);

    if (err != CL_SUCCESS || cmd_queue_ == 0)
    {
        std::cout << "CL_context::create: [WARNING] Could not create command-queue!" << std::endl;
        return false;
    }

    if (selected_platform != 0)
    {
        char buf[2048];
        //get some infos
        clGetPlatformInfo(selected_platform, CL_PLATFORM_VENDOR, 0, NULL, &size);
        clGetPlatformInfo(selected_platform, CL_PLATFORM_VENDOR, size, buf, &size);
        vendor_ = buf;

        clGetPlatformInfo(selected_platform, CL_PLATFORM_NAME, 0, NULL, &size);
        clGetPlatformInfo(selected_platform, CL_PLATFORM_NAME, size, buf, &size);
        name_ = buf;

        clGetPlatformInfo(selected_platform, CL_PLATFORM_VERSION, 0, NULL, &size);
        clGetPlatformInfo(selected_platform, CL_PLATFORM_VERSION, size, buf, &size);
        version_ = buf;
    }


    return true;
}

}

#endif
