#include <iomanip>
#include "CLCaster.h"

CLCaster::CLCaster() {}
CLCaster::~CLCaster() {

    delete[] viewport_matrix;
    delete[] viewport_image;

	camera.reset();
	map.reset();
}

bool CLCaster::init() {

	Logger::log("Initializing the Hardware Caster", Logger::LogLevel::INFO);

	if (!aquire_hardware()) {
		Logger::log("Failed to acquire OpenCL hardware", Logger::LogLevel::ERROR, __LINE__, __FILE__);
		return false;
	}

	if (!load_config()) {

		std::cout << "Select a device number which you wish to use" << std::endl;

		for (int i = 0; i < device_list.size(); i++) {

			std::cout << "\n-----------------------------------------------------------------" << std::endl;
			std::cout << "\tDevice Number : " << i << std::endl;
			std::cout << "-----------------------------------------------------------------" << std::endl;

			device_list.at(i).print(std::cout);
		}

		int selection = -1;

		while (selection < 0 && selection >= device_list.size()) {

			std::cout << "Device which you wish to use : ";
			std::cin >> selection;
		}

		device_id = device_list.at(selection).getDeviceId();
		platform_id = device_list.at(selection).getPlatformId();

		save_config();
	}

	if (!create_shared_context()) {
		Logger::log("Failed to create shared CL GL context", Logger::LogLevel::ERROR, __LINE__, __FILE__);
		return false;
	}

	if (!create_command_queue()) {
		Logger::log("Failed to create a OpenCL command queue", Logger::LogLevel::ERROR, __LINE__, __FILE__);
		return false;
	}

    if (!create_settings_buffer()) {
        Logger::log("Failed to create settings buffer", Logger::LogLevel::ERROR, __LINE__, __FILE__);
        return false;
    }

	srand(time(nullptr));

	int *seed_memory = new int[1920*1080];

	if (!create_buffer("seed", sizeof(int) * 1920 * 1080, seed_memory))
		return false;

	return true;

}

bool CLCaster::assign_map(std::shared_ptr<Map> map) {
	
	this->map = map;
	auto dimensions = map->array_map.getDimensions();

	if (!create_buffer("map", sizeof(char) * dimensions.x * dimensions.y * dimensions.z, map->array_map.getDataPtr()))
		return false;
	if (!create_buffer("map_dimensions", sizeof(int) * 3, &dimensions))
		return false;

	return true;
}

bool CLCaster::release_map() {
	
	this->map = nullptr;

	if (!release_buffer("map"))
		return false;
	if (!release_buffer("map_dimensions"))
		return false;

	return true;
}


bool CLCaster::assign_octree(std::shared_ptr<Map> map) {

	this->map = map;

	if (!create_buffer("octree_descriptor_buffer", map->octree.buffer_size * sizeof(uint64_t), map->octree.descriptor_buffer))
		return false;
	if (!create_buffer("octree_attachment_lookup_buffer", map->octree.buffer_size * sizeof(uint32_t), map->octree.attachment_lookup))
		return false;
	if (!create_buffer("octree_attachment_buffer", map->octree.buffer_size * sizeof(uint64_t), map->octree.attachment_buffer))
		return false;

    add_to_settings_buffer("octree_root_index", "OCTREE_ROOT_INDEX", map->octree.root_index);

	return true;
}


bool CLCaster::release_octree()
{
	this->map = nullptr;

	if (!release_buffer("octree_descriptor_buffer"))
		return false;
	if (!release_buffer("octree_attachment_lookup_buffer"))
		return false;
	if (!release_buffer("octree_attachment_buffer"))
		return false;

	return true;
}

bool CLCaster::assign_camera(std::shared_ptr<Camera> camera) {
	
	this->camera = camera;

	if (!create_buffer("camera_direction", sizeof(float) * 4, (void*)camera->get_direction_pointer(), CL_MEM_READ_ONLY | CL_MEM_USE_HOST_PTR))
		return false;
	if (!create_buffer("camera_position", sizeof(float) * 4, (void*)camera->get_position_pointer(), CL_MEM_READ_ONLY | CL_MEM_USE_HOST_PTR))
		return false;

	return true;
}

bool CLCaster::release_camera() {

	this->camera = nullptr;

	if (!release_buffer("camera_direction"))
		return false;
	if (!release_buffer("camera_position"))
		return false;

	return true;
}

bool CLCaster::validate() {

    if (!compile_kernel("../kernels/ray_caster_kernel.cl", true, "raycaster")) {
        Logger::log("Failed to compile the kernel", Logger::LogLevel::ERROR, __LINE__, __FILE__);
        std::cin.get(); // hang the output window so we can read the error
        return false;
    }

	Logger::log("Validating OpenCL kernel args", Logger::LogLevel::INFO);

	// Check to make sure everything has been entered
	if (!camera.get()) {
		Logger::log("Raycaster.validate() failed, camera not initialized", Logger::LogLevel::WARN);
		return false;
	}
	if (!map.get()) {
		Logger::log("Raycaster.validate() failed, map not initialized", Logger::LogLevel::WARN);
		return false;
	}
	if (!viewport_image) {
		Logger::log("Raycaster.validate() failed, viewport_image not initialized", Logger::LogLevel::WARN);
		return false;
	}
	if (!viewport_matrix) {
		Logger::log("Raycaster.validate() failed, viewport_matrix not initialized", Logger::LogLevel::WARN);
		return false;
	}

	// Set all the kernel args
	set_kernel_arg("raycaster", 0, "map");
	set_kernel_arg("raycaster", 1, "map_dimensions");
	set_kernel_arg("raycaster", 2, "viewport_resolution");
	set_kernel_arg("raycaster", 3, "viewport_matrix");
	set_kernel_arg("raycaster", 4, "camera_direction");
	set_kernel_arg("raycaster", 5, "camera_position");
	set_kernel_arg("raycaster", 6, "lights");
	set_kernel_arg("raycaster", 7, "light_count");
	set_kernel_arg("raycaster", 8, "image");
	//set_kernel_arg("raycaster", 9, "seed");
	set_kernel_arg("raycaster", 9, "texture_atlas");
	set_kernel_arg("raycaster", 10, "atlas_dim");
	set_kernel_arg("raycaster", 11, "tile_dim");
	set_kernel_arg("raycaster", 12, "octree_descriptor_buffer");
	set_kernel_arg("raycaster", 13, "octree_attachment_lookup_buffer");
	set_kernel_arg("raycaster", 14, "octree_attachment_buffer");
	set_kernel_arg("raycaster", 15, "settings_buffer");

	return true;

}

bool CLCaster::create_texture_atlas(sf::Texture *t, sf::Vector2i tile_dim) {
	
	if (!create_image_buffer("texture_atlas", t->getSize().x * t->getSize().x * 4 * sizeof(float), t, CL_MEM_READ_ONLY))
		return false;
	
	// create_buffer observes arg 3's
	sf::Vector2u v = t->getSize();
	
	if (!create_buffer("atlas_dim", sizeof(sf::Vector2u) , &v))
		return false;
	if (!create_buffer("tile_dim", sizeof(sf::Vector2i), &tile_dim))
		return false;

	return true;
}

bool CLCaster::compute() {
	
	// correlating work size with texture size? good, bad?
	return run_kernel("raycaster", viewport_texture.getSize().x, viewport_texture.getSize().y);
}

// There is a possibility that I would want to move this over to be all inside it's own
// container to make it so it can be changed via CL_MEM_USE_HOST_PTR. But I doubt it
// would ever be called enough to warrant that
bool CLCaster::create_viewport(int width, int height, float v_fov, float h_fov) {
	
	// CL needs the screen resolution
	sf::Vector2i view_res(width, height);
	if (!create_buffer("viewport_resolution", sizeof(int) * 2, &view_res))
		return false;

	// And an array of vectors describing the way the "lens" of our
	// camera works
	viewport_matrix = new sf::Vector4f[width * height * 4];

	for (int y = -view_res.y / 2; y < view_res.y / 2; y++) {

		for (int x = -view_res.x / 2; x < view_res.x / 2; x++) {

			// The base ray direction to slew from
			sf::Vector3f ray(-800, x, y);

			// correct for the base ray pointing to (1, 0, 0) as (0, 0). Should equal (1.57, 0)
			ray = sf::Vector3f(
				static_cast<float>(ray.z * sin(1.57) + ray.x * cos(1.57)),
				static_cast<float>(ray.y),
				static_cast<float>(ray.z * cos(1.57) - ray.x * sin(1.57))
			);

//             ray.y += (rand() % 1000) / 100000.0;
//             ray.x += (rand() % 1000) / 100000.0;
//             ray.z += (rand() % 1000) / 100000.0;

			ray = Normalize(ray);
			int index = (x + view_res.x / 2) + view_res.x * (y + view_res.y / 2);

			viewport_matrix[index] = sf::Vector4f(
					ray.x,
					ray.y,
					ray.z,
					0
			);
		}
	}

	if (!create_buffer("viewport_matrix", sizeof(float) * 4 * view_res.x * view_res.y, viewport_matrix))
		return false;

	// Create the image that opencl's rays write to
	viewport_image = new sf::Uint8[width * height * 4];

	for (int i = 0; i < width * height * 4; i += 4) {

		viewport_image[i] = 255;     // R
		viewport_image[i + 1] = 255; // G
		viewport_image[i + 2] = 255; // B
		viewport_image[i + 3] = 100; // A
	}

	// Interop lets us keep a reference to it as a texture
	viewport_texture.create(width, height);
	viewport_texture.update(viewport_image);
	viewport_sprite.setTexture(viewport_texture);

	// Pass the buffer to opencl
	if (!create_image_buffer("image", sizeof(sf::Uint8) * width * height * 4, &viewport_texture, CL_MEM_WRITE_ONLY))
		return false;

	return true;

}

bool CLCaster::release_viewport() {

    bool success = true;
    if (!release_buffer("viewport_resolution"))
        success = false;
    if (!release_buffer("viewport_matrix"))
        success = false;
    if (!release_buffer("image"))
        success = false;
    return success;
}

bool CLCaster::assign_lights(std::vector<PackedData> *data) {

	// Get a pointer to the packed light data
	this->lights = data;

	light_count = static_cast<int>(lights->size());

	cl_uint packed_size = sizeof(PackedData);

	if (!create_buffer("lights", packed_size * light_count, lights->data(), CL_MEM_READ_ONLY | CL_MEM_USE_HOST_PTR))
		return false;
	if (!create_buffer("light_count", 8, &light_count))
		return false;

	return true;
}

void CLCaster::draw(sf::RenderWindow* window) {
	window->draw(viewport_sprite);
}

bool CLCaster::debug_quick_recompile() {
	
	if (!compile_kernel("../kernels/ray_caster_kernel.cl", true, "raycaster")) {
		Logger::log("Failed to recompile kernel", Logger::LogLevel::WARN, __LINE__, __FILE__);
		std::cin.get(); // hang the output window so we can read the error
		return false;
	}
	
	return validate();

}


void CLCaster::render_gui() {
	
	ImGui::Begin("CLCaster");

	if (ImGui::Button("Recompile Kernel")) {
		while (!debug_quick_recompile()) {
			std::cin.get();
		};
	}

	ImGui::Checkbox("HEX", &hex); ImGui::SameLine();
	ImGui::Checkbox("DECIMAL", &decimal); ImGui::SameLine();
	ImGui::Checkbox("BINARY", &binary);

    std::stringstream ss;

	int columns = 1;

	if (binary) {columns++;} if (decimal) {columns++;} if (hex) {columns++;}

    ImGui::Columns(columns, NULL, true);

    for (auto const& x : settings_buffer_indices) {

		int offset = 150;
		int count = 1;

        ImGui::Text("%s", x.first.c_str());

        ImGui::NextColumn();

		if (decimal) {
			ImGui::SetColumnOffset(count, offset);
			offset += 150; count++;
			ss << std::dec << std::to_string(settings_buffer[x.second]);
			ImGui::Text("%s", ss.str().c_str());
			ss.clear();
			ss.str("");
			ImGui::NextColumn();
		}

		if (hex){
			ImGui::SetColumnOffset(count, offset);
			offset += 150; count++;
			ss << "0x" << std::setfill ('0') << std::setw(sizeof(int64_t)*2) << std::hex << settings_buffer[x.second];
			ImGui::Text("%s", ss.str().c_str());
			ss.clear();
			ss.str("");
			ImGui::NextColumn();
		}

		if (binary) {
			ImGui::SetColumnOffset(count, offset);
			offset += 150; count++;
			ss << "b" << std::setfill('0') << std::setw(sizeof(int64_t) * 2) << std::bitset<64>(settings_buffer[x.second]);
			ImGui::Text("%s", ss.str().c_str());
			ss.clear();
			ss.str("");
			ImGui::NextColumn();
		}

    }
	ImGui::End();
}


void CLCaster::update_gui() {
	rendering = true;
}


void CLCaster::event_handler(VrEventPublisher *publisher, std::unique_ptr<vr::Event> event) {

	if (event->type == vr::Event::KeyPressed) {

		vr::KeyPressed *key_event = static_cast<vr::KeyPressed*>(event.get());

		if (key_event->code == sf::Keyboard::T) {
			debug_quick_recompile();
		}
}
}

bool CLCaster::aquire_hardware() {

	Logger::log("Acquiring OpenCL Hardware", Logger::LogLevel::INFO);

	// Get the number of platforms
	cl_uint platform_count = 0;

	error = clGetPlatformIDs(0, nullptr, &platform_count);

	if (cl_assert(error)) {
		Logger::log("Failed at clGetPlatformIDs() :" + cl_err_lookup(error), Logger::LogLevel::ERROR, __LINE__, __FILE__);
		return false;
	}

	if (platform_count == 0) {
		Logger::log("There appears to be no OpenCL platforms on this machine", Logger::LogLevel::ERROR, __LINE__, __FILE__);
		return false;
	}

	// Get the ID's for those platforms
	std::vector<cl_platform_id> plt_buf(platform_count);

	error = clGetPlatformIDs(platform_count, plt_buf.data(), nullptr);

	if (cl_assert(error)) {
		Logger::log("Failed at clGetPlatformIDs() :" + cl_err_lookup(error), Logger::LogLevel::ERROR, __LINE__, __FILE__);
		return false;
	}

	// Cycle through the platform ID's
	for (unsigned int i = 0; i < platform_count; i++) {

		// And get their device count
		cl_uint deviceIdCount = 0;
		error = clGetDeviceIDs(plt_buf[i], CL_DEVICE_TYPE_ALL, 0, nullptr, &deviceIdCount);

		if (cl_assert(error)) {
			Logger::log("Failed at clGetDeviceIDs() :" + cl_err_lookup(error), Logger::LogLevel::ERROR, __LINE__, __FILE__);
			return false;
		}

		if (deviceIdCount == 0) {
			Logger::log("There appears to be no OpenCL platforms on this platform", Logger::LogLevel::INFO, __LINE__, __FILE__);
		}
		else {

			// Get the device ids and place them in the device list
			std::vector<cl_device_id> deviceIds(deviceIdCount);

			error = clGetDeviceIDs(plt_buf[i], CL_DEVICE_TYPE_ALL, deviceIdCount, deviceIds.data(), NULL);

			if (cl_assert(error)) {
				Logger::log("Failed at clGetDeviceIDs() :" + cl_err_lookup(error), Logger::LogLevel::ERROR, __LINE__, __FILE__);
				return false;
			}

			for (int d = 0; d < deviceIds.size(); d++) {
				device_list.emplace_back(device(deviceIds[d], plt_buf.at(i)));
			}
		}
	}

	return true;
}

void CLCaster::save_config() {

	Logger::log("Saving OpenCL hardware config", Logger::LogLevel::INFO);

	std::ofstream output_file;
	output_file.open("device_config.bin", std::ofstream::binary | std::ofstream::out | std::ofstream::trunc);

	device d(device_id, platform_id);
	d.print_packed_data(output_file);

	output_file.close();
}

bool CLCaster::load_config() {

	Logger::log("Loading hardware config", Logger::LogLevel::INFO);

	std::ifstream input_file("device_config.bin", std::ios::binary | std::ios::in);

	if (!input_file.is_open()) {
		Logger::log("No device_config.bin file found", Logger::LogLevel::WARN);
		return false;
	}

	device::packed_data data;
	input_file.read(reinterpret_cast<char*>(&data), sizeof(data));
	input_file.close();

	bool found = false;

	for (auto d : device_list) {

		if (memcmp(&d, &data, sizeof(device::packed_data)) == 0) {
			Logger::log("Found saved hardware config", Logger::LogLevel::INFO);
			found = true;
			device_id = d.getDeviceId();
			platform_id = d.getPlatformId();
			break;
		}
	}

	if (!found) {
		Logger::log("No hardware matching the saved device in device_config.bin found", Logger::LogLevel::WARN);
		return false;
	}

	return true;
}

bool CLCaster::query_hardware()
{

	Logger::log("Querying OpenCL hardware", Logger::LogLevel::INFO);

	// Get the number of platforms
	cl_uint plt_cnt = 0;
	error = clGetPlatformIDs(0, nullptr, &plt_cnt);

	if (cl_assert(error)) {
		Logger::log("Failed at clGetPlatformIDs() :" + cl_err_lookup(error), Logger::LogLevel::ERROR, __LINE__, __FILE__);
		return false;
	}

	// Fetch the platforms
	std::map<cl_platform_id, std::vector<device_info>> plt_ids;

	// buffer before map init
	std::vector<cl_platform_id> plt_buf(plt_cnt);
	error = clGetPlatformIDs(plt_cnt, plt_buf.data(), nullptr);

	if (cl_assert(error)) {
		Logger::log("Failed at clGetPlatformIDs() :" + cl_err_lookup(error), Logger::LogLevel::ERROR, __LINE__, __FILE__);
		return false;
	}

	// Map init
	for (auto id : plt_buf) {
		plt_ids.emplace(std::make_pair(id, std::vector<device_info>()));
	}

	// For each platform, populate its devices
	for (unsigned int i = 0; i < plt_cnt; i++) {

		cl_uint deviceIdCount = 0;
		error = clGetDeviceIDs(plt_buf[i], CL_DEVICE_TYPE_ALL, 0, nullptr, &deviceIdCount);

		// Check to see if we even have OpenCL on this machine
		if (deviceIdCount == 0) {
			Logger::log("No devices supporting OpenCL found", Logger::LogLevel::ERROR, __LINE__, __FILE__);
			return false;
		}

		// Get the device ids
		std::vector<cl_device_id> deviceIds(deviceIdCount);
		error = clGetDeviceIDs(plt_buf[i], CL_DEVICE_TYPE_ALL, deviceIdCount, deviceIds.data(), NULL);

		if (cl_assert(error)) {
			Logger::log("Failed at clGetDeviceIDs() :" + cl_err_lookup(error), Logger::LogLevel::ERROR, __LINE__, __FILE__);
			return false;
		}

		for (unsigned int q = 0; q < deviceIdCount; q++) {

			device_info d;

			cl_device_id id = deviceIds[q];
			
			clGetDeviceInfo(id, CL_DEVICE_ADDRESS_BITS, sizeof(cl_uint), &d.cl_device_address_bits, NULL);			
			clGetDeviceInfo(id, CL_DEVICE_AVAILABLE, sizeof(cl_bool), &d.cl_device_available, NULL);
			clGetDeviceInfo(id, CL_DEVICE_COMPILER_AVAILABLE, sizeof(cl_bool), &d.cl_device_compiler_available, NULL);
			clGetDeviceInfo(id, CL_DEVICE_ENDIAN_LITTLE, sizeof(cl_bool), &d.cl_device_endian_little, NULL);
			clGetDeviceInfo(id, CL_DEVICE_ERROR_CORRECTION_SUPPORT, sizeof(cl_bool), &d.cl_device_error_correction_support, NULL);
			clGetDeviceInfo(id, CL_DEVICE_EXTENSIONS, sizeof(char)*1024, &d.cl_device_extensions, NULL);
			clGetDeviceInfo(id, CL_DEVICE_GLOBAL_MEM_CACHE_SIZE, sizeof(cl_ulong), &d.cl_device_global_mem_cache_size, NULL);
			clGetDeviceInfo(id, CL_DEVICE_GLOBAL_MEM_CACHELINE_SIZE, sizeof(cl_uint), &d.cl_device_global_mem_cacheline_size, NULL);
			clGetDeviceInfo(id, CL_DEVICE_GLOBAL_MEM_SIZE, sizeof(cl_ulong), &d.cl_device_global_mem_size, NULL);
			clGetDeviceInfo(id, CL_DEVICE_IMAGE_SUPPORT, sizeof(cl_bool), &d.cl_device_image_support, NULL);
			clGetDeviceInfo(id, CL_DEVICE_IMAGE2D_MAX_HEIGHT, sizeof(size_t), &d.cl_device_image2d_max_height, NULL);
			clGetDeviceInfo(id, CL_DEVICE_IMAGE2D_MAX_WIDTH, sizeof(size_t), &d.cl_device_image2d_max_width, NULL);
			clGetDeviceInfo(id, CL_DEVICE_IMAGE3D_MAX_DEPTH, sizeof(size_t), &d.cl_device_image3d_max_depth, NULL);
			clGetDeviceInfo(id, CL_DEVICE_IMAGE3D_MAX_HEIGHT, sizeof(size_t), &d.cl_device_image3d_max_height, NULL);
			clGetDeviceInfo(id, CL_DEVICE_IMAGE3D_MAX_WIDTH, sizeof(size_t), &d.cl_device_image3d_max_width, NULL);
			clGetDeviceInfo(id, CL_DEVICE_LOCAL_MEM_SIZE, sizeof(cl_ulong), &d.cl_device_local_mem_size, NULL);
			clGetDeviceInfo(id, CL_DEVICE_MAX_CLOCK_FREQUENCY, sizeof(size_t), &d.cl_device_max_clock_frequency, NULL);
			clGetDeviceInfo(id, CL_DEVICE_MAX_COMPUTE_UNITS, sizeof(size_t), &d.cl_device_max_compute_units, NULL);
			clGetDeviceInfo(id, CL_DEVICE_MAX_CONSTANT_ARGS, sizeof(size_t), &d.cl_device_max_constant_args, NULL);
			clGetDeviceInfo(id, CL_DEVICE_MAX_CONSTANT_BUFFER_SIZE, sizeof(cl_ulong), &d.cl_device_max_constant_buffer_size, NULL);
			clGetDeviceInfo(id, CL_DEVICE_MAX_MEM_ALLOC_SIZE, sizeof(cl_ulong), &d.cl_device_max_mem_alloc_size, NULL);
			clGetDeviceInfo(id, CL_DEVICE_MAX_PARAMETER_SIZE, sizeof(size_t), &d.cl_device_max_parameter_size, NULL);
			clGetDeviceInfo(id, CL_DEVICE_MAX_READ_IMAGE_ARGS, sizeof(cl_uint), &d.cl_device_max_read_image_args, NULL);
			clGetDeviceInfo(id, CL_DEVICE_MAX_SAMPLERS, sizeof(cl_uint), &d.cl_device_max_samplers, NULL);
			clGetDeviceInfo(id, CL_DEVICE_MAX_WORK_GROUP_SIZE, sizeof(cl_ulong), &d.cl_device_max_work_group_size, NULL);
			clGetDeviceInfo(id, CL_DEVICE_MAX_WORK_ITEM_DIMENSIONS, sizeof(cl_ulong), &d.cl_device_max_work_item_dimensions, NULL);
			clGetDeviceInfo(id, CL_DEVICE_MAX_WORK_ITEM_SIZES, sizeof(size_t)*3, &d.cl_device_max_work_item_sizes, NULL);
			clGetDeviceInfo(id, CL_DEVICE_MAX_WRITE_IMAGE_ARGS, sizeof(cl_uint), &d.cl_device_max_write_image_args, NULL);
			clGetDeviceInfo(id, CL_DEVICE_MEM_BASE_ADDR_ALIGN, sizeof(cl_uint), &d.cl_device_mem_base_addr_align, NULL);
			clGetDeviceInfo(id, CL_DEVICE_MIN_DATA_TYPE_ALIGN_SIZE, sizeof(cl_uint), &d.cl_device_min_data_type_align_size, NULL);
			clGetDeviceInfo(id, CL_DEVICE_NAME, sizeof(char)*128, &d.cl_device_name, NULL);
			clGetDeviceInfo(id, CL_DEVICE_PLATFORM, sizeof(cl_platform_id), &d.cl_device_platform, NULL);
			clGetDeviceInfo(id, CL_DEVICE_PREFERRED_GLOBAL_ATOMIC_ALIGNMENT, sizeof(cl_uint), &d.cl_device_preferred_vector_width_char, NULL);
			clGetDeviceInfo(id, CL_DEVICE_PREFERRED_VECTOR_WIDTH_SHORT, sizeof(cl_uint), &d.cl_device_preferred_vector_width_short, NULL);
			clGetDeviceInfo(id, CL_DEVICE_PREFERRED_VECTOR_WIDTH_INT, sizeof(cl_uint), &d.cl_device_preferred_vector_width_int, NULL);
			clGetDeviceInfo(id, CL_DEVICE_PREFERRED_VECTOR_WIDTH_LONG, sizeof(cl_uint), &d.cl_device_preferred_vector_width_long, NULL);
			clGetDeviceInfo(id, CL_DEVICE_PREFERRED_VECTOR_WIDTH_FLOAT, sizeof(cl_uint), &d.cl_device_preferred_vector_width_float, NULL);
			clGetDeviceInfo(id, CL_DEVICE_PREFERRED_VECTOR_WIDTH_DOUBLE, sizeof(cl_uint), &d.cl_device_preferred_vector_width_double, NULL);
			clGetDeviceInfo(id, CL_DEVICE_PROFILE, sizeof(char) * 256, &d.cl_device_profile, NULL);
			clGetDeviceInfo(id, CL_DEVICE_PROFILING_TIMER_RESOLUTION, sizeof(size_t), &d.cl_device_profiling_timer_resolution, NULL);
			clGetDeviceInfo(id, CL_DEVICE_TYPE, sizeof(cl_device_type), &d.device_type, NULL);
			clGetDeviceInfo(id, CL_DEVICE_VENDOR, sizeof(char)*128, &d.cl_device_vendor, NULL);
			clGetDeviceInfo(id, CL_DEVICE_VENDOR_ID, sizeof(cl_uint), &d.cl_device_vendor_id, NULL);
			clGetDeviceInfo(id, CL_DEVICE_VERSION, sizeof(char)*128, &d.cl_device_version, NULL);
			clGetDeviceInfo(id, CL_DRIVER_VERSION, sizeof(char)*128, &d.cl_driver_version, NULL);

			plt_ids.at(d.cl_device_platform).push_back(d);
		}
	}

	return true;
}

bool CLCaster::create_shared_context()
{

	// Hurray for standards!
	// Setup the context properties to grab the current GL context

#ifdef linux

	cl_context_properties context_properties[] = {
		CL_GL_CONTEXT_KHR, (cl_context_properties)glXGetCurrentContext(),
		CL_GLX_DISPLAY_KHR, (cl_context_properties)glXGetCurrentDisplay(),
        //CL_GL_CONTEXT_KHR, (cl_context_properties)eglGetCurrentContext(),
        //CL_EGL_DISPLAY_KHR, (cl_context_properties)eglGetCurrentDisplay(),
		CL_CONTEXT_PLATFORM, (cl_context_properties)platform_id,
		0
	};

#elif defined _WIN32

	HDC hDC = wglGetCurrentDC();
	HGLRC hGLRC = wglGetCurrentContext();
	cl_context_properties context_properties[] = {
		CL_CONTEXT_PLATFORM, (cl_context_properties)platform_id,
		CL_GL_CONTEXT_KHR, (cl_context_properties)hGLRC,
		CL_WGL_HDC_KHR, (cl_context_properties)hDC,
		0
	};

#elif defined TARGET_OS_MAC

	CGLContextObj glContext = CGLGetCurrentContext();
	CGLShareGroupObj shareGroup = CGLGetShareGroup(glContext);
	cl_context_properties context_properties[] = {
		CL_CONTEXT_PROPERTY_USE_CGL_SHAREGROUP_APPLE,
		(cl_context_properties)shareGroup,
		0
	};

#endif

	// Create our shared context
	context = clCreateContext(
		context_properties,
		1,
		&device_id,
		nullptr, nullptr,
		&error
		);

	if (cl_assert(error)) {
		Logger::log("Failed at clCreateContext() :" + cl_err_lookup(error), Logger::LogLevel::ERROR, __LINE__, __FILE__);
		return false;
	}

	return true;
}

bool CLCaster::create_command_queue() {

	// If context and device_id have initialized
	if (context && device_id) {
		
		command_queue = clCreateCommandQueue(context, device_id, 0, &error);

		if (cl_assert(error)) {
			Logger::log("Failed at clCreateCommandQueue() :" + cl_err_lookup(error), Logger::LogLevel::ERROR, __LINE__, __FILE__);
			return false;
		}

	} else {

		Logger::log("Failed creating the command queue. Context or device_id not initialized", Logger::LogLevel::ERROR, __LINE__, __FILE__);
		return false;
	}

	return true;
}

bool CLCaster::compile_kernel(std::string kernel_source, bool is_path, std::string kernel_name) {
	
	Logger::log("Compiling OpenCL Kernel", Logger::LogLevel::INFO);
	
	const char* source;
	std::string tmp;

	if (is_path) {
		//Load in the kernel, and c stringify it
		tmp = read_file(kernel_source);
		source = tmp.c_str();
	}
	else {
		source = kernel_source.c_str();
	}

	size_t kernel_source_size = strlen(source);

	// Load the source into CL's data structure
	cl_program program = clCreateProgramWithSource(
		context, 1,
		&source,
		&kernel_source_size, &error
		);

	// This is not for compilation, it only loads the source
	if (cl_assert(error)) {
		Logger::log("Failed at clCreateProgramWithSource() :" + cl_err_lookup(error), Logger::LogLevel::ERROR, __LINE__, __FILE__);
		return false;
	}

    std::stringstream build_string_stream;

    // walk the settings index's and add them to the defines
    for (auto const& define : defines_map){
        build_string_stream << " -D" << define.first << "=" << define.second;
    }

	build_string_stream << " -cl-finite-math-only -cl-fast-relaxed-math -cl-unsafe-math-optimizations -cl-opt-disable";

    std::string build_string = build_string_stream.str();

	error = clBuildProgram(program, 1, &device_id, build_string.c_str(), NULL, NULL);

	// Check to see if it error'd out
	if (cl_assert(error)) {

		// Get the size of the queued log
		size_t log_size;
		clGetProgramBuildInfo(program, device_id, CL_PROGRAM_BUILD_LOG, 0, NULL, &log_size);
		char *log = new char[log_size];

		// Grab the log
		clGetProgramBuildInfo(program, device_id, CL_PROGRAM_BUILD_LOG, log_size, log, NULL);

		Logger::log("Failed at clBuildProgram() with " + cl_err_lookup(error), Logger::LogLevel::ERROR, __LINE__, __FILE__);
		Logger::log("CL_ERROR -->" + std::string(log), Logger::LogLevel::ERROR, __LINE__, __FILE__);

		return false;
	}

	// Done initializing the kernel
	cl_kernel kernel = clCreateKernel(program, kernel_name.c_str(), &error);

	if (cl_assert(error)) {
		Logger::log("Failed at clCreateKernel() :" + cl_err_lookup(error), Logger::LogLevel::ERROR, __LINE__, __FILE__);
		return false;
	}

	// Do I want these to overlap when repeated??
	kernel_map[kernel_name] = kernel;
	//kernel_map.emplace(std::make_pair(kernel_name, kernel));

	return true;
}

bool CLCaster::set_kernel_arg(
	std::string kernel_name,
	int index,
	std::string buffer_name) {

	error = clSetKernelArg(
		kernel_map.at(kernel_name),
		index,
		sizeof(cl_mem),
		(void *)&buffer_map.at(buffer_name));

	if (cl_assert(error)) {
		Logger::log("Failed at clSetKernelArg() :" + cl_err_lookup(error), Logger::LogLevel::ERROR, __LINE__, __FILE__);
		Logger::log("Buffer name : " + buffer_name, Logger::LogLevel::ERROR, __LINE__, __FILE__);
		return false;
	}

	return true;

}

bool CLCaster::create_image_buffer(std::string buffer_name, cl_uint size, sf::Texture* texture, cl_int access_type) {

	// I can imagine overwriting buffers will be common, so I think
	// this is safe to overwrite / release old buffers quietly
	if (buffer_map.count(buffer_name) > 0) {
		Logger::log("buffer_map already contains buffer of the same name, releasing conflicting buffer : " + buffer_name, Logger::LogLevel::INFO);
		if (!release_buffer(buffer_name))
			return false;
	}

	cl_mem buff = clCreateFromGLTexture(
		getContext(), access_type, GL_TEXTURE_2D,
		0, texture->getNativeHandle(), &error);

	if (cl_assert(error)) {
		Logger::log("Failed at clCreateFromGLTexture() :" + cl_err_lookup(error), Logger::LogLevel::ERROR, __LINE__, __FILE__);
		return false;
	}

	if (!store_buffer(buff, buffer_name))
		return false;

	return true;
}

bool CLCaster::create_buffer(std::string buffer_name, cl_uint size, void* data, cl_mem_flags flags) {

	// I can imagine overwriting buffers will be common, so I think
	// this is safe to overwrite / release old buffers quietly
	if (buffer_map.count(buffer_name) > 0) {
		Logger::log("buffer_map already contains buffer of the same name, releasing conflicting buffer : " + buffer_name, Logger::LogLevel::INFO);
		if (!release_buffer(buffer_name))
			return false;
	}

	cl_mem buff = clCreateBuffer(
		getContext(), flags,
		size, data, &error
		);

	if (cl_assert(error)) {
		Logger::log("Failed at clCreateBuffer() :" + cl_err_lookup(error), Logger::LogLevel::ERROR, __LINE__, __FILE__);
		Logger::log("Buffer name : " + buffer_name, Logger::LogLevel::ERROR, __LINE__, __FILE__);
		return false;
	}

	if (!store_buffer(buff, buffer_name))
		return false;

	return true;

}

bool CLCaster::create_buffer(std::string buffer_name, cl_uint size, void* data) {
	
	// I can imagine overwriting buffers will be common, so I think
	// this is safe to overwrite / release old buffers quietly
	if (buffer_map.count(buffer_name) > 0) {
		Logger::log("buffer_map already contains buffer of the same name, releasing conflicting buffer : " + buffer_name, Logger::LogLevel::INFO);
		if (!release_buffer(buffer_name))
			return false;
	}

	cl_mem buff = clCreateBuffer(
		getContext(), CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR,
		size, data, &error
		);

	if (cl_assert(error)) {
		Logger::log("Failed at clCreateBuffer() :" + cl_err_lookup(error), Logger::LogLevel::ERROR, __LINE__, __FILE__);
		Logger::log("Buffer name : " + buffer_name, Logger::LogLevel::ERROR, __LINE__, __FILE__);
		return false;
	}

	if (!store_buffer(buff, buffer_name))
		return false;

	return true;

}

bool CLCaster::release_buffer(std::string buffer_name) {

	if (buffer_map.count(buffer_name) > 0) {

		int error = clReleaseMemObject(buffer_map.at(buffer_name));
		
		if (cl_assert(error)) {
			Logger::log("Error releasing buffer at clReleaseMemObject()" + cl_err_lookup(error), Logger::LogLevel::ERROR, __LINE__, __FILE__);
			Logger::log("buffer not removed : " + buffer_name, Logger::LogLevel::WARN, __LINE__, __FILE__);
			return false;
		} 
	
		buffer_map.erase(buffer_name);
		
	} else {
		Logger::log("Error releasing buffer, buffer not found : " + buffer_name , Logger::LogLevel::ERROR, __LINE__, __FILE__);
		return false;
	}

	return true;

}

bool CLCaster::store_buffer(cl_mem buffer, std::string buffer_name) {
	
	if (buffer_map.count(buffer_name) == 0) {
		buffer_map.emplace(std::make_pair(buffer_name, buffer));
		return true;
	}

	Logger::log("Failed to store buffer : " + buffer_name + " , name already taken", Logger::LogLevel::ERROR, __LINE__, __FILE__);
	return false;

}

bool CLCaster::run_kernel(std::string kernel_name, const int work_dim_x, const int work_dim_y) {

	size_t global_work_size[2] = { static_cast<size_t>(work_dim_x), static_cast<size_t>(work_dim_y)};

	cl_kernel kernel = kernel_map.at(kernel_name);

	error = clEnqueueAcquireGLObjects(getCommandQueue(), 1, &buffer_map.at("image"), 0, 0, 0);

	if (cl_assert(error)) {
		Logger::log("Failed at clEnqueueAcquireGLObjects() :" + cl_err_lookup(error), Logger::LogLevel::ERROR, __LINE__, __FILE__);
		return false;
	}

	//error = clEnqueueTask(command_queue, kernel, 0, NULL, NULL);
	error = clEnqueueNDRangeKernel(
		command_queue, kernel,
		2, NULL, global_work_size,
		NULL, 0, NULL, NULL);

	if (cl_assert(error)) {
		Logger::log("Failed at clEnqueueNDRangeKernel() :" + cl_err_lookup(error), Logger::LogLevel::ERROR, __LINE__, __FILE__);
		return false;
	}

	error = clFinish(getCommandQueue());

	if (cl_assert(error)) {
		Logger::log("Failed at clFinish() :" + cl_err_lookup(error), Logger::LogLevel::ERROR, __LINE__, __FILE__);
		return false;
	}

	// What if errors out and gl objects are never released?
	error = clEnqueueReleaseGLObjects(getCommandQueue(), 1, &buffer_map.at("image"), 0, NULL, NULL);

	if (cl_assert(error)) {
		Logger::log("Failed at clEnqueueReleaseGLObjects() :" + cl_err_lookup(error), Logger::LogLevel::ERROR, __LINE__, __FILE__);
		return false;
	}


	return true;
}

void CLCaster::print_kernel_arguments()
{
	compile_kernel("../kernels/print_arguments.cl", true, "printer");
	set_kernel_arg("printer", 0, "map");
	set_kernel_arg("printer", 1, "map_dimensions");
	set_kernel_arg("printer", 2, "viewport_resolution");
	set_kernel_arg("printer", 3, "viewport_matrix");
	set_kernel_arg("printer", 4, "camera_direction");
	set_kernel_arg("printer", 5, "camera_position");
	set_kernel_arg("printer", 6, "lights");
	set_kernel_arg("printer", 7, "light_count");
	set_kernel_arg("printer", 8, "image");

	run_kernel("printer", 1, 1);
}

cl_device_id CLCaster::getDeviceID() { return device_id; };
cl_platform_id CLCaster::getPlatformID() { return platform_id; };
cl_context CLCaster::getContext() { return context; };
cl_kernel CLCaster::getKernel(std::string kernel_name) { return kernel_map.at(kernel_name); };
cl_command_queue CLCaster::getCommandQueue() { return command_queue; };

bool CLCaster::cl_assert(int error_code) {
	
	if (error_code == CL_SUCCESS || error_code == 1)
		return false;
	else
		return true;
}



void CLCaster::set_define(std::string name, std::string value) {
	defines_map[name] = value;
}

void CLCaster::remove_define(std::string name) {
    defines_map.erase(name);
}

template<typename T>
bool CLCaster::add_to_settings_buffer(std::string setting_name, std::string define_accessor_name, T value) {

    bool success = true;

    if (settings_buffer == nullptr){

        Logger::log("Trying to push settings to an uninitialized settings buffer", Logger::LogLevel::ERROR, __LINE__, __FILE__);
        success = false;

    } else if (defines_map.count(define_accessor_name)) {

        Logger::log("Define name already present in the defines map", Logger::LogLevel::ERROR, __LINE__, __FILE__);
        success = false;

    } else {

        if (settings_buffer_position < SETTINGS_BUFFER_SIZE) {
            defines_map[define_accessor_name] = std::to_string(settings_buffer_position);
            settings_buffer[settings_buffer_position] = (int64_t)value;
			settings_buffer_indices[setting_name] = settings_buffer_position;
            settings_buffer_position++;
        } else {
            Logger::log("Settings buffer has reached the maximum size of " + std::to_string(SETTINGS_BUFFER_SIZE) + " elements", Logger::LogLevel::ERROR, __LINE__, __FILE__);
            success = false;
        }
    }

    return success;
}

template bool CLCaster::add_to_settings_buffer(std::string, std::string, int);
template bool CLCaster::add_to_settings_buffer(std::string, std::string, long);
template bool CLCaster::add_to_settings_buffer(std::string, std::string, bool);
template bool CLCaster::add_to_settings_buffer(std::string, std::string, char);
template bool CLCaster::add_to_settings_buffer(std::string, std::string, uint64_t);

bool CLCaster::create_settings_buffer() {

    settings_buffer = new int64_t[SETTINGS_BUFFER_SIZE]();
    if (!create_buffer("settings_buffer", sizeof(int64_t) * SETTINGS_BUFFER_SIZE, settings_buffer, CL_MEM_USE_HOST_PTR))
        return false;
    return true;
}

bool CLCaster::remove_from_settings_buffer(std::string setting_name) {

    Logger::log("remove_from_settings_buffer() not implimented", Logger::LogLevel::WARN, __LINE__, __FILE__);
    return false;
}

bool CLCaster::release_settings_buffer() {

    if (!release_buffer("settings_buffer"))
        return false;
    return true;
}

bool CLCaster::overwrite_setting(std::string settings_name, int64_t *value) {

    bool success = true;

    if (settings_buffer == nullptr){

        Logger::log("Trying to push settings to an uninitialized settings buffer", Logger::LogLevel::ERROR, __LINE__, __FILE__);
        success = false;

    } else {

        if (settings_buffer_indices.count(settings_name)) {

            unsigned int postion = settings_buffer_indices[settings_name];
            settings_buffer[postion] = *value;
        } else {

            Logger::log("No setting matching [" + settings_name +"]", Logger::LogLevel::ERROR, __LINE__, __FILE__);
            success = false;
        }
    }
    return success;
}


std::string CLCaster::cl_err_lookup(int error_code) {

    std::string err_msg = "";

    switch (error_code) {

        case CL_SUCCESS:
            err_msg += "CL_SUCCESS";
            break;
        case 1:
            err_msg += "CL_SUCCESS";
            break;
        case CL_DEVICE_NOT_FOUND:
            err_msg += "CL_DEVICE_NOT_FOUND";
            break;
        case CL_DEVICE_NOT_AVAILABLE:
            err_msg = "CL_DEVICE_NOT_AVAILABLE";
            break;
        case CL_COMPILER_NOT_AVAILABLE:
            err_msg = "CL_COMPILER_NOT_AVAILABLE";
            break;
        case CL_MEM_OBJECT_ALLOCATION_FAILURE:
            err_msg = "CL_MEM_OBJECT_ALLOCATION_FAILURE";
            break;
        case CL_OUT_OF_RESOURCES:
            err_msg = "CL_OUT_OF_RESOURCES";
            break;
        case CL_OUT_OF_HOST_MEMORY:
            err_msg = "CL_OUT_OF_HOST_MEMORY";
            break;
        case CL_PROFILING_INFO_NOT_AVAILABLE:
            err_msg = "CL_PROFILING_INFO_NOT_AVAILABLE";
            break;
        case CL_MEM_COPY_OVERLAP:
            err_msg = "CL_MEM_COPY_OVERLAP";
            break;
        case CL_IMAGE_FORMAT_MISMATCH:
            err_msg = "CL_IMAGE_FORMAT_MISMATCH";
            break;
        case CL_IMAGE_FORMAT_NOT_SUPPORTED:
            err_msg = "CL_IMAGE_FORMAT_NOT_SUPPORTED";
            break;
        case CL_BUILD_PROGRAM_FAILURE:
            err_msg = "CL_BUILD_PROGRAM_FAILURE";
            break;
        case CL_MAP_FAILURE:
            err_msg = "CL_MAP_FAILURE";
            break;
        case CL_MISALIGNED_SUB_BUFFER_OFFSET:
            err_msg = "CL_MISALIGNED_SUB_BUFFER_OFFSET";
            break;
        case CL_EXEC_STATUS_ERROR_FOR_EVENTS_IN_WAIT_LIST:
            err_msg = "CL_EXEC_STATUS_ERROR_FOR_EVENTS_IN_WAIT_LIST";
            break;
        case CL_COMPILE_PROGRAM_FAILURE:
            err_msg = "CL_COMPILE_PROGRAM_FAILURE";
            break;
        case CL_LINKER_NOT_AVAILABLE:
            err_msg = "CL_LINKER_NOT_AVAILABLE";
            break;
        case CL_LINK_PROGRAM_FAILURE:
            err_msg = "CL_LINK_PROGRAM_FAILURE";
            break;
        case CL_DEVICE_PARTITION_FAILED:
            err_msg = "CL_DEVICE_PARTITION_FAILED";
            break;
        case CL_KERNEL_ARG_INFO_NOT_AVAILABLE:
            err_msg = "CL_KERNEL_ARG_INFO_NOT_AVAILABLE";
            break;
        case CL_INVALID_VALUE:
            err_msg = "CL_INVALID_VALUE";
            break;
        case CL_INVALID_DEVICE_TYPE:
            err_msg = "CL_INVALID_DEVICE_TYPE";
            break;
        case CL_INVALID_PLATFORM:
            err_msg = "CL_INVALID_PLATFORM";
            break;
        case CL_INVALID_DEVICE:
            err_msg = "CL_INVALID_DEVICE";
            break;
        case CL_INVALID_CONTEXT:
            err_msg = "CL_INVALID_CONTEXT";
            break;
        case CL_INVALID_QUEUE_PROPERTIES:
            err_msg = "CL_INVALID_QUEUE_PROPERTIES";
            break;
        case CL_INVALID_COMMAND_QUEUE:
            err_msg = "CL_INVALID_COMMAND_QUEUE";
            break;
        case CL_INVALID_HOST_PTR:
            err_msg = "CL_INVALID_HOST_PTR";
            break;
        case CL_INVALID_MEM_OBJECT:
            err_msg = "CL_INVALID_MEM_OBJECT";
            break;
        case CL_INVALID_IMAGE_FORMAT_DESCRIPTOR:
            err_msg = "CL_INVALID_IMAGE_FORMAT_DESCRIPTOR";
            break;
        case CL_INVALID_IMAGE_SIZE:
            err_msg = "CL_INVALID_IMAGE_SIZE";
            break;
        case CL_INVALID_SAMPLER:
            err_msg = "CL_INVALID_SAMPLER";
            break;
        case CL_INVALID_BINARY:
            err_msg = "CL_INVALID_BINARY";
            break;
        case CL_INVALID_BUILD_OPTIONS:
            err_msg = "CL_INVALID_BUILD_OPTIONS";
            break;
        case CL_INVALID_PROGRAM:
            err_msg = "CL_INVALID_PROGRAM";
            break;
        case CL_INVALID_PROGRAM_EXECUTABLE:
            err_msg = "CL_INVALID_PROGRAM_EXECUTABLE";
            break;
        case CL_INVALID_KERNEL_NAME:
            err_msg = "CL_INVALID_KERNEL_NAME";
            break;
        case CL_INVALID_KERNEL_DEFINITION:
            err_msg = "CL_INVALID_KERNEL_DEFINITION";
            break;
        case CL_INVALID_KERNEL:
            err_msg = "CL_INVALID_KERNEL";
            break;
        case CL_INVALID_ARG_INDEX:
            err_msg = "CL_INVALID_ARG_INDEX";
            break;
        case CL_INVALID_ARG_VALUE:
            err_msg = "CL_INVALID_ARG_VALUE";
            break;
        case CL_INVALID_ARG_SIZE:
            err_msg = "CL_INVALID_ARG_SIZE";
            break;
        case CL_INVALID_KERNEL_ARGS:
            err_msg = "CL_INVALID_KERNEL_ARGS";
            break;
        case CL_INVALID_WORK_DIMENSION:
            err_msg = "CL_INVALID_WORK_DIMENSION";
            break;
        case CL_INVALID_WORK_GROUP_SIZE:
            err_msg = "CL_INVALID_WORK_GROUP_SIZE";
            break;
        case CL_INVALID_WORK_ITEM_SIZE:
            err_msg = "CL_INVALID_WORK_ITEM_SIZE";
            break;
        case CL_INVALID_GLOBAL_OFFSET:
            err_msg = "CL_INVALID_GLOBAL_OFFSET";
            break;
        case CL_INVALID_EVENT_WAIT_LIST:
            err_msg = "CL_INVALID_EVENT_WAIT_LIST";
            break;
        case CL_INVALID_EVENT:
            err_msg = "CL_INVALID_EVENT";
            break;
        case CL_INVALID_OPERATION:
            err_msg = "CL_INVALID_OPERATION";
            break;
        case CL_INVALID_GL_OBJECT:
            err_msg = "CL_INVALID_GL_OBJECT";
            break;
        case CL_INVALID_BUFFER_SIZE:
            err_msg = "CL_INVALID_BUFFER_SIZE";
            break;
        case CL_INVALID_MIP_LEVEL:
            err_msg = "CL_INVALID_MIP_LEVEL";
            break;
        case CL_INVALID_GLOBAL_WORK_SIZE:
            err_msg = "CL_INVALID_GLOBAL_WORK_SIZE";
            break;
        case CL_INVALID_PROPERTY:
            err_msg = "CL_INVALID_PROPERTY";
            break;
        case CL_INVALID_IMAGE_DESCRIPTOR:
            err_msg = "CL_INVALID_IMAGE_DESCRIPTOR";
            break;
        case CL_INVALID_COMPILER_OPTIONS:
            err_msg = "CL_INVALID_COMPILER_OPTIONS";
            break;
        case CL_INVALID_LINKER_OPTIONS:
            err_msg = "CL_INVALID_LINKER_OPTIONS";
            break;
        case CL_INVALID_DEVICE_PARTITION_COUNT:
            err_msg = "CL_INVALID_DEVICE_PARTITION_COUNT";
            break;
        case CL_INVALID_GL_SHAREGROUP_REFERENCE_KHR:
            err_msg = "CL_INVALID_GL_SHAREGROUP_REFERENCE_KHR";
            break;
        case CL_PLATFORM_NOT_FOUND_KHR:
            err_msg = "CL_PLATFORM_NOT_FOUND_KHR";
            break;
        default:
            err_msg = "UNKNOWN_ERROR";
    }

    return err_msg;

}


/*
 * ==================================================
 *
 *  CL DEVICE
 *
 * ==================================================
 */


CLCaster::device::device(cl_device_id device_id, cl_platform_id platform_id) {

	this->device_id = device_id;
	this->platform_id = platform_id;

	int error = 0;
	error = clGetPlatformInfo(platform_id, CL_PLATFORM_NAME, 128, (void*)&data.platform_name, nullptr);
	if (cl_assert(error)) {
		Logger::log("Failed at function clGetPlatformInfo", Logger::LogLevel::ERROR, __LINE__, __FILE__);
		return;
	}
		
	error = clGetDeviceInfo(device_id, CL_DEVICE_VERSION, sizeof(char) * 128, &data.opencl_version, NULL);

	// Just check for error on the first call
	if (cl_assert(error)) {
		Logger::log("Failed at function clGetDeviceInfo", Logger::LogLevel::ERROR, __LINE__, __FILE__);
		return;
	}

	error = clGetDeviceInfo(device_id, CL_DEVICE_TYPE, sizeof(cl_device_type), &data.device_type, NULL);
	error = clGetDeviceInfo(device_id, CL_DEVICE_MAX_CLOCK_FREQUENCY, sizeof(cl_uint), &data.clock_frequency, NULL);
	error = clGetDeviceInfo(device_id, CL_DEVICE_MAX_COMPUTE_UNITS, sizeof(cl_uint), &data.compute_units, NULL);
	error = clGetDeviceInfo(device_id, CL_DEVICE_EXTENSIONS, 1024, &data.device_extensions, NULL);
	error = clGetDeviceInfo(device_id, CL_DEVICE_NAME, 256, &data.device_name, NULL);
	error = clGetDeviceInfo(device_id, CL_DEVICE_ENDIAN_LITTLE, sizeof(cl_bool), &is_little_endian, NULL);

	// Check for the sharing extension
	if (std::string(data.device_extensions).find("cl_khr_gl_sharing") != std::string::npos ||
		std::string(data.device_extensions).find("cl_APPLE_gl_sharing") != std::string::npos) {
		cl_gl_sharing = true;
	}
}


CLCaster::device::device(const device& d) {

	// member values, copy individually
	device_id = d.device_id;
	platform_id = d.platform_id;
	is_little_endian = d.is_little_endian;
	cl_gl_sharing = d.cl_gl_sharing;

	// struct so it copies by value
	data = d.data;

}

void CLCaster::device::print(std::ostream& stream) const {

	stream << "\n\tDevice ID        : " << device_id << std::endl;
    stream <<   "\tPlatform ID      : " << platform_id << std::endl;

    stream <<   "\tDevice Name      : " << data.device_name << std::endl;
	stream <<   "\tPlatform Name    : " << data.platform_name << std::endl;
    stream <<   "\tDevice Type      : ";

    if (data.device_type == CL_DEVICE_TYPE_CPU)
        stream << "CPU" << std::endl;

    else if (data.device_type == CL_DEVICE_TYPE_GPU)
        stream << "GPU" << std::endl;

    else if (data.device_type == CL_DEVICE_TYPE_ACCELERATOR)
        stream << "Accelerator" << std::endl;

	stream <<   "\tOpenCL Version   : " << data.opencl_version << std::endl;
	stream <<   "\tSupports sharing : " << std::boolalpha << cl_gl_sharing << std::endl;

	stream << "\tIs Little Endian : " << std::boolalpha << is_little_endian << std::endl;
	stream << "\tClock Frequency  : " << data.clock_frequency << std::endl;
	stream << "\tCompute Units    : " << data.compute_units << std::endl;

	stream << "\n*Extensions*" << std::endl;
	stream << data.device_extensions << std::endl;
	stream << "\n";

}

void CLCaster::device::print_packed_data(std::ostream& stream) {
	stream.write(reinterpret_cast<char*>(&data), sizeof(data));
}
