/*======================================================================================

	Program:		Range Imaging Toolkit (RITK)
	Module:			cl_utils.cxx
	Version:		TODO
	Date:			TODO

	Copyright (c) 2010-2011
	Pattern Recognition Lab, 
    University Erlangen-Nuremberg, Germany
    All rights reserved.

	This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 
	See RITKCopyright.txt for more information.

	http://www5.informatik.uni-erlangen.de/research/software/ritk

========================================================================================*/

#include "cl_utils.h"
#include "CL/cl.h"


char* getErrorMessage(cl_int err) {
  switch (err) {
        case CL_SUCCESS:									return "Success!";
        case CL_DEVICE_NOT_FOUND:							return "Device not found.";
        case CL_DEVICE_NOT_AVAILABLE:						return "Device not available";
        case CL_COMPILER_NOT_AVAILABLE:						return "Compiler not available";
        case CL_MEM_OBJECT_ALLOCATION_FAILURE:				return "Memory object allocation failure";
        case CL_OUT_OF_RESOURCES:							return "Out of resources";
        case CL_OUT_OF_HOST_MEMORY:							return "Out of host memory";
        case CL_PROFILING_INFO_NOT_AVAILABLE:				return "Profiling information not available";
        case CL_MEM_COPY_OVERLAP:							return "Memory copy overlap";
        case CL_IMAGE_FORMAT_MISMATCH:						return "Image format mismatch";
        case CL_IMAGE_FORMAT_NOT_SUPPORTED:					return "Image format not supported";
        case CL_BUILD_PROGRAM_FAILURE:						return "Program build failure";
        case CL_MAP_FAILURE:								return "Map failure";
		//case CL_MISALIGNED_SUB_BUFFER_OFFSET:				return "Misaligned sub buffer offset";
		//case CL_EXEC_STATUS_ERROR_FOR_EVENTS_IN_WAIT_LIST:	return "Execution status error for events in wait list";
        case CL_INVALID_VALUE:								return "Invalid value";
        case CL_INVALID_DEVICE_TYPE:						return "Invalid device type";
        case CL_INVALID_PLATFORM:							return "Invalid platform";
        case CL_INVALID_DEVICE:								return "Invalid device";
        case CL_INVALID_CONTEXT:							return "Invalid context";
        case CL_INVALID_QUEUE_PROPERTIES:					return "Invalid queue properties";
        case CL_INVALID_COMMAND_QUEUE:						return "Invalid command queue";
        case CL_INVALID_HOST_PTR:							return "Invalid host pointer";
        case CL_INVALID_MEM_OBJECT:							return "Invalid memory object";
        case CL_INVALID_IMAGE_FORMAT_DESCRIPTOR:			return "Invalid image format descriptor";
        case CL_INVALID_IMAGE_SIZE:							return "Invalid image size";
        case CL_INVALID_SAMPLER:							return "Invalid sampler";
        case CL_INVALID_BINARY:								return "Invalid binary";
        case CL_INVALID_BUILD_OPTIONS:						return "Invalid build options";
        case CL_INVALID_PROGRAM:							return "Invalid program";
        case CL_INVALID_PROGRAM_EXECUTABLE:					return "Invalid program executable";
        case CL_INVALID_KERNEL_NAME:						return "Invalid kernel name";
        case CL_INVALID_KERNEL_DEFINITION:					return "Invalid kernel definition";
        case CL_INVALID_KERNEL:								return "Invalid kernel";
        case CL_INVALID_ARG_INDEX:							return "Invalid argument index";
        case CL_INVALID_ARG_VALUE:							return "Invalid argument value";
        case CL_INVALID_ARG_SIZE:							return "Invalid argument size";
        case CL_INVALID_KERNEL_ARGS:						return "Invalid kernel arguments";
        case CL_INVALID_WORK_DIMENSION:						return "Invalid work dimension";
        case CL_INVALID_WORK_GROUP_SIZE:					return "Invalid work group size";
        case CL_INVALID_WORK_ITEM_SIZE:						return "Invalid work item size";
        case CL_INVALID_GLOBAL_OFFSET:						return "Invalid global offset";
        case CL_INVALID_EVENT_WAIT_LIST:					return "Invalid event wait list";
        case CL_INVALID_EVENT:								return "Invalid event";
        case CL_INVALID_OPERATION:							return "Invalid operation";
        case CL_INVALID_GL_OBJECT:							return "Invalid OpenGL object";
        case CL_INVALID_BUFFER_SIZE:						return "Invalid buffer size";
        case CL_INVALID_MIP_LEVEL:							return "Invalid mip-map level";
		case CL_INVALID_GLOBAL_WORK_SIZE:					return "Invalid global work size";
		//case CL_INVALID_PROPERTY:							return "Invalid property";
		default: return "Unknown";
    }
}

//---------------------------------------------------------------------------------
	cl_context createAvailableContext(cl_device_type deviceType) {
		
		
		cl_uint lenPlatformList;
		cl_uint numPlatforms;

		CL_CONTEXT_PLATFORM;

		// Get count of Platforms
		if(clGetPlatformIDs(0, NULL, &lenPlatformList) != CL_SUCCESS) return NULL;
		
		if(lenPlatformList < 1) return NULL;

		cl_platform_id *platforms = new cl_platform_id[lenPlatformList];
		
		// Get all Platforms
		clSafeCall(clGetPlatformIDs(lenPlatformList, platforms, &numPlatforms));

		// For each platform, try to get a device from type deviceType
		for(cl_uint i = 0; i < numPlatforms && i < lenPlatformList; i++){
			cl_uint lenDeviceList;
			cl_uint numDevices;
			
			// get count of devices on the platform
			if(clGetDeviceIDs(platforms[i], deviceType, 0, NULL, &lenDeviceList) != CL_SUCCESS) continue;

			if(lenDeviceList < 1) continue;			

			cl_device_id *devices = new cl_device_id[lenDeviceList];

			// get devices of the platform
			if(clGetDeviceIDs(platforms[i], deviceType, lenDeviceList, devices, &numDevices) != CL_SUCCESS) continue;

			// for each device, try to get a valid context
			for(cl_uint j = 0; j < numDevices && j < lenPlatformList; j++){

				cl_context_properties contextProperties[] = {CL_CONTEXT_PLATFORM, (cl_context_properties)(platforms[i]), 0};

				int err;
				
				cl_context context = clCreateContext(contextProperties, 1, &devices[j], NULL, NULL, &err);
				if (err != CL_SUCCESS || context == NULL) continue;

				if (!checkIfContextValid(context)){
						clSafeCall(clReleaseContext(context));
						continue;
				}

				delete [] platforms;
				delete [] devices;
				return context;

			}

			delete [] devices;

		}


		// if no valid device found, return NULL
		delete [] platforms ;
		return NULL;


	}


	
//---------------------------------------------------------------------------------
	bool checkIfContextValid(cl_context context) {
		
		// get the Device connected to context
		size_t retSize;
		cl_device_id device;
		clSafeCall(clGetContextInfo(context, CL_CONTEXT_DEVICES, sizeof(device), &device, &retSize));
		if (retSize != sizeof(device)) clDie("You don't have any idea why this isn't working.");
		
		
		char buffer[500];
		
		// get the device name
		clSafeCall(clGetDeviceInfo(device, CL_DEVICE_NAME, 500, buffer, &retSize));
		std::cout << "Testing \"" << buffer << "\"" << std::endl;
		
		// get the device version string
		clSafeCall(clGetDeviceInfo(device, CL_DEVICE_VERSION, 500, buffer, &retSize));
		if(retSize < 11) return false;



		// check version string to be valid
		if(strncmp(buffer, "OpenCL ", 7)) return false;
		
		// get version number
		char *end;
		double versionNumber;
		versionNumber = strtod(buffer+7, &end);
		if(*end != ' ') return false;
		
		//std::cout << "OpenCL Version: " << versionNumber << std::endl;
		
		double requiredVersion = 1.0;

		// testing version number to be at least 1.0
		if (versionNumber < requiredVersion){
			std::cout << "\t\tFailure: Device does not support OpenCL " << requiredVersion << "1.0!" << std::endl;
			return false;
		}


		std::cout << "\t\tValid context found. (OpenCL " << versionNumber << ")"<< std::endl;
		return true;

	}
	


