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

	Program:		Range Imaging Toolkit (RITK)
	Module:			OpenCLContext.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 "OpenCLContext.h"
#include "cl_utils.h"
#include <iostream>
#include <cstring>
#include "CL/cl.h"
#include <QMutex>

cl_context createAvailableContext(cl_device_type deviceType);

namespace ritk
{



//---------------------------------------------------------------------------------
	OpenCLContext::OpenCLContext()
	{
		
		
		// INITIALIZE OPENCL DEVICE
		
		m_clContext = createAvailableContext(CL_DEVICE_TYPE_ACCELERATOR);
		if(m_clContext == NULL)
			m_clContext = createAvailableContext(CL_DEVICE_TYPE_GPU);
		if(m_clContext == NULL)
			m_clContext = createAvailableContext(CL_DEVICE_TYPE_CPU);
		if(m_clContext == NULL)
			m_clContext = createAvailableContext(CL_DEVICE_TYPE_ALL);

		if(m_clContext == NULL) clDie("No device found!");

		// get the Device connected to context
		size_t retSize;
		clSafeCall(clGetContextInfo(m_clContext, CL_CONTEXT_DEVICES, sizeof(m_clDevice), &m_clDevice, &retSize));
		if (retSize != sizeof(m_clDevice)) clDie("You don't have any idea why this isn't working.");

		// Print Name of device
		char name[500];
		clSafeCall(clGetDeviceInfo(m_clDevice, CL_DEVICE_NAME, 500, name, NULL));
		name[499] = 0;
		
		std::cout << "Using OpenCL-Device: " << name << std::endl;
		

		
		// INITIALIZE COMMAND QUEUE

		int err;
		m_clCommandQueue = clCreateCommandQueue(m_clContext, m_clDevice, 0, &err);
		clSafeCall(err);
		
		
		numMemObjs= 0;
		numProgs= 0;
		numKernels=0;
		numMaps=0;
	}



//---------------------------------------------------------------------------------
	OpenCLContext::~OpenCLContext()
	{
		std::cout << "numMemObjs: " << numMemObjs << std::endl;
		std::cout << "numKernels: " << numKernels << std::endl;
		std::cout << "numMaps: " << numMaps << std::endl;
		std::cout << "numProgs: " << numProgs << std::endl;
		
		std::cout<<"~OpenCLContext()"<<std::endl;
		//clSafeCall(clFlush(m_clCommandQueue));
		//std::cout<<"called clflush..." << std::endl;
		if(m_clCommandQueue != NULL) clSafeCall(clReleaseCommandQueue(m_clCommandQueue));
		std::cout<<"~OpenCLCommandQueue released."<<std::endl;
		if(m_clContext != NULL) clSafeCall(clReleaseContext(m_clContext));
		std::cout<<"~OpenCLContext() end"<<std::endl;
		
	}



//---------------------------------------------------------------------------------
// OPENCL FUNCTIONS
//---------------------------------------------------------------------------------

	cl_mem OpenCLContext::oclCreateBuffer(cl_mem_flags flags, size_t size, void *host_ptr){
		
		
		//std::cout << "oclCreateBuffer (size: " << size << ")" << std::endl;

		cl_mem erg;
		cl_int err;

		m_mutex.lock();
		erg = clCreateBuffer(m_clContext, flags, size, host_ptr, &err);
		m_mutex.unlock();
		clSafeCall(err);

		numMemObjs++;

		return erg;

	}

//---------------------------------------------------------------------------------

	cl_mem OpenCLContext::oclCreateImage2D(cl_mem_flags flags, const cl_image_format *image_format, size_t image_width, size_t image_height, size_t image_row_pitch, void *host_ptr){

		cl_mem erg;
		cl_int err;
		m_mutex.lock();
		erg = clCreateImage2D(m_clContext, flags, image_format, image_width, image_height, image_row_pitch, host_ptr, &err);
		m_mutex.unlock();
		clSafeCall(err);

		numMemObjs++;

		return erg;

	}

	
//---------------------------------------------------------------------------------
	void* OpenCLContext::oclEnqueueMapBuffer(cl_mem buffer, cl_bool blocking_map, cl_map_flags map_flags, size_t offset, size_t cb, cl_uint num_events_in_wait_list, const cl_event *event_wait_list, cl_event *event){
		
		//std::cout << "oclEnqueueMapBuffer" << std::endl;
		
		void *erg;
		cl_int err;

		cl_event internalEvent;

		m_mutex.lock();
		erg = clEnqueueMapBuffer(m_clCommandQueue, buffer, CL_FALSE, map_flags, offset, cb, num_events_in_wait_list, event_wait_list, &internalEvent, &err);
		m_mutex.unlock();
		clSafeCall(err);

		if(event != NULL) *event = internalEvent;

		// Blockin Call outside of the mutex, otherwise possible deadlock
		if(blocking_map != CL_FALSE){
			clSafeCall(clWaitForEvents(1, &internalEvent));
		}

		numMaps++;

		return erg;
	
	}


//---------------------------------------------------------------------------------
	void OpenCLContext::oclEnqueueUnmapMemObject (cl_mem memobj, void *mapped_ptr, cl_uint num_events_in_wait_list, const cl_event *event_wait_list, cl_event *event){
		
		//std::cout << "oclEnqueueUnmapMemObject" << std::endl;
		cl_int err;

		m_mutex.lock();
		err = clEnqueueUnmapMemObject(m_clCommandQueue, memobj, mapped_ptr, num_events_in_wait_list, event_wait_list, event);
		m_mutex.unlock();

		clSafeCall(err);

		numMaps--;

	}


//---------------------------------------------------------------------------------
	void OpenCLContext::oclReleaseMemObject (cl_mem memobj){
		
		//std::cout << "oclReleaseMemObject" << std::endl;
		cl_int err;

		m_mutex.lock();
		err = clReleaseMemObject(memobj);
		m_mutex.unlock();

		clSafeCall(err);

		numMemObjs--;

	}



//---------------------------------------------------------------------------------
	void OpenCLContext::oclEnqueueReadBuffer(cl_mem buffer, cl_bool blocking_read, size_t offset, size_t cb, void *ptr, cl_uint num_events_in_wait_list, const cl_event *event_wait_list, cl_event *event){

		//std::cout << "oclEnqueueReadBuffer" << std::endl;
		cl_int err;
		cl_event internalEvent;

		m_mutex.lock();
		err = clEnqueueReadBuffer(m_clCommandQueue, buffer, CL_FALSE, offset, cb, ptr, num_events_in_wait_list, event_wait_list, &internalEvent);
		m_mutex.unlock();
		//std::cout << "oclEnqueueReadBuffer called." << std::endl;
		
		clSafeCall(err);

		if(event != NULL) *event = internalEvent;

		// Blockin Call outside of the mutex, otherwise possible deadlock
		if(blocking_read != CL_FALSE){
			//std::cout << "blocking until call done..." << std::endl;
			clSafeCall(clWaitForEvents(1, &internalEvent));
			//std::cout << "done." << std::endl;
		}

	}


	
	
	
//---------------------------------------------------------------------------------
	void OpenCLContext::oclEnqueueWriteBuffer(cl_mem buffer, cl_bool blocking_write, size_t offset, size_t cb, const void *ptr, cl_uint num_events_in_wait_list, const cl_event *event_wait_list, cl_event *event){

		//std::cout << "oclEnqueueWriteBuffer" << std::endl;
		cl_int err;
		cl_event internalEvent;

		m_mutex.lock();
		err = clEnqueueWriteBuffer(m_clCommandQueue, buffer, CL_FALSE, offset, cb, ptr, num_events_in_wait_list, event_wait_list, &internalEvent);
		m_mutex.unlock();
		//std::cout << "clEnqueueWriteBuffer called." << std::endl;
		
		clSafeCall(err);

		if(event != NULL) *event = internalEvent;

		// Blockin Call outside of the mutex, otherwise deadlock possible
		if(blocking_write != CL_FALSE){
			//std::cout << "blocking call done..." << std::endl;
			clSafeCall(clWaitForEvents(1, &internalEvent));
			//std::cout << "done." << std::endl;
		}

	}


//---------------------------------------------------------------------------------
	void OpenCLContext::oclEnqueueCopyBuffer(cl_mem src_buffer, cl_mem dst_buffer, size_t src_offset, size_t dst_offset, size_t cb, cl_uint num_events_in_wait_list, const cl_event *event_wait_list, cl_event *event){

		//std::cout << "oclEnqueueCopyBuffer" << std::endl;
		cl_int err;

		m_mutex.lock();
		err = clEnqueueCopyBuffer(m_clCommandQueue, src_buffer, dst_buffer, src_offset, dst_offset, cb, num_events_in_wait_list, event_wait_list, event);
		m_mutex.unlock();

		clSafeCall(err);

	}

//---------------------------------------------------------------------------------
	void OpenCLContext::oclEnqueueReadImage(cl_mem image, cl_bool blocking_read, const size_t origin[3], const size_t region[3], size_t row_pitch, size_t slice_pitch, void *ptr, cl_uint num_events_in_wait_list, const cl_event *event_wait_list, cl_event *event){

		cl_int err;
		cl_event internalEvent;

		m_mutex.lock();
		err = clEnqueueReadImage(m_clCommandQueue,image, CL_FALSE, origin, region, row_pitch, slice_pitch, ptr, num_events_in_wait_list, event_wait_list, &internalEvent);
		m_mutex.unlock();

		clSafeCall(err);

		if(event != NULL) *event = internalEvent;

		if(blocking_read != CL_FALSE){
			clSafeCall(clWaitForEvents(1, &internalEvent));
		}

	}
	

//---------------------------------------------------------------------------------
	void OpenCLContext::oclEnqueueWriteImage(cl_mem image, cl_bool blocking_write, const size_t origin[3], const size_t region[3], size_t input_row_pitch, size_t input_slice_pitch, const void * ptr, cl_uint num_events_in_wait_list, const cl_event *event_wait_list, cl_event *event){
		
		cl_int err;
		cl_event internalEvent;

		m_mutex.lock();
		err = clEnqueueWriteImage(m_clCommandQueue, image, CL_FALSE, origin, region, input_row_pitch, input_slice_pitch, ptr, num_events_in_wait_list, event_wait_list, &internalEvent);
		m_mutex.unlock();

		clSafeCall(err);

		if(event != NULL) *event = internalEvent;

		// Blockin Call outside of the mutex, otherwise deadlock possible
		if(blocking_write != CL_FALSE){
			//std::cout << "blocking call done..." << std::endl;
			clSafeCall(clWaitForEvents(1, &internalEvent));
			//std::cout << "done." << std::endl;
		}

	}

//---------------------------------------------------------------------------------
	void OpenCLContext::oclEnqueueCopyImage(cl_mem src_image, cl_mem dst_image, const size_t src_origin[3], const size_t dst_origin[3], const size_t region[3], cl_uint num_events_in_wait_list, const cl_event *event_wait_list, cl_event *event){
		
		cl_int err;

		m_mutex.lock();
		err = clEnqueueCopyImage(m_clCommandQueue, src_image, dst_image, src_origin, dst_origin, region, num_events_in_wait_list, event_wait_list, event);
		m_mutex.unlock();

		clSafeCall(err);

	}
	

//---------------------------------------------------------------------------------
	void OpenCLContext::oclEnqueueCopyImageToBuffer(cl_mem src_image, cl_mem dst_buffer, const size_t src_origin[3], const size_t region[3], size_t dst_offset, cl_uint num_events_in_wait_list, const cl_event *event_wait_list, cl_event *event){
		
		cl_int err;

		m_mutex.lock();
		err = clEnqueueCopyImageToBuffer(m_clCommandQueue, src_image, dst_buffer, src_origin, region, dst_offset, num_events_in_wait_list, event_wait_list, event);
		m_mutex.unlock();

		clSafeCall(err);

	}


//---------------------------------------------------------------------------------
	void OpenCLContext::oclEnqueueCopyBufferToImage(cl_mem src_buffer, cl_mem dst_image, size_t src_offset, const size_t dst_origin[3], const size_t region[3], cl_uint num_events_in_wait_list, const cl_event *event_wait_list, cl_event *event){
		
		cl_int err;

		m_mutex.lock();
		err = clEnqueueCopyBufferToImage(m_clCommandQueue, src_buffer, dst_image, src_offset, dst_origin, region, num_events_in_wait_list, event_wait_list, event);
		m_mutex.unlock();

		clSafeCall(err);

	}

//---------------------------------------------------------------------------------
	void OpenCLContext::oclWaitForEvents(cl_uint num_events, const cl_event *event_list){
		//std::cout << "oclWaitForEvents" << std::endl;

		clSafeCall(clWaitForEvents(num_events, event_list));

		//std::cout << "oclWaitForEvents done" << std::endl;
	}


//---------------------------------------------------------------------------------
	cl_program OpenCLContext::oclCreateProgramWithSource(const char *string, const size_t length){

		cl_int err;
		cl_program erg;

		m_mutex.lock();
		erg = clCreateProgramWithSource (m_clContext, 1, &string, &length, &err);
		m_mutex.unlock();

		clSafeCall(err);

		numProgs++;

		return erg;

	}


//---------------------------------------------------------------------------------
	void OpenCLContext::oclBuildProgram(cl_program program, const char *options){

		cl_int err;
		
		m_mutex.lock();
		err = clBuildProgram(program, 1, &m_clDevice, options, NULL, NULL);
		m_mutex.unlock();

		if(err != CL_SUCCESS){
			std::cerr << "Build Log:" << std::endl;
			// Shows the log
			char* build_log;
			size_t log_size;
			// First call to know the proper size
			clSafeCall(clGetProgramBuildInfo(program, m_clDevice, CL_PROGRAM_BUILD_LOG, 0, NULL, &log_size));
			build_log = new char[log_size+1];
			// Second call to get the log
			clSafeCall(clGetProgramBuildInfo(program, m_clDevice, CL_PROGRAM_BUILD_LOG, log_size, build_log, NULL));
			build_log[log_size] = '\0';
			std::cerr << build_log << std::endl;
			delete[] build_log;
	    }

		clSafeCall(err);
	
	}


//---------------------------------------------------------------------------------
	void OpenCLContext::oclReleaseProgram(cl_program program){

		cl_int err;
		
		m_mutex.lock();
		err = clReleaseProgram(program);
		m_mutex.unlock();

		clSafeCall(err);

		numProgs--;

	}

//---------------------------------------------------------------------------------
	void OpenCLContext::oclReleaseKernel(cl_kernel kernel){

		cl_int err;
		
		m_mutex.lock();
		err = clReleaseKernel(kernel);
		m_mutex.unlock();
		
		clSafeCall(err);

		numKernels--;

	}

//---------------------------------------------------------------------------------
	cl_kernel OpenCLContext::oclCreateKernel(cl_program program, const char *kernel_name){
		
		cl_int err;
		cl_kernel erg;

		m_mutex.lock();
		erg = clCreateKernel(program, kernel_name, &err);
		m_mutex.unlock();

		clSafeCall(err);

		numKernels++;

		return erg;

	}

//---------------------------------------------------------------------------------
	void OpenCLContext::oclSetKernelArg(cl_kernel kernel, cl_uint arg_index, size_t arg_size, const void *arg_value){

		cl_int err;

		m_mutex.lock();
		err = clSetKernelArg(kernel, arg_index, arg_size, arg_value);
		m_mutex.unlock();

		clSafeCall(err);

	}

//---------------------------------------------------------------------------------
	void OpenCLContext::oclEnqueueNDRangeKernel(cl_kernel kernel, cl_uint work_dim, const size_t *global_work_offset, const size_t *global_work_size, const size_t *local_work_size, cl_uint num_events_in_wait_list, const cl_event *event_wait_list, cl_event *clevent){

		cl_int err;

		m_mutex.lock();
		err = clEnqueueNDRangeKernel(m_clCommandQueue, kernel, work_dim, global_work_offset, global_work_size, local_work_size, num_events_in_wait_list, event_wait_list, clevent);
		m_mutex.unlock();

		clSafeCall(err);

	}

//---------------------------------------------------------------------------------
	void OpenCLContext::oclFinish(){

		clSafeCall(clFinish(m_clCommandQueue));

	}


//---------------------------------------------------------------------------------
	cl_sampler OpenCLContext::oclCreateSampler(cl_bool normalized_coords, cl_addressing_mode addressing_mode, cl_filter_mode filter_mode){

		cl_int err;
		cl_sampler erg;

		m_mutex.lock();
		erg = clCreateSampler(m_clContext, normalized_coords, addressing_mode, filter_mode, &err);
		m_mutex.unlock();

		clSafeCall(err);

		return erg;

	}

//---------------------------------------------------------------------------------
	void OpenCLContext::oclReleaseSampler(cl_sampler sampler){

		cl_int err;

		m_mutex.lock();
		err = clReleaseSampler(sampler);
		m_mutex.unlock();

		clSafeCall(err);

	}

	

//---------------------------------------------------------------------------------
	cl_int OpenCLContext::oclGetEventReturnValue(cl_event event){
		
		cl_int erg;
		size_t ergSize;
		cl_int err;

		this->oclWaitForEvents(1, &event);

		m_mutex.lock();
		err = clGetEventInfo(event, CL_EVENT_COMMAND_EXECUTION_STATUS, sizeof(erg), &erg, &ergSize);
		m_mutex.unlock();

		clSafeCall(err);

		if(ergSize != sizeof(erg)){std::cerr << "This should never happen!" << std::endl; exit(1);}

		return erg;	

	}


} // end namespace ritk